streamly-core 0.1.0 → 0.3.1
raw patch · 216 files changed
Files
- Changelog.md +133/−1
- LICENSE +41/−0
- configure +305/−256
- configure.ac +1/−1
- docs/ApiChangelogs/0.1.0-0.2.0.txt +1471/−0
- docs/ApiChangelogs/0.2.0-0.2.2.txt +306/−0
- docs/ApiChangelogs/0.2.2-0.3.0.txt +1889/−0
- docs/Changelog.md +133/−1
- docs/Readme.md +2/−1
- src/DocTestDataArray.hs +0/−14
- src/DocTestDataFold.hs +0/−28
- src/DocTestDataMutArray.hs +0/−10
- src/DocTestDataMutArrayGeneric.hs +0/−10
- src/DocTestDataParser.hs +0/−20
- src/DocTestDataStream.hs +0/−37
- src/DocTestDataStreamK.hs +0/−20
- src/DocTestDataUnfold.hs +0/−12
- src/Streamly/Console/Stdio.hs +26/−28
- src/Streamly/Control/Exception.hs +40/−0
- src/Streamly/Data/Array.hs +72/−60
- src/Streamly/Data/Array/Generic.hs +27/−11
- src/Streamly/Data/Fold.hs +239/−182
- src/Streamly/Data/MutArray.hs +50/−11
- src/Streamly/Data/MutArray/Generic.hs +35/−8
- src/Streamly/Data/MutByteArray.hs +131/−0
- src/Streamly/Data/Parser.hs +178/−69
- src/Streamly/Data/ParserK.hs +127/−15
- src/Streamly/Data/RingArray.hs +92/−0
- src/Streamly/Data/Scanl.hs +189/−0
- src/Streamly/Data/Stream.hs +412/−203
- src/Streamly/Data/Stream/Zip.hs +0/−16
- src/Streamly/Data/StreamK.hs +111/−47
- src/Streamly/Data/Unfold.hs +10/−1
- src/Streamly/FileSystem/Dir.hs +2/−0
- src/Streamly/FileSystem/DirIO.hs +41/−0
- src/Streamly/FileSystem/File.hs +4/−1
- src/Streamly/FileSystem/FileIO.hs +67/−0
- src/Streamly/FileSystem/Handle.hs +20/−30
- src/Streamly/FileSystem/Path.hs +194/−0
- src/Streamly/FileSystem/Path/Node.hs +37/−0
- src/Streamly/FileSystem/Path/Seg.hs +37/−0
- src/Streamly/FileSystem/Path/SegNode.hs +37/−0
- src/Streamly/Internal/Console/Stdio.hs +28/−13
- src/Streamly/Internal/Control/Exception.hs +290/−1
- src/Streamly/Internal/Data/Array.hs +1175/−569
- src/Streamly/Internal/Data/Array/Generic.hs +44/−258
- src/Streamly/Internal/Data/Array/Generic/Mut/Type.hs +0/−796
- src/Streamly/Internal/Data/Array/Generic/Type.hs +495/−0
- src/Streamly/Internal/Data/Array/Mut.hs +0/−86
- src/Streamly/Internal/Data/Array/Mut/Stream.hs +0/−323
- src/Streamly/Internal/Data/Array/Mut/Type.hs +0/−2356
- src/Streamly/Internal/Data/Array/Stream.hs +723/−0
- src/Streamly/Internal/Data/Array/Type.hs +1556/−591
- src/Streamly/Internal/Data/Binary/Parser.hs +399/−0
- src/Streamly/Internal/Data/Binary/Stream.hs +383/−0
- src/Streamly/Internal/Data/Builder.hs +12/−9
- src/Streamly/Internal/Data/CString.hs +109/−0
- src/Streamly/Internal/Data/Fold.hs +35/−2597
- src/Streamly/Internal/Data/Fold/Chunked.hs +36/−32
- src/Streamly/Internal/Data/Fold/Combinators.hs +2383/−0
- src/Streamly/Internal/Data/Fold/Container.hs +812/−97
- src/Streamly/Internal/Data/Fold/Exception.hs +199/−0
- src/Streamly/Internal/Data/Fold/Step.hs +14/−2
- src/Streamly/Internal/Data/Fold/Tee.hs +3/−1
- src/Streamly/Internal/Data/Fold/Type.hs +794/−185
- src/Streamly/Internal/Data/Fold/Window.hs +110/−95
- src/Streamly/Internal/Data/IOFinalizer.hs +12/−2
- src/Streamly/Internal/Data/IORef.hs +114/−0
- src/Streamly/Internal/Data/IORef/Unboxed.hs +0/−100
- src/Streamly/Internal/Data/IsMap.hs +8/−0
- src/Streamly/Internal/Data/List.hs +0/−169
- src/Streamly/Internal/Data/MutArray.hs +425/−0
- src/Streamly/Internal/Data/MutArray/Generic.hs +981/−0
- src/Streamly/Internal/Data/MutArray/Lib.c +15/−0
- src/Streamly/Internal/Data/MutArray/Stream.hs +146/−0
- src/Streamly/Internal/Data/MutArray/Type.hs +4221/−0
- src/Streamly/Internal/Data/MutByteArray.hs +241/−0
- src/Streamly/Internal/Data/MutByteArray/Type.hs +447/−0
- src/Streamly/Internal/Data/Parser.hs +3639/−14
- src/Streamly/Internal/Data/Parser/ParserD.hs +0/−3629
- src/Streamly/Internal/Data/Parser/ParserD/Tee.hs +0/−617
- src/Streamly/Internal/Data/Parser/ParserD/Type.hs +0/−1429
- src/Streamly/Internal/Data/Parser/ParserK/Type.hs +0/−545
- src/Streamly/Internal/Data/Parser/Tee.hs +617/−0
- src/Streamly/Internal/Data/Parser/Type.hs +1548/−0
- src/Streamly/Internal/Data/ParserDrivers.h +1100/−0
- src/Streamly/Internal/Data/ParserDrivers.hs +141/−0
- src/Streamly/Internal/Data/ParserK.hs +39/−0
- src/Streamly/Internal/Data/ParserK/Type.hs +629/−0
- src/Streamly/Internal/Data/Path.hs +58/−0
- src/Streamly/Internal/Data/Pipe.hs +18/−255
- src/Streamly/Internal/Data/Pipe/Type.hs +517/−232
- src/Streamly/Internal/Data/Producer.hs +6/−13
- src/Streamly/Internal/Data/Producer/Source.hs +78/−54
- src/Streamly/Internal/Data/Producer/Type.hs +5/−1
- src/Streamly/Internal/Data/Refold/Type.hs +2/−2
- src/Streamly/Internal/Data/Ring.hs +0/−164
- src/Streamly/Internal/Data/Ring/Unboxed.hs +0/−615
- src/Streamly/Internal/Data/RingArray.hs +963/−0
- src/Streamly/Internal/Data/RingArray/Generic.hs +189/−0
- src/Streamly/Internal/Data/Scanl.hs +71/−0
- src/Streamly/Internal/Data/Scanl/Combinators.hs +2393/−0
- src/Streamly/Internal/Data/Scanl/Container.hs +850/−0
- src/Streamly/Internal/Data/Scanl/Type.hs +2030/−0
- src/Streamly/Internal/Data/Scanl/Window.hs +513/−0
- src/Streamly/Internal/Data/Scanr.hs +401/−0
- src/Streamly/Internal/Data/Serialize/TH.hs +529/−0
- src/Streamly/Internal/Data/Serialize/TH/Bottom.hs +478/−0
- src/Streamly/Internal/Data/Serialize/TH/Common.hs +70/−0
- src/Streamly/Internal/Data/Serialize/TH/RecHeader.hs +413/−0
- src/Streamly/Internal/Data/Serialize/Type.hs +321/−0
- src/Streamly/Internal/Data/Stream.hs +30/−3
- src/Streamly/Internal/Data/Stream/Bottom.hs +0/−670
- src/Streamly/Internal/Data/Stream/Chunked.hs +0/−1215
- src/Streamly/Internal/Data/Stream/Common.hs +0/−105
- src/Streamly/Internal/Data/Stream/Container.hs +308/−0
- src/Streamly/Internal/Data/Stream/Cross.hs +0/−143
- src/Streamly/Internal/Data/Stream/Eliminate.hs +509/−230
- src/Streamly/Internal/Data/Stream/Enumerate.hs +0/−560
- src/Streamly/Internal/Data/Stream/Exception.hs +665/−97
- src/Streamly/Internal/Data/Stream/Expand.hs +0/−893
- src/Streamly/Internal/Data/Stream/Generate.hs +1214/−460
- src/Streamly/Internal/Data/Stream/Lift.hs +71/−37
- src/Streamly/Internal/Data/Stream/Nesting.hs +3981/−0
- src/Streamly/Internal/Data/Stream/Reduce.hs +0/−444
- src/Streamly/Internal/Data/Stream/Step.hs +39/−0
- src/Streamly/Internal/Data/Stream/StreamD.hs +3/−29
- src/Streamly/Internal/Data/Stream/StreamD/Container.hs +0/−302
- src/Streamly/Internal/Data/Stream/StreamD/Eliminate.hs +0/−833
- src/Streamly/Internal/Data/Stream/StreamD/Exception.hs +0/−479
- src/Streamly/Internal/Data/Stream/StreamD/Generate.hs +0/−1205
- src/Streamly/Internal/Data/Stream/StreamD/Lift.hs +0/−129
- src/Streamly/Internal/Data/Stream/StreamD/Nesting.hs +0/−3111
- src/Streamly/Internal/Data/Stream/StreamD/Step.hs +0/−39
- src/Streamly/Internal/Data/Stream/StreamD/Top.hs +0/−353
- src/Streamly/Internal/Data/Stream/StreamD/Transform.hs +0/−1945
- src/Streamly/Internal/Data/Stream/StreamD/Transformer.hs +0/−182
- src/Streamly/Internal/Data/Stream/StreamD/Type.hs +0/−2074
- src/Streamly/Internal/Data/Stream/StreamDK.hs +0/−52
- src/Streamly/Internal/Data/Stream/StreamK.hs +0/−1372
- src/Streamly/Internal/Data/Stream/StreamK/Alt.hs +0/−244
- src/Streamly/Internal/Data/Stream/StreamK/Transformer.hs +0/−79
- src/Streamly/Internal/Data/Stream/StreamK/Type.hs +0/−2063
- src/Streamly/Internal/Data/Stream/Top.hs +457/−0
- src/Streamly/Internal/Data/Stream/Transform.hs +2289/−1056
- src/Streamly/Internal/Data/Stream/Transformer.hs +118/−44
- src/Streamly/Internal/Data/Stream/Type.hs +2704/−491
- src/Streamly/Internal/Data/Stream/Zip.hs +0/−91
- src/Streamly/Internal/Data/StreamK.hs +1397/−0
- src/Streamly/Internal/Data/StreamK/Alt.hs +244/−0
- src/Streamly/Internal/Data/StreamK/Transformer.hs +91/−0
- src/Streamly/Internal/Data/StreamK/Type.hs +3126/−0
- src/Streamly/Internal/Data/Time/Clock.hs +4/−4
- src/Streamly/Internal/Data/Time/Clock/Type.hsc +5/−5
- src/Streamly/Internal/Data/Time/Units.hs +1/−4
- src/Streamly/Internal/Data/Unbox.hs +923/−0
- src/Streamly/Internal/Data/Unbox/TH.hs +499/−0
- src/Streamly/Internal/Data/Unboxed.hs +0/−855
- src/Streamly/Internal/Data/Unfold.hs +79/−99
- src/Streamly/Internal/Data/Unfold/Enumeration.hs +14/−45
- src/Streamly/Internal/Data/Unfold/Type.hs +259/−64
- src/Streamly/Internal/FileSystem/Dir.hs +50/−286
- src/Streamly/Internal/FileSystem/DirIO.hs +532/−0
- src/Streamly/Internal/FileSystem/DirOptions.hs +125/−0
- src/Streamly/Internal/FileSystem/File.hs +59/−39
- src/Streamly/Internal/FileSystem/File/Common.hs +97/−0
- src/Streamly/Internal/FileSystem/FileIO.hs +643/−0
- src/Streamly/Internal/FileSystem/Handle.hs +56/−69
- src/Streamly/Internal/FileSystem/Path.hs +129/−0
- src/Streamly/Internal/FileSystem/Path/Common.hs +1564/−0
- src/Streamly/Internal/FileSystem/Path/Node.hs +20/−0
- src/Streamly/Internal/FileSystem/Path/Seg.hs +20/−0
- src/Streamly/Internal/FileSystem/Path/SegNode.hs +20/−0
- src/Streamly/Internal/FileSystem/Posix/Errno.hs +60/−0
- src/Streamly/Internal/FileSystem/Posix/File.hsc +316/−0
- src/Streamly/Internal/FileSystem/Posix/ReadDir.hsc +931/−0
- src/Streamly/Internal/FileSystem/PosixPath.hs +1611/−0
- src/Streamly/Internal/FileSystem/PosixPath/Node.hs +162/−0
- src/Streamly/Internal/FileSystem/PosixPath/Seg.hs +172/−0
- src/Streamly/Internal/FileSystem/PosixPath/SegNode.hs +311/−0
- src/Streamly/Internal/FileSystem/Windows/File.hsc +202/−0
- src/Streamly/Internal/FileSystem/Windows/ReadDir.hsc +273/−0
- src/Streamly/Internal/FileSystem/WindowsPath.hs +366/−0
- src/Streamly/Internal/FileSystem/WindowsPath/Node.hs +2/−0
- src/Streamly/Internal/FileSystem/WindowsPath/Seg.hs +2/−0
- src/Streamly/Internal/FileSystem/WindowsPath/SegNode.hs +2/−0
- src/Streamly/Internal/Serialize/FromBytes.hs +0/−394
- src/Streamly/Internal/Serialize/ToBytes.hs +0/−374
- src/Streamly/Internal/Unicode/Array.hs +8/−2
- src/Streamly/Internal/Unicode/Parser.hs +354/−25
- src/Streamly/Internal/Unicode/Stream.hs +369/−76
- src/Streamly/Internal/Unicode/String.hs +18/−13
- src/Streamly/Unicode/Parser.hs +12/−2
- src/Streamly/Unicode/Stream.hs +17/−10
- src/Streamly/Unicode/String.hs +15/−1
- src/config.h.in +2/−2
- src/deprecation.h +9/−0
- src/doctest/DocTestControlException.hs +13/−0
- src/doctest/DocTestDataArray.hs +22/−0
- src/doctest/DocTestDataFold.hs +33/−0
- src/doctest/DocTestDataMutArray.hs +11/−0
- src/doctest/DocTestDataMutArrayGeneric.hs +10/−0
- src/doctest/DocTestDataParser.hs +20/−0
- src/doctest/DocTestDataParserK.hs +18/−0
- src/doctest/DocTestDataScanl.hs +36/−0
- src/doctest/DocTestDataStream.hs +45/−0
- src/doctest/DocTestDataStreamK.hs +24/−0
- src/doctest/DocTestDataUnfold.hs +13/−0
- src/doctest/DocTestFileSystemHandle.hs +15/−0
- src/doctest/DocTestFileSystemPath.hs +20/−0
- src/doctest/DocTestFileSystemPosixPath.hs +19/−0
- src/doctest/DocTestFileSystemWindowsPath.hs +21/−0
- src/doctest/DocTestUnicodeParser.hs +10/−0
- src/doctest/DocTestUnicodeStream.hs +12/−0
- src/doctest/DocTestUnicodeString.hs +5/−0
- streamly-core.cabal +277/−162
Changelog.md view
@@ -1,8 +1,140 @@ # Changelog +## 0.3.1 (May 2026)++* Fixed `Path.fromString` truncation when unicode chars are present.+* Fixed `DirIO.followSymlinks` option not working correctly on macOS.++## 0.3.0 (Sep 2025)++See [0.2.2-0.3.0 API Changelog](/core/docs/ApiChangelogs/0.2.2-0.3.0.txt) for a+full list of deprecations, additions, and changes to the function signatures.++### Enhancements++* Added APIs for prompt cleanup of resources, allowing guaranteed+ cleanup as an alternative to GC-based cleanup.+* Added operations for fair nesting of inner and outer streams for+ exploring them equally, generally useful but especially useful for logic+ programming use cases.+* Introduced `Streamly.Data.Scanl` with a new `Scanl` type. Scans can+ split a stream into multiple streams, process them independently, and+ merge the results. The `Fold` type is now split into `Fold` and `Scanl`.+* Added `RingArray` module for high-performance, unboxed circular buffers.+* Added `Streamly.FileSystem.Path` module with a `Path` type for flexibly typed+ file system paths.+* Added `Streamly.FileSystem.DirIO` and `Streamly.FileSystem.FileIO` to replace+ the deprecated `Streamly.FileSystem.Dir` and `Streamly.FileSystem.File`. The+ new modules use Streamly’s native `Path` type instead of `FilePath`. `DirIO`+ APIs take a `ReadOptions` argument, and its directory read APIs do not follow+ symlinks by default.+* Removed `Storable` constraint from:+ - `Streamly.Data.Stream.isInfixOf`+ - `Streamly.Data.Array.writeLastN`++### Deprecations++Following APIs/modules are deprecated and renamed or replaced with new+APIs.++* `Streamly.FileSystem.Dir`, `Streamly.FileSystem.File` have been replaced by+ new modules.+* Renamed `writeN`-like APIs to `createOf`-like in Array modules.+* Renamed `new`-like APIs to `emptyOf`-like in Array modules.+* In the Fold module renamed `indexGeneric`, `lengthGeneric`, and `foldlM1'` to+ `genericIndex`, `genericLength`, and `foldl1M'` respectively.++### Internal API Changes++* In `Streamly.Internal.Data.Parser`, constructors `Partial`, `Continue`, and+ `Done` are deprecated and replaced with `SPartial`, `SContinue`, and `SDone`.+ Migration steps:+ * In parser step functions:+ - `Partial n` -> `SPartial (1-n)`+ - `Continue n` -> `SContinue (1-n)`+ - `Done n` -> `SDone (1-n)`+ - `Error` -> `SError`+ * Extract function now returns `Parser.Final` (instead of `Parser.Step`):+ - `Continue n` -> `FContinue (-n)`+ - `Done n` -> `FDone (-n)`+ - `Partial n` -> `FContinue (-n)`+ - `Error` -> `FError`+ * If `n` is used for decision-making, the logic must be updated accordingly.+ See docs for details.+* Internal (mut)array functions now use explicit IO callbacks instead of lifted+ callbacks.+* Removed `Storable` constraint from several ring buffer functions.+* Added `Streamly.Internal.Data.IORef` module exposing `IORef` and related+ functions.++## 0.2.2 (Jan 2024)++* Add fixities `infixr 5` for `cons` and `consM` functions.+* Fix a bug in Array `Eq` instance when the type is a sum type with+ differently sized constructors.+* lpackArraysChunksOf, compact, writeChunksWith, putChunksWith now take the+ buffer size in number of array elements instead of bytes.++## 0.2.1 (Dec 2023)++* Make the serialization of the unit constructor deterministic.+* Expose `pinnedSerialize` & `deserialize` via `Streamly.Data.Array`.++## 0.2.0 (Nov 2023)++See [0.1.0-0.2.0 API Changelog](https://github.com/composewell/streamly/blob/streamly-0.10.0/core/docs/ApiChangelogs/0.1.0-0.2.0.txt)+for a full list of API changes in this release. Only a few significant+changes are mentioned here.++### Breaking Changes++* `ParserK` in `Streamly.Data.ParserK` is not implicitly specialized+ to arrays anymore. To adapt to the new code, change `ParserK a m+ b` to `ParserK (Array a) m b` where the `Array` type comes from+ `Streamly.Data.Array`. This change also affected the signatures of+ `parseChunks` and `parseBreakChunks`.+* Changed the signature of 'Streamly.Data.Stream.handle' to make the+ exception handler monadic.+* Behavior change: Exceptions are now rethrown promptly in `bracketIO`.++### Enhancements++* __Serialization__: Added a `Streamly.Data.MutByteArray` module with a+ `Serialize` type class for fast binary serialization. The Data.Array+ module supplies the `serialize` and `deserialize` operations for arrays.+* __Unpinned Arrays__: Unboxed arrays are now created unpinned by default,+ they were created pinned earlier. During IO operations, unpinned arrays+ are automatically copied to pinned memory. When arrays are directly+ passed to IO operations programmers can choose to create them pinned to+ avoid a copy. To create pinned arrays, use the internal APIs with the+ `pinned*` prefix.+* StreamK now supports native exception handling routines (handle, bracketIO).+ Earlier we had to convert it to the `Stream` type for exception handling.++### Deprecations++See [0.1.0-0.2.0 API Changelog](https://github.com/composewell/streamly/blob/streamly-0.10.0/core/docs/ApiChangelogs/0.1.0-0.2.0.txt)+for a full list of deprecations.++### Internal API Changes++* Fold constructor has changed, added a `final` field to support+ finalization and cleanup of a chain of folds. The `extract` field is+ now used only for mapping the fold internal state to fold result for+ scanning purposes. If your fold does not require cleanup you can just use+ your existing `extract` function as `final` as well to adapt to this change.+* Many low level internal modules have been removed, they are entirely+ exported from higher level internal modules. If you were importing any+ of the missing low level modules then import the higher level modules instead.+* Internal module changes:+ * Streamly.Internal.Serialize.FromBytes -> Streamly.Internal.Data.Binary.Parser+ * Streamly.Internal.Serialize.ToBytes -> Streamly.Internal.Data.Binary.Stream+ * Streamly.Internal.Data.Unbox is now exported via Streamly.Internal.Data.Serialize+ * Streamly.Internal.Data.IORef.Unboxed is now exported via Streamly.Internal.Data.Serialize+ ## 0.1.0 (March 2023) -Also see [streamly-core-0.1.0 API Changelog](/core/docs/ApiChangelogs/0.1.0.txt) or+Also see [streamly-core-0.1.0 API Changelog](https://github.com/composewell/streamly/blob/streamly-0.10.0/core/docs/ApiChangelogs/0.1.0.txt) or https://hackage.haskell.org/package/streamly-core-0.1.0/docs/docs/ApiChangelogs/0.1.0.txt `streamly` package is split into two packages, (1) `streamly-core` that
LICENSE view
@@ -279,3 +279,44 @@ 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.++-------------------------------------------------------------------------------+th-utilities-0.2.5.0 (https://hackage.haskell.org/package/th-utilities)+-------------------------------------------------------------------------------+Copyright (c) 2016 FP Complete Corporation.++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.++-------------------------------------------------------------------------------+th-abstraction-0.5.0.0 (https://hackage.haskell.org/package/th-abstraction)+-------------------------------------------------------------------------------+Copyright (c) 2017-2020 Eric Mertens++Permission to use, copy, modify, and/or distribute this software for any purpose+with or without fee is hereby granted, provided that the above copyright notice+and this permission notice appear in all copies.++THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF+THIS SOFTWARE.
configure view
@@ -1,11 +1,11 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles.-# Generated by GNU Autoconf 2.71 for streamly-core 0.1.0.+# Generated by GNU Autoconf 2.72 for streamly-core 0.3.1. # # Report bugs to <streamly@composewell.com>. # #-# Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation,+# Copyright (C) 1992-1996, 1998-2017, 2020-2023 Free Software Foundation, # Inc. # #@@ -17,7 +17,6 @@ # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh-as_nop=: if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh@@ -26,12 +25,13 @@ # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST-else $as_nop- case `(set -o) 2>/dev/null` in #(+else case e in #(+ e) case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;;+esac ;; esac fi @@ -103,7 +103,7 @@ ;; esac-# We did not find ourselves, most probably we were run as `sh COMMAND'+# We did not find ourselves, most probably we were run as 'sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0@@ -133,15 +133,14 @@ esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail-# out after a failed `exec'.+# out after a failed 'exec'. printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then- as_bourne_compatible="as_nop=:-if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1+ as_bourne_compatible="if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=:@@ -149,12 +148,13 @@ # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST-else \$as_nop- case \`(set -o) 2>/dev/null\` in #(+else case e in #(+ e) case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;;+esac ;; esac fi "@@ -172,8 +172,9 @@ if ( set x; as_fn_ret_success y && test x = \"\$1\" ) then : -else \$as_nop- exitcode=1; echo positional parameters were not saved.+else case e in #(+ e) exitcode=1; echo positional parameters were not saved. ;;+esac fi test x\$exitcode = x0 || exit 1 blah=\$(echo \$(echo blah))@@ -186,14 +187,15 @@ if (eval "$as_required") 2>/dev/null then : as_have_required=yes-else $as_nop- as_have_required=no+else case e in #(+ e) as_have_required=no ;;+esac fi if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null then : -else $as_nop- as_save_IFS=$IFS; IFS=$PATH_SEPARATOR+else case e in #(+ e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do@@ -226,12 +228,13 @@ if $as_found then : -else $as_nop- if { test -f "$SHELL" || test -f "$SHELL.exe"; } &&+else case e in #(+ e) if { test -f "$SHELL" || test -f "$SHELL.exe"; } && as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null then : CONFIG_SHELL=$SHELL as_have_required=yes-fi+fi ;;+esac fi @@ -253,7 +256,7 @@ esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail-# out after a failed `exec'.+# out after a failed 'exec'. printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi@@ -273,7 +276,8 @@ $0: under such a shell if you do have one." fi exit 1-fi+fi ;;+esac fi fi SHELL=${CONFIG_SHELL-/bin/sh}@@ -312,14 +316,6 @@ as_fn_set_status $1 exit $1 } # as_fn_exit-# as_fn_nop-# ----------# Do nothing but, unlike ":", preserve the value of $?.-as_fn_nop ()-{- return $?-}-as_nop=as_fn_nop # as_fn_mkdir_p # -------------@@ -388,11 +384,12 @@ { eval $1+=\$2 }'-else $as_nop- as_fn_append ()+else case e in #(+ e) as_fn_append () { eval $1=\$$1\$2- }+ } ;;+esac fi # as_fn_append # as_fn_arith ARG...@@ -406,21 +403,14 @@ { as_val=$(( $* )) }'-else $as_nop- as_fn_arith ()+else case e in #(+ e) as_fn_arith () { as_val=`expr "$@" || test $? -eq 1`- }+ } ;;+esac fi # as_fn_arith -# as_fn_nop-# ----------# Do nothing but, unlike ":", preserve the value of $?.-as_fn_nop ()-{- return $?-}-as_nop=as_fn_nop # as_fn_error STATUS ERROR [LINENO LOG_FD] # ----------------------------------------@@ -494,6 +484,8 @@ /[$]LINENO/= ' <$as_myself | sed '+ t clear+ :clear s/[$]LINENO.*/&-/ t lineno b@@ -542,7 +534,6 @@ as_echo='printf %s\n' as_echo_n='printf %s' - rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file@@ -554,9 +545,9 @@ if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas:- # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.- # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.- # In both cases, we have to default to `cp -pR'.+ # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail.+ # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable.+ # In both cases, we have to default to 'cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then@@ -581,10 +572,12 @@ as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name.-as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"+as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g"+as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated # Sed expression to map a string onto a valid variable name.-as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"+as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g"+as_tr_sh="eval sed '$as_sed_sh'" # deprecated test -n "$DJDIR" || exec 7<&0 </dev/null@@ -610,8 +603,8 @@ # Identity of this package. PACKAGE_NAME='streamly-core' PACKAGE_TARNAME='streamly-core'-PACKAGE_VERSION='0.1.0'-PACKAGE_STRING='streamly-core 0.1.0'+PACKAGE_VERSION='0.3.1'+PACKAGE_STRING='streamly-core 0.3.1' PACKAGE_BUGREPORT='streamly@composewell.com' PACKAGE_URL='https://streamly.composewell.com' @@ -816,7 +809,7 @@ ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&- as_fn_error $? "invalid feature name: \`$ac_useropt'"+ as_fn_error $? "invalid feature name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in@@ -842,7 +835,7 @@ ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&- as_fn_error $? "invalid feature name: \`$ac_useropt'"+ as_fn_error $? "invalid feature name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in@@ -1055,7 +1048,7 @@ ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&- as_fn_error $? "invalid package name: \`$ac_useropt'"+ as_fn_error $? "invalid package name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in@@ -1071,7 +1064,7 @@ ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&- as_fn_error $? "invalid package name: \`$ac_useropt'"+ as_fn_error $? "invalid package name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in@@ -1101,8 +1094,8 @@ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; - -*) as_fn_error $? "unrecognized option: \`$ac_option'-Try \`$0 --help' for more information"+ -*) as_fn_error $? "unrecognized option: '$ac_option'+Try '$0 --help' for more information" ;; *=*)@@ -1110,7 +1103,7 @@ # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* )- as_fn_error $? "invalid variable name: \`$ac_envvar'" ;;+ as_fn_error $? "invalid variable name: '$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;;@@ -1160,7 +1153,7 @@ as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done -# There might be people who depend on the old broken behavior: `$host'+# There might be people who depend on the old broken behavior: '$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias@@ -1228,7 +1221,7 @@ test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi-ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work"+ac_msg="sources are in $srcdir, but 'cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)`@@ -1256,7 +1249,7 @@ # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF-\`configure' configures streamly-core 0.1.0 to adapt to many kinds of systems.+'configure' configures streamly-core 0.3.1 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1270,11 +1263,11 @@ --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit- -q, --quiet, --silent do not print \`checking ...' messages+ -q, --quiet, --silent do not print 'checking ...' messages --cache-file=FILE cache test results in FILE [disabled]- -C, --config-cache alias for \`--cache-file=config.cache'+ -C, --config-cache alias for '--cache-file=config.cache' -n, --no-create do not create output files- --srcdir=DIR find the sources in DIR [configure dir or \`..']+ --srcdir=DIR find the sources in DIR [configure dir or '..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX@@ -1282,10 +1275,10 @@ --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] -By default, \`make install' will install all the files in-\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify-an installation prefix other than \`$ac_default_prefix' using \`--prefix',-for instance \`--prefix=\$HOME'.+By default, 'make install' will install all the files in+'$ac_default_prefix/bin', '$ac_default_prefix/lib' etc. You can specify+an installation prefix other than '$ac_default_prefix' using '--prefix',+for instance '--prefix=\$HOME'. For better control, use the options below. @@ -1318,7 +1311,7 @@ if test -n "$ac_init_help"; then case $ac_init_help in- short | recursive ) echo "Configuration of streamly-core 0.1.0:";;+ short | recursive ) echo "Configuration of streamly-core 0.3.1:";; esac cat <<\_ACEOF @@ -1336,7 +1329,7 @@ CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if you have headers in a nonstandard directory <include dir> -Use these variables to override the choices made by `configure' or to help+Use these variables to override the choices made by 'configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to <streamly@composewell.com>.@@ -1404,10 +1397,10 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF-streamly-core configure 0.1.0-generated by GNU Autoconf 2.71+streamly-core configure 0.3.1+generated by GNU Autoconf 2.72 -Copyright (C) 2021 Free Software Foundation, Inc.+Copyright (C) 2023 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF@@ -1446,11 +1439,12 @@ } && test -s conftest.$ac_objext then : ac_retval=0-else $as_nop- printf "%s\n" "$as_me: failed program was:" >&5+else case e in #(+ e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 - ac_retval=1+ ac_retval=1 ;;+esac fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval@@ -1469,8 +1463,8 @@ if eval test \${$3+y} then : printf %s "(cached) " >&6-else $as_nop- cat confdefs.h - <<_ACEOF >conftest.$ac_ext+else case e in #(+ e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2>@@ -1478,10 +1472,12 @@ if ac_fn_c_try_compile "$LINENO" then : eval "$3=yes"-else $as_nop- eval "$3=no"+else case e in #(+ e) eval "$3=no" ;;+esac fi-rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;;+esac fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5@@ -1521,11 +1517,12 @@ } then : ac_retval=0-else $as_nop- printf "%s\n" "$as_me: failed program was:" >&5+else case e in #(+ e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 - ac_retval=1+ ac_retval=1 ;;+esac fi # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would@@ -1548,15 +1545,15 @@ if eval test \${$3+y} then : printf %s "(cached) " >&6-else $as_nop- cat confdefs.h - <<_ACEOF >conftest.$ac_ext+else case e in #(+ e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Define $2 to an innocuous variant, in case <limits.h> declares $2. For example, HP-UX 11i <limits.h> declares gettimeofday. */ #define $2 innocuous_$2 /* System header to define __stub macros and hopefully few prototypes,- which can conflict with char $2 (); below. */+ which can conflict with char $2 (void); below. */ #include <limits.h> #undef $2@@ -1567,7 +1564,7 @@ #ifdef __cplusplus extern "C" #endif-char $2 ();+char $2 (void); /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */@@ -1586,11 +1583,13 @@ if ac_fn_c_try_link "$LINENO" then : eval "$3=yes"-else $as_nop- eval "$3=no"+else case e in #(+ e) eval "$3=no" ;;+esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \- conftest$ac_exeext conftest.$ac_ext+ conftest$ac_exeext conftest.$ac_ext ;;+esac fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5@@ -1622,8 +1621,8 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by streamly-core $as_me 0.1.0, which was-generated by GNU Autoconf 2.71. Invocation command line was+It was created by streamly-core $as_me 0.3.1, which was+generated by GNU Autoconf 2.72. Invocation command line was $ $0$ac_configure_args_raw @@ -1869,10 +1868,10 @@ printf "%s\n" "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \- || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5-printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}+ || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5+printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file-See \`config.log' for more details" "$LINENO" 5; }+See 'config.log' for more details" "$LINENO" 5; } fi done @@ -1908,9 +1907,7 @@ /* Most of the following tests are stolen from RCS 5.7 src/conf.sh. */ struct buf { int x; }; struct buf * (*rcsopen) (struct buf *, struct stat *, int);-static char *e (p, i)- char **p;- int i;+static char *e (char **p, int i) { return p[i]; }@@ -1924,6 +1921,21 @@ return s; } +/* C89 style stringification. */+#define noexpand_stringify(a) #a+const char *stringified = noexpand_stringify(arbitrary+token=sequence);++/* C89 style token pasting. Exercises some of the corner cases that+ e.g. old MSVC gets wrong, but not very hard. */+#define noexpand_concat(a,b) a##b+#define expand_concat(a,b) noexpand_concat(a,b)+extern int vA;+extern int vbee;+#define aye A+#define bee B+int *pvA = &expand_concat(v,aye);+int *pvbee = &noexpand_concat(v,bee);+ /* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has function prototypes and stuff, but not \xHH hex character constants. These do not provoke an error unfortunately, instead are silently treated@@ -1951,16 +1963,19 @@ # Test code for whether the C compiler supports C99 (global declarations) ac_c_conftest_c99_globals='-// Does the compiler advertise C99 conformance?+/* Does the compiler advertise C99 conformance? */ #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L # error "Compiler does not advertise C99 conformance" #endif +// See if C++-style comments work.+ #include <stdbool.h> extern int puts (const char *); extern int printf (const char *, ...); extern int dprintf (int, const char *, ...); extern void *malloc (size_t);+extern void free (void *); // Check varargs macros. These examples are taken from C99 6.10.3.5. // dprintf is used instead of fprintf to avoid needing to declare@@ -2010,7 +2025,6 @@ static inline int test_restrict (ccp restrict text) {- // See if C++-style comments work. // Iterate through items via the restricted pointer. // Also check for declarations in for loops. for (unsigned int i = 0; *(text+i) != '\''\0'\''; ++i)@@ -2076,6 +2090,8 @@ ia->datasize = 10; for (int i = 0; i < ia->datasize; ++i) ia->data[i] = i * 1.234;+ // Work around memory leak warnings.+ free (ia); // Check named initializers. struct named_init ni = {@@ -2097,7 +2113,7 @@ # Test code for whether the C compiler supports C11 (global declarations) ac_c_conftest_c11_globals='-// Does the compiler advertise C11 conformance?+/* Does the compiler advertise C11 conformance? */ #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112L # error "Compiler does not advertise C11 conformance" #endif@@ -2220,12 +2236,12 @@ eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,)- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5-printf "%s\n" "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&5+printf "%s\n" "$as_me: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set)- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5-printf "%s\n" "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was not set in the previous run" >&5+printf "%s\n" "$as_me: error: '$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *)@@ -2234,18 +2250,18 @@ ac_old_val_w=`echo x $ac_old_val` ac_new_val_w=`echo x $ac_new_val` if test "$ac_old_val_w" != "$ac_new_val_w"; then- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5-printf "%s\n" "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' has changed since the previous run:" >&5+printf "%s\n" "$as_me: error: '$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5-printf "%s\n" "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;}+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&5+printf "%s\n" "$as_me: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5-printf "%s\n" "$as_me: former value: \`$ac_old_val'" >&2;}- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5-printf "%s\n" "$as_me: current value: \`$ac_new_val'" >&2;}+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: '$ac_old_val'" >&5+printf "%s\n" "$as_me: former value: '$ac_old_val'" >&2;}+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: '$ac_new_val'" >&5+printf "%s\n" "$as_me: current value: '$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status.@@ -2261,11 +2277,11 @@ fi done if $ac_cache_corrupted; then- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5-printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5+printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 printf "%s\n" "$as_me: error: changes in the environment can compromise the build" >&2;}- as_fn_error $? "run \`${MAKE-make} distclean' and/or \`rm $cache_file'+ as_fn_error $? "run '${MAKE-make} distclean' and/or 'rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ##@@ -2312,8 +2328,8 @@ if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6-else $as_nop- if test -n "$CC"; then+else case e in #(+ e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR@@ -2335,7 +2351,8 @@ done IFS=$as_save_IFS -fi+fi ;;+esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then@@ -2357,8 +2374,8 @@ if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6-else $as_nop- if test -n "$ac_ct_CC"; then+else case e in #(+ e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR@@ -2380,7 +2397,8 @@ done IFS=$as_save_IFS -fi+fi ;;+esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then@@ -2415,8 +2433,8 @@ if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6-else $as_nop- if test -n "$CC"; then+else case e in #(+ e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR@@ -2438,7 +2456,8 @@ done IFS=$as_save_IFS -fi+fi ;;+esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then@@ -2460,8 +2479,8 @@ if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6-else $as_nop- if test -n "$CC"; then+else case e in #(+ e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else ac_prog_rejected=no@@ -2500,7 +2519,8 @@ ac_cv_prog_CC="$as_dir$ac_word${1+' '}$@" fi fi-fi+fi ;;+esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then@@ -2524,8 +2544,8 @@ if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6-else $as_nop- if test -n "$CC"; then+else case e in #(+ e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR@@ -2547,7 +2567,8 @@ done IFS=$as_save_IFS -fi+fi ;;+esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then@@ -2573,8 +2594,8 @@ if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6-else $as_nop- if test -n "$ac_ct_CC"; then+else case e in #(+ e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR@@ -2596,7 +2617,8 @@ done IFS=$as_save_IFS -fi+fi ;;+esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then@@ -2634,8 +2656,8 @@ if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6-else $as_nop- if test -n "$CC"; then+else case e in #(+ e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR@@ -2657,7 +2679,8 @@ done IFS=$as_save_IFS -fi+fi ;;+esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then@@ -2679,8 +2702,8 @@ if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6-else $as_nop- if test -n "$ac_ct_CC"; then+else case e in #(+ e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR@@ -2702,7 +2725,8 @@ done IFS=$as_save_IFS -fi+fi ;;+esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then@@ -2731,10 +2755,10 @@ fi -test -z "$CC" && { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5-printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}+test -z "$CC" && { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5+printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH-See \`config.log' for more details" "$LINENO" 5; }+See 'config.log' for more details" "$LINENO" 5; } # Provide some information about the compiler. printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5@@ -2806,8 +2830,8 @@ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then :- # Autoconf-2.13 could set the ac_cv_exeext variable to `no'.-# So ignore a value of `no', otherwise this would lead to `EXEEXT = no'+ # Autoconf-2.13 could set the ac_cv_exeext variable to 'no'.+# So ignore a value of 'no', otherwise this would lead to 'EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf.@@ -2827,7 +2851,7 @@ ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not- # safe: cross compilers may not add the suffix if given an `-o'+ # safe: cross compilers may not add the suffix if given an '-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working.@@ -2838,8 +2862,9 @@ done test "$ac_cv_exeext" = no && ac_cv_exeext= -else $as_nop- ac_file=''+else case e in #(+ e) ac_file='' ;;+esac fi if test -z "$ac_file" then :@@ -2848,13 +2873,14 @@ printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 -{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5-printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}+{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5+printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "C compiler cannot create executables-See \`config.log' for more details" "$LINENO" 5; }-else $as_nop- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5-printf "%s\n" "yes" >&6; }+See 'config.log' for more details" "$LINENO" 5; }+else case e in #(+ e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5+printf "%s\n" "yes" >&6; } ;;+esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 printf %s "checking for C compiler default output file name... " >&6; }@@ -2878,10 +2904,10 @@ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then :- # If both `conftest.exe' and `conftest' are `present' (well, observable)-# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will-# work properly (i.e., refer to `conftest.exe'), while it won't with-# `rm'.+ # If both 'conftest.exe' and 'conftest' are 'present' (well, observable)+# catch 'conftest.exe'. For instance with Cygwin, 'ls conftest' will+# work properly (i.e., refer to 'conftest.exe'), while it won't with+# 'rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in@@ -2891,11 +2917,12 @@ * ) break;; esac done-else $as_nop- { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5-printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}+else case e in #(+ e) { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5+printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link-See \`config.log' for more details" "$LINENO" 5; }+See 'config.log' for more details" "$LINENO" 5; } ;;+esac fi rm -f conftest conftest$ac_cv_exeext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5@@ -2911,6 +2938,8 @@ main (void) { FILE *f = fopen ("conftest.out", "w");+ if (!f)+ return 1; return ferror (f) || fclose (f) != 0; ;@@ -2950,26 +2979,27 @@ if test "$cross_compiling" = maybe; then cross_compiling=yes else- { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5-printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}+ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5+printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "cannot run C compiled programs.-If you meant to cross compile, use \`--host'.-See \`config.log' for more details" "$LINENO" 5; }+If you meant to cross compile, use '--host'.+See 'config.log' for more details" "$LINENO" 5; } fi fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 printf "%s\n" "$cross_compiling" >&6; } -rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out+rm -f conftest.$ac_ext conftest$ac_cv_exeext \+ conftest.o conftest.obj conftest.out ac_clean_files=$ac_clean_files_save { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 printf %s "checking for suffix of object files... " >&6; } if test ${ac_cv_objext+y} then : printf %s "(cached) " >&6-else $as_nop- cat confdefs.h - <<_ACEOF >conftest.$ac_ext+else case e in #(+ e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int@@ -3001,16 +3031,18 @@ break;; esac done-else $as_nop- printf "%s\n" "$as_me: failed program was:" >&5+else case e in #(+ e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 -{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5-printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}+{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5+printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile-See \`config.log' for more details" "$LINENO" 5; }+See 'config.log' for more details" "$LINENO" 5; } ;;+esac fi-rm -f conftest.$ac_cv_objext conftest.$ac_ext+rm -f conftest.$ac_cv_objext conftest.$ac_ext ;;+esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 printf "%s\n" "$ac_cv_objext" >&6; }@@ -3021,8 +3053,8 @@ if test ${ac_cv_c_compiler_gnu+y} then : printf %s "(cached) " >&6-else $as_nop- cat confdefs.h - <<_ACEOF >conftest.$ac_ext+else case e in #(+ e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int@@ -3039,12 +3071,14 @@ if ac_fn_c_try_compile "$LINENO" then : ac_compiler_gnu=yes-else $as_nop- ac_compiler_gnu=no+else case e in #(+ e) ac_compiler_gnu=no ;;+esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu-+ ;;+esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 printf "%s\n" "$ac_cv_c_compiler_gnu" >&6; }@@ -3062,8 +3096,8 @@ if test ${ac_cv_prog_cc_g+y} then : printf %s "(cached) " >&6-else $as_nop- ac_save_c_werror_flag=$ac_c_werror_flag+else case e in #(+ e) ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes ac_cv_prog_cc_g=no CFLAGS="-g"@@ -3081,8 +3115,8 @@ if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes-else $as_nop- CFLAGS=""+else case e in #(+ e) CFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -3097,8 +3131,8 @@ if ac_fn_c_try_compile "$LINENO" then : -else $as_nop- ac_c_werror_flag=$ac_save_c_werror_flag+else case e in #(+ e) ac_c_werror_flag=$ac_save_c_werror_flag CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */@@ -3115,12 +3149,15 @@ then : ac_cv_prog_cc_g=yes fi-rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;;+esac fi-rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;;+esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext- ac_c_werror_flag=$ac_save_c_werror_flag+ ac_c_werror_flag=$ac_save_c_werror_flag ;;+esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 printf "%s\n" "$ac_cv_prog_cc_g" >&6; }@@ -3147,8 +3184,8 @@ if test ${ac_cv_prog_cc_c11+y} then : printf %s "(cached) " >&6-else $as_nop- ac_cv_prog_cc_c11=no+else case e in #(+ e) ac_cv_prog_cc_c11=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */@@ -3165,25 +3202,28 @@ test "x$ac_cv_prog_cc_c11" != "xno" && break done rm -f conftest.$ac_ext-CC=$ac_save_CC+CC=$ac_save_CC ;;+esac fi if test "x$ac_cv_prog_cc_c11" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; }-else $as_nop- if test "x$ac_cv_prog_cc_c11" = x+else case e in #(+ e) if test "x$ac_cv_prog_cc_c11" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; }-else $as_nop- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5+else case e in #(+ e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5 printf "%s\n" "$ac_cv_prog_cc_c11" >&6; }- CC="$CC $ac_cv_prog_cc_c11"+ CC="$CC $ac_cv_prog_cc_c11" ;;+esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11- ac_prog_cc_stdc=c11+ ac_prog_cc_stdc=c11 ;;+esac fi fi if test x$ac_prog_cc_stdc = xno@@ -3193,8 +3233,8 @@ if test ${ac_cv_prog_cc_c99+y} then : printf %s "(cached) " >&6-else $as_nop- ac_cv_prog_cc_c99=no+else case e in #(+ e) ac_cv_prog_cc_c99=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */@@ -3211,25 +3251,28 @@ test "x$ac_cv_prog_cc_c99" != "xno" && break done rm -f conftest.$ac_ext-CC=$ac_save_CC+CC=$ac_save_CC ;;+esac fi if test "x$ac_cv_prog_cc_c99" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; }-else $as_nop- if test "x$ac_cv_prog_cc_c99" = x+else case e in #(+ e) if test "x$ac_cv_prog_cc_c99" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; }-else $as_nop- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5+else case e in #(+ e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 printf "%s\n" "$ac_cv_prog_cc_c99" >&6; }- CC="$CC $ac_cv_prog_cc_c99"+ CC="$CC $ac_cv_prog_cc_c99" ;;+esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99- ac_prog_cc_stdc=c99+ ac_prog_cc_stdc=c99 ;;+esac fi fi if test x$ac_prog_cc_stdc = xno@@ -3239,8 +3282,8 @@ if test ${ac_cv_prog_cc_c89+y} then : printf %s "(cached) " >&6-else $as_nop- ac_cv_prog_cc_c89=no+else case e in #(+ e) ac_cv_prog_cc_c89=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */@@ -3257,25 +3300,28 @@ test "x$ac_cv_prog_cc_c89" != "xno" && break done rm -f conftest.$ac_ext-CC=$ac_save_CC+CC=$ac_save_CC ;;+esac fi if test "x$ac_cv_prog_cc_c89" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; }-else $as_nop- if test "x$ac_cv_prog_cc_c89" = x+else case e in #(+ e) if test "x$ac_cv_prog_cc_c89" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; }-else $as_nop- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5+else case e in #(+ e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 printf "%s\n" "$ac_cv_prog_cc_c89" >&6; }- CC="$CC $ac_cv_prog_cc_c89"+ CC="$CC $ac_cv_prog_cc_c89" ;;+esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89- ac_prog_cc_stdc=c89+ ac_prog_cc_stdc=c89 ;;+esac fi fi @@ -3343,8 +3389,8 @@ # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. #-# `ac_cv_env_foo' variables (set or unset) will be overridden when-# loading this file, other *unset* `ac_cv_foo' will be assigned the+# 'ac_cv_env_foo' variables (set or unset) will be overridden when+# loading this file, other *unset* 'ac_cv_foo' will be assigned the # following values. _ACEOF@@ -3374,14 +3420,14 @@ (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *)- # `set' does not quote correctly, so add quotes: double-quote+ # 'set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *)- # `set' quotes correctly as required by POSIX, so do not add quotes.+ # 'set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac |@@ -3471,7 +3517,6 @@ # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh-as_nop=: if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh@@ -3480,12 +3525,13 @@ # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST-else $as_nop- case `(set -o) 2>/dev/null` in #(+else case e in #(+ e) case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;;+esac ;; esac fi @@ -3557,7 +3603,7 @@ ;; esac-# We did not find ourselves, most probably we were run as `sh COMMAND'+# We did not find ourselves, most probably we were run as 'sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0@@ -3586,7 +3632,6 @@ } # as_fn_error - # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking.@@ -3626,11 +3671,12 @@ { eval $1+=\$2 }'-else $as_nop- as_fn_append ()+else case e in #(+ e) as_fn_append () { eval $1=\$$1\$2- }+ } ;;+esac fi # as_fn_append # as_fn_arith ARG...@@ -3644,11 +3690,12 @@ { as_val=$(( $* )) }'-else $as_nop- as_fn_arith ()+else case e in #(+ e) as_fn_arith () { as_val=`expr "$@" || test $? -eq 1`- }+ } ;;+esac fi # as_fn_arith @@ -3731,9 +3778,9 @@ if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas:- # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.- # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.- # In both cases, we have to default to `cp -pR'.+ # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail.+ # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable.+ # In both cases, we have to default to 'cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then@@ -3814,10 +3861,12 @@ as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name.-as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"+as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g"+as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated # Sed expression to map a string onto a valid variable name.-as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"+as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g"+as_tr_sh="eval sed '$as_sed_sh'" # deprecated exec 6>&1@@ -3832,8 +3881,8 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log="-This file was extended by streamly-core $as_me 0.1.0, which was-generated by GNU Autoconf 2.71. Invocation command line was+This file was extended by streamly-core $as_me 0.3.1, which was+generated by GNU Autoconf 2.72. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS@@ -3860,7 +3909,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\-\`$as_me' instantiates files and other configuration actions+'$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. @@ -3888,11 +3937,11 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\-streamly-core config.status 0.1.0-configured by $0, generated by GNU Autoconf 2.71,+streamly-core config.status 0.3.1+configured by $0, generated by GNU Autoconf 2.72, with options \\"\$ac_cs_config\\" -Copyright (C) 2021 Free Software Foundation, Inc.+Copyright (C) 2023 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." @@ -3943,8 +3992,8 @@ ac_need_defaults=false;; --he | --h) # Conflict between --help and --header- as_fn_error $? "ambiguous option: \`$1'-Try \`$0 --help' for more information.";;+ as_fn_error $? "ambiguous option: '$1'+Try '$0 --help' for more information.";; --help | --hel | -h ) printf "%s\n" "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \@@ -3952,8 +4001,8 @@ ac_cs_silent=: ;; # This is an error.- -*) as_fn_error $? "unrecognized option: \`$1'-Try \`$0 --help' for more information." ;;+ -*) as_fn_error $? "unrecognized option: '$1'+Try '$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;;@@ -4003,7 +4052,7 @@ case $ac_config_target in "src/config.h") CONFIG_HEADERS="$CONFIG_HEADERS src/config.h" ;; - *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;+ *) as_fn_error $? "invalid argument: '$ac_config_target'" "$LINENO" 5;; esac done @@ -4021,7 +4070,7 @@ # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned:-# after its creation but before its name has been assigned to `$tmp'.+# after its creation but before its name has been assigned to '$tmp'. $debug || { tmp= ac_tmp=@@ -4045,13 +4094,13 @@ # Set up the scripts for CONFIG_HEADERS section. # No need to generate them if there are no CONFIG_HEADERS.-# This happens for instance with `./config.status Makefile'.+# This happens for instance with './config.status Makefile'. if test -n "$CONFIG_HEADERS"; then cat >"$ac_tmp/defines.awk" <<\_ACAWK || BEGIN { _ACEOF -# Transform confdefs.h into an awk script `defines.awk', embedded as+# Transform confdefs.h into an awk script 'defines.awk', embedded as # here-document in config.status, that substitutes the proper values into # config.h.in to produce config.h. @@ -4161,7 +4210,7 @@ esac case $ac_mode$ac_tag in :[FHL]*:*);;- :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;;+ :L* | :C*:*) as_fn_error $? "invalid tag '$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac@@ -4183,19 +4232,19 @@ -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style,- # because $ac_f cannot contain `:'.+ # because $ac_f cannot contain ':'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac ||- as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;;+ as_fn_error 1 "cannot find input file: '$ac_f'" "$LINENO" 5;; esac case $ac_f in *\'*) ac_f=`printf "%s\n" "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done - # Let's still pretend it is `configure' which instantiates (i.e., don't+ # Let's still pretend it is 'configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '`
configure.ac view
@@ -3,7 +3,7 @@ # See https://www.gnu.org/software/autoconf/manual/autoconf.html for help on # the macros used in this file. -AC_INIT([streamly-core], [0.1.0], [streamly@composewell.com], [streamly-core], [https://streamly.composewell.com])+AC_INIT([streamly-core], [0.3.1], [streamly@composewell.com], [streamly-core], [https://streamly.composewell.com]) # To suppress "WARNING: unrecognized options: --with-compiler" AC_ARG_WITH([compiler], [GHC])
+ docs/ApiChangelogs/0.1.0-0.2.0.txt view
@@ -0,0 +1,1471 @@+---------------------------------+API Annotations+---------------------------------++[A] : Added+[R] : Removed+[C] : Changed+[O] : Old definition+[N] : New definition+[D] : Deprecated++---------------------------------+API diff+---------------------------------++[C] Streamly.Unicode.Parser+ [A] double :: Monad m => Parser Char m Double+[C] Streamly.FileSystem.Handle+ [A] readWith :: MonadIO m => Int -> Handle -> Stream m Word8+ [A] readChunksWith :: MonadIO m => Int -> Handle -> Stream m (Array Word8)+[C] Streamly.FileSystem.Dir+ [C] readEither+ [O] readEither :: MonadIO m => FilePath -> Stream m (Either FilePath FilePath)+ [N] readEither :: (MonadIO m, MonadCatch m) => FilePath -> Stream m (Either FilePath FilePath)+ [C] read+ [O] read :: MonadIO m => FilePath -> Stream m FilePath+ [N] read :: (MonadIO m, MonadCatch m) => FilePath -> Stream m FilePath+[C] Streamly.Data.Unfold+ [A] class Enum a => Enumerable a+ [A] second :: b -> Unfold m (a, b) c -> Unfold m a c+ [A] first :: a -> Unfold m (a, b) c -> Unfold m b c+ [A] enumerateFromTo :: (Enumerable a, Monad m) => Unfold m (a, a) a+ [A] enumerateFromThenTo :: (Enumerable a, Monad m) => Unfold m (a, a, a) a+ [A] enumerateFromThen :: (Enumerable a, Monad m) => Unfold m (a, a) a+ [A] enumerateFrom :: (Enumerable a, Monad m) => Unfold m a a+[C] Streamly.Data.StreamK+ [C] parseChunks+ [O] parseChunks :: (Monad m, Unbox a) => ParserK a m b -> StreamK m (Array a) -> m (Either ParseError b)+ [N] parseChunks :: (Monad m, Unbox a) => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b)+ [C] parseBreakChunks+ [O] parseBreakChunks :: (Monad m, Unbox a) => ParserK a m b -> StreamK m (Array a) -> m (Either ParseError b, StreamK m (Array a))+ [N] parseBreakChunks :: (Monad m, Unbox a) => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b, StreamK m (Array a))+ [A] parseBreak :: forall m a b. Monad m => ParserK a m b -> StreamK m a -> m (Either ParseError b, StreamK m a)+ [A] parse :: Monad m => ParserK a m b -> StreamK m a -> m (Either ParseError b)+ [A] handle :: (MonadCatch m, Exception e) => (e -> m (StreamK m a)) -> StreamK m a -> StreamK m a+ [A] bracketIO :: (MonadIO m, MonadCatch m) => IO b -> (b -> IO c) -> (b -> StreamK m a) -> StreamK m a+[C] Streamly.Data.Stream+ [A] wordsBy :: Monad m => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+ [A] splitOn :: Monad m => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+ [C] handle+ [O] handle :: (MonadCatch m, Exception e) => (e -> Stream m a) -> Stream m a -> Stream m a+ [N] handle :: (MonadCatch m, Exception e) => (e -> m (Stream m a)) -> Stream m a -> Stream m a+ [A] groupsOf :: Monad m => Int -> Fold m a b -> Stream m a -> Stream m b+[C] Streamly.Data.ParserK+ [D] fromParser :: (MonadIO m, Unbox a) => Parser a m b -> ParserK (Array a) m b+ [D] fromFold :: (MonadIO m, Unbox a) => Fold m a b -> ParserK (Array a) m b+ [A] adaptCG :: Monad m => Parser a m b -> ParserK (Array a) m b+ [A] adaptC :: (Monad m, Unbox a) => Parser a m b -> ParserK (Array a) m b+ [A] adapt :: Monad m => Parser a m b -> ParserK a m b+[C] Streamly.Data.Parser+ [A] groupByRollingEither :: Monad m => (a -> a -> Bool) -> Fold m a b -> Fold m a c -> Parser a m (Either b c)+ [A] groupByRolling :: Monad m => (a -> a -> Bool) -> Fold m a b -> Parser a m b+[A] Streamly.Data.MutByteArray+ [A] class Unbox a+ [A] class Serialize a+ [A] SerializeConfig+ [A] MutByteArray+ [A] unpin :: MutByteArray -> IO MutByteArray+ [A] sizeOf :: (Unbox a, SizeOfRep (Rep a)) => Proxy a -> Int+ [A] serializeAt :: Serialize a => Int -> MutByteArray -> a -> IO Int+ [A] pokeAt :: (Unbox a, Generic a, PokeRep (Rep a)) => Int -> MutByteArray -> a -> IO ()+ [A] pinnedNew :: Int -> IO MutByteArray+ [A] pin :: MutByteArray -> IO MutByteArray+ [A] peekAt :: (Unbox a, Generic a, PeekRep (Rep a)) => Int -> MutByteArray -> IO a+ [A] new :: Int -> IO MutByteArray+ [A] isPinned :: MutByteArray -> Bool+ [A] inlineSerializeAt :: Maybe Inline -> SerializeConfig -> SerializeConfig+ [A] inlineDeserializeAt :: Maybe Inline -> SerializeConfig -> SerializeConfig+ [A] inlineAddSizeTo :: Maybe Inline -> SerializeConfig -> SerializeConfig+ [A] deserializeAt :: Serialize a => Int -> MutByteArray -> Int -> IO (Int, a)+ [A] deriveUnbox :: Q [Dec] -> Q [Dec]+ [A] deriveSerializeWith :: (SerializeConfig -> SerializeConfig) -> Q [Dec] -> Q [Dec]+ [A] deriveSerialize :: Q [Dec] -> Q [Dec]+ [A] addSizeTo :: Serialize a => Int -> a -> Int+[C] Streamly.Data.MutArray.Generic+ [A] write :: MonadIO m => Fold m a (MutArray a)+ [A] readRev :: MonadIO m => MutArray a -> Stream m a+ [A] read :: MonadIO m => MutArray a -> Stream m a+ [A] putIndexUnsafe :: forall m a. MonadIO m => Int -> MutArray a -> a -> m ()+ [A] modifyIndexUnsafe :: MonadIO m => Int -> MutArray a -> (a -> (a, b)) -> m b+ [A] length :: MutArray a -> Int+ [A] getIndexUnsafe :: MonadIO m => Int -> MutArray a -> m a+ [C] getIndex+ [O] getIndex :: MonadIO m => Int -> MutArray a -> m a+ [N] getIndex :: MonadIO m => Int -> MutArray a -> m (Maybe a)+ [A] fromListN :: MonadIO m => Int -> [a] -> m (MutArray a)+ [A] fromList :: MonadIO m => [a] -> m (MutArray a)+[C] Streamly.Data.MutArray+ [A] unpin :: MutArray a -> IO (MutArray a)+ [A] readRev :: forall m a. (MonadIO m, Unbox a) => MutArray a -> Stream m a+ [A] read :: forall m a. (MonadIO m, Unbox a) => MutArray a -> Stream m a+ [A] putIndexUnsafe :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> a -> m ()+ [D] pokeByteIndex :: Unbox a => Int -> MutByteArray -> a -> IO ()+ [A] pokeAt :: (Unbox a, Generic a, PokeRep (Rep a)) => Int -> MutByteArray -> a -> IO ()+ [A] pinnedNew :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a)+ [A] pin :: MutArray a -> IO (MutArray a)+ [D] peekByteIndex :: Unbox a => Int -> MutByteArray -> IO a+ [A] peekAt :: (Unbox a, Generic a, PeekRep (Rep a)) => Int -> MutByteArray -> IO a+ [D] newPinned :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a)+ [A] modifyIndexUnsafe :: forall m a b. (MonadIO m, Unbox a) => Int -> MutArray a -> (a -> (a, b)) -> m b+ [A] modifyIndex :: forall m a b. (MonadIO m, Unbox a) => Int -> MutArray a -> (a -> (a, b)) -> m b+ [A] modify :: forall m a. (MonadIO m, Unbox a) => MutArray a -> (a -> a) -> m ()+ [A] isPinned :: MutArray a -> Bool+ [A] getIndexUnsafe :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m a+ [C] getIndex+ [O] getIndex :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m a+ [N] getIndex :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m (Maybe a)+[C] Streamly.Data.Array.Generic+ [A] toList :: Array a -> [a]+ [A] getIndex :: Int -> Array a -> Maybe a+[C] Streamly.Data.Array+ [A] unpin :: Array a -> IO (Array a)+ [D] pokeByteIndex :: Unbox a => Int -> MutByteArray -> a -> IO ()+ [A] pokeAt :: (Unbox a, Generic a, PokeRep (Rep a)) => Int -> MutByteArray -> a -> IO ()+ [A] pin :: Array a -> IO (Array a)+ [D] peekByteIndex :: Unbox a => Int -> MutByteArray -> IO a+ [A] peekAt :: (Unbox a, Generic a, PeekRep (Rep a)) => Int -> MutByteArray -> IO a+ [A] isPinned :: Array a -> Bool+[C] Streamly.Console.Stdio++---------------------------------+Internal API diff+---------------------------------++[C] Streamly.Internal.Unicode.Stream+ [C] writeCharUtf8'+ [O] writeCharUtf8' :: Monad m => Fold m Word8 Char+ [N] writeCharUtf8' :: Monad m => Parser Word8 m Char+[C] Streamly.Internal.Unicode.Parser+ [A] number :: Monad m => Parser Char m (Integer, Int)+ [A] mkDouble :: Integer -> Int -> Double+ [A] doubleParser :: Monad m => Parser Char m (Int, Int)+ [C] double+ [O] double :: Parser Char m Double+ [N] double :: Monad m => Parser Char m Double+[R] Streamly.Internal.Serialize.ToBytes+[R] Streamly.Internal.Serialize.FromBytes+[C] Streamly.Internal.FileSystem.File+ [A] writeAppendWith :: (MonadIO m, MonadCatch m) => Int -> FilePath -> Stream m Word8 -> m ()+ [A] writeAppendChunks :: (MonadIO m, MonadCatch m) => FilePath -> Stream m (Array a) -> m ()+ [A] writeAppendArray :: FilePath -> Array a -> IO ()+ [A] writeAppend :: (MonadIO m, MonadCatch m) => FilePath -> Stream m Word8 -> m ()+ [R] appendWith :: (MonadIO m, MonadCatch m) => Int -> FilePath -> Stream m Word8 -> m ()+ [R] appendChunks :: (MonadIO m, MonadCatch m) => FilePath -> Stream m (Array a) -> m ()+ [R] appendArray :: FilePath -> Array a -> IO ()+ [R] append :: (MonadIO m, MonadCatch m) => FilePath -> Stream m Word8 -> m ()+[C] Streamly.Internal.FileSystem.Dir+ [C] reader+ [O] reader :: MonadIO m => Unfold m FilePath FilePath+ [N] reader :: (MonadIO m, MonadCatch m) => Unfold m FilePath FilePath+ [C] readFiles+ [O] readFiles :: MonadIO m => FilePath -> Stream m FilePath+ [N] readFiles :: (MonadIO m, MonadCatch m) => FilePath -> Stream m FilePath+ [C] readEitherPaths+ [O] readEitherPaths :: MonadIO m => FilePath -> Stream m (Either FilePath FilePath)+ [N] readEitherPaths :: (MonadIO m, MonadCatch m) => FilePath -> Stream m (Either FilePath FilePath)+ [C] readEither+ [O] readEither :: MonadIO m => FilePath -> Stream m (Either FilePath FilePath)+ [N] readEither :: (MonadIO m, MonadCatch m) => FilePath -> Stream m (Either FilePath FilePath)+ [C] readDirs+ [O] readDirs :: MonadIO m => FilePath -> Stream m FilePath+ [N] readDirs :: (MonadIO m, MonadCatch m) => FilePath -> Stream m FilePath+ [C] read+ [O] read :: MonadIO m => FilePath -> Stream m FilePath+ [N] read :: (MonadIO m, MonadCatch m) => FilePath -> Stream m FilePath+ [C] fileReader+ [O] fileReader :: MonadIO m => Unfold m FilePath FilePath+ [N] fileReader :: (MonadIO m, MonadCatch m) => Unfold m FilePath FilePath+ [C] eitherReaderPaths+ [O] eitherReaderPaths :: MonadIO m => Unfold m FilePath (Either FilePath FilePath)+ [N] eitherReaderPaths :: (MonadIO m, MonadCatch m) => Unfold m FilePath (Either FilePath FilePath)+ [C] eitherReader+ [O] eitherReader :: MonadIO m => Unfold m FilePath (Either FilePath FilePath)+ [N] eitherReader :: (MonadIO m, MonadCatch m) => Unfold m FilePath (Either FilePath FilePath)+ [C] dirReader+ [O] dirReader :: MonadIO m => Unfold m FilePath FilePath+ [N] dirReader :: (MonadIO m, MonadCatch m) => Unfold m FilePath FilePath+[R] Streamly.Internal.Data.Unfold.Type+[R] Streamly.Internal.Data.Unfold.Enumeration+[C] Streamly.Internal.Data.Unfold+ [C] Unfold+ [A] Unfold :: (s -> m (Step s b)) -> (a -> m s) -> Unfold m a b+ [A] takeWhileMWithInput :: Monad m => (a -> b -> m Bool) -> Unfold m a b -> Unfold m a b+ [A] manyInterleave :: Monad m => Unfold m a b -> Unfold m c a -> Unfold m c b+ [A] enumerateFromStepIntegral :: (Monad m, Integral a) => Unfold m (a, a) a+ [A] crossApplySnd :: Unfold m a b -> Unfold m a c -> Unfold m a c+ [A] crossApplyFst :: Unfold m a b -> Unfold m a c -> Unfold m a b+ [A] concatMap :: Monad m => (b -> Unfold m a c) -> Unfold m a b -> Unfold m a c+[R] Streamly.Internal.Data.Unboxed+[C] Streamly.Internal.Data.Time.Units+ [R] Streamly.Internal.Data.Unboxed.Unbox+ [A] Streamly.Internal.Data.Unbox.Unbox+ [A] instance Streamly.Internal.Data.Unbox.Unbox Streamly.Internal.Data.Time.Units.NanoSecond64+ [A] instance Streamly.Internal.Data.Unbox.Unbox Streamly.Internal.Data.Time.Units.MilliSecond64+ [A] instance Streamly.Internal.Data.Unbox.Unbox Streamly.Internal.Data.Time.Units.MicroSecond64+[R] Streamly.Internal.Data.Time.Clock.Type+[A] Streamly.Internal.Data.StreamK+ [A] CrossStreamK+ [A] (FixityR,6)+ [A] (FixityR,6)+ [A] (FixityR,6)+ [A] (FixityR,5)+ [A] (FixityR,5)+ [A] (FixityR,6)+ [A] (FixityR,5)+ [A] StreamK+ [A] MkStream :: (forall r. State StreamK m a -> (a -> StreamK m a -> m r) -> (a -> m r) -> m r -> m r) -> StreamK m a+ [A] zipWithM :: Monad m => (a -> b -> m c) -> StreamK m a -> StreamK m b -> StreamK m c+ [A] zipWith :: Monad m => (a -> b -> c) -> StreamK m a -> StreamK m b -> StreamK m c+ [A] unfoldrMWith :: Monad m => (m a -> StreamK m a -> StreamK m a) -> (b -> m (Maybe (a, b))) -> b -> StreamK m a+ [A] unfoldrM :: Monad m => (b -> m (Maybe (a, b))) -> b -> StreamK m a+ [A] unfoldr :: (b -> Maybe (a, b)) -> b -> StreamK m a+ [A] uncons :: Applicative m => StreamK m a -> m (Maybe (a, StreamK m a))+ [A] unShare :: StreamK m a -> StreamK m a+ [A] unCross :: CrossStreamK m a -> StreamK m a+ [A] toStream :: Applicative m => StreamK m a -> Stream m a+ [A] toList :: Monad m => StreamK m a -> m [a]+ [A] the :: (Eq a, Monad m) => StreamK m a -> m (Maybe a)+ [A] takeWhile :: (a -> Bool) -> StreamK m a -> StreamK m a+ [A] take :: Int -> StreamK m a -> StreamK m a+ [A] tail :: Applicative m => StreamK m a -> m (Maybe (StreamK m a))+ [A] sortBy :: Monad m => (a -> a -> Ordering) -> StreamK m a -> StreamK m a+ [A] sequence :: Monad m => StreamK m (m a) -> StreamK m a+ [A] scanlx' :: (x -> a -> x) -> x -> (x -> b) -> StreamK m a -> StreamK m b+ [A] scanl' :: (b -> a -> b) -> b -> StreamK m a -> StreamK m b+ [A] reverse :: StreamK m a -> StreamK m a+ [A] replicateMWith :: (m a -> StreamK m a -> StreamK m a) -> Int -> m a -> StreamK m a+ [A] replicateM :: Monad m => Int -> m a -> StreamK m a+ [A] replicate :: Int -> a -> StreamK m a+ [A] repeatMWith :: (m a -> t m a -> t m a) -> m a -> t m a+ [A] repeatM :: Monad m => m a -> StreamK m a+ [A] repeat :: a -> StreamK m a+ [A] parseDBreak :: Monad m => Parser a m b -> StreamK m a -> m (Either ParseError b, StreamK m a)+ [A] parseD :: Monad m => Parser a m b -> StreamK m a -> m (Either ParseError b)+ [A] parseChunksGeneric :: Monad m => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b)+ [A] parseChunks :: (Monad m, Unbox a) => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b)+ [A] parseBreakChunksGeneric :: forall m a b. Monad m => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b, StreamK m (Array a))+ [A] parseBreakChunks :: (Monad m, Unbox a) => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b, StreamK m (Array a))+ [A] parseBreak :: forall m a b. Monad m => ParserK a m b -> StreamK m a -> m (Either ParseError b, StreamK m a)+ [A] parse :: Monad m => ParserK a m b -> StreamK m a -> m (Either ParseError b)+ [A] null :: Monad m => StreamK m a -> m Bool+ [A] notElem :: (Monad m, Eq a) => a -> StreamK m a -> m Bool+ [A] nilM :: Applicative m => m b -> StreamK m a+ [A] nil :: StreamK m a+ [A] mkStream :: (forall r. State StreamK m a -> (a -> StreamK m a -> m r) -> (a -> m r) -> m r -> m r) -> StreamK m a+ [A] mkCross :: StreamK m a -> CrossStreamK m a+ [A] minimumBy :: Monad m => (a -> a -> Ordering) -> StreamK m a -> m (Maybe a)+ [A] minimum :: (Monad m, Ord a) => StreamK m a -> m (Maybe a)+ [A] mfix :: Monad m => (m a -> StreamK m a) -> StreamK m a+ [A] mergeMapWith :: (StreamK m b -> StreamK m b -> StreamK m b) -> (a -> StreamK m b) -> StreamK m a -> StreamK m b+ [A] mergeIterateWith :: (StreamK m a -> StreamK m a -> StreamK m a) -> (a -> StreamK m a) -> StreamK m a -> StreamK m a+ [A] mergeByM :: Monad m => (a -> a -> m Ordering) -> StreamK m a -> StreamK m a -> StreamK m a+ [A] mergeBy :: (a -> a -> Ordering) -> StreamK m a -> StreamK m a -> StreamK m a+ [A] maximumBy :: Monad m => (a -> a -> Ordering) -> StreamK m a -> m (Maybe a)+ [A] maximum :: (Monad m, Ord a) => StreamK m a -> m (Maybe a)+ [A] mapMaybe :: (a -> Maybe b) -> StreamK m a -> StreamK m b+ [A] mapM_ :: Monad m => (a -> m b) -> StreamK m a -> m ()+ [A] mapMWith :: (m b -> StreamK m b -> StreamK m b) -> (a -> m b) -> StreamK m a -> StreamK m b+ [A] mapMSerial :: Monad m => (a -> m b) -> StreamK m a -> StreamK m b+ [A] mapM :: Monad m => (a -> m b) -> StreamK m a -> StreamK m b+ [A] map :: (a -> b) -> StreamK m a -> StreamK m b+ [A] lookup :: (Monad m, Eq a) => a -> StreamK m (a, b) -> m (Maybe b)+ [A] liftInner :: (Monad m, MonadTrans t, Monad (t m)) => StreamK m a -> StreamK (t m) a+ [A] last :: Monad m => StreamK m a -> m (Maybe a)+ [A] iterateMWith :: Monad m => (m a -> StreamK m a -> StreamK m a) -> (a -> m a) -> m a -> StreamK m a+ [A] iterateM :: Monad m => (a -> m a) -> m a -> StreamK m a+ [A] iterate :: (a -> a) -> a -> StreamK m a+ [A] intersperseM :: Monad m => m a -> StreamK m a -> StreamK m a+ [A] intersperse :: Monad m => a -> StreamK m a -> StreamK m a+ [A] interleaveMin :: StreamK m a -> StreamK m a -> StreamK m a+ [A] interleaveFst :: StreamK m a -> StreamK m a -> StreamK m a+ [A] interleave :: StreamK m a -> StreamK m a -> StreamK m a+ [A] insertBy :: (a -> a -> Ordering) -> a -> StreamK m a -> StreamK m a+ [A] init :: Applicative m => StreamK m a -> m (Maybe (StreamK m a))+ [A] hoist :: (Monad m, Monad n) => (forall x. m x -> n x) -> StreamK m a -> StreamK n a+ [A] head :: Monad m => StreamK m a -> m (Maybe a)+ [A] handle :: (MonadCatch m, Exception e) => (e -> m (StreamK m a)) -> StreamK m a -> StreamK m a+ [A] fromYieldK :: YieldK m a -> StreamK m a+ [A] fromStream :: Monad m => Stream m a -> StreamK m a+ [A] fromStopK :: StopK m -> StreamK m a+ [A] fromPure :: a -> StreamK m a+ [A] fromList :: [a] -> StreamK m a+ [A] fromIndicesMWith :: (m a -> StreamK m a -> StreamK m a) -> (Int -> m a) -> StreamK m a+ [A] fromIndicesM :: Monad m => (Int -> m a) -> StreamK m a+ [A] fromIndices :: (Int -> a) -> StreamK m a+ [A] fromFoldableM :: (Foldable f, Monad m) => f (m a) -> StreamK m a+ [A] fromFoldable :: Foldable f => f a -> StreamK m a+ [A] fromEffect :: Monad m => m a -> StreamK m a+ [A] foldrT :: (Monad m, Monad (s m), MonadTrans s) => (a -> s m b -> s m b) -> s m b -> StreamK m a -> s m b+ [A] foldrSShared :: (a -> StreamK m b -> StreamK m b) -> StreamK m b -> StreamK m a -> StreamK m b+ [A] foldrSM :: Monad m => (m a -> StreamK m b -> StreamK m b) -> StreamK m b -> StreamK m a -> StreamK m b+ [A] foldrS :: (a -> StreamK m b -> StreamK m b) -> StreamK m b -> StreamK m a -> StreamK m b+ [A] foldrM :: (a -> m b -> m b) -> m b -> StreamK m a -> m b+ [A] foldr1 :: Monad m => (a -> a -> a) -> StreamK m a -> m (Maybe a)+ [A] foldr :: Monad m => (a -> b -> b) -> b -> StreamK m a -> m b+ [A] foldlx' :: forall m a b x. Monad m => (x -> a -> x) -> x -> (x -> b) -> StreamK m a -> m b+ [A] foldlT :: (Monad m, Monad (s m), MonadTrans s) => (s m b -> a -> s m b) -> s m b -> StreamK m a -> s m b+ [A] foldlS :: (StreamK m b -> a -> StreamK m b) -> StreamK m b -> StreamK m a -> StreamK m b+ [A] foldlMx' :: Monad m => (x -> a -> m x) -> m x -> (x -> m b) -> StreamK m a -> m b+ [A] foldlM' :: Monad m => (b -> a -> m b) -> m b -> StreamK m a -> m b+ [A] foldl' :: Monad m => (b -> a -> b) -> b -> StreamK m a -> m b+ [A] foldStreamShared :: State StreamK m a -> (a -> StreamK m a -> m r) -> (a -> m r) -> m r -> StreamK m a -> m r+ [A] foldStream :: State StreamK m a -> (a -> StreamK m a -> m r) -> (a -> m r) -> m r -> StreamK m a -> m r+ [A] foldEither :: Monad m => Fold m a b -> StreamK m a -> m (Either (Fold m a b) (b, StreamK m a))+ [A] foldConcat :: Monad m => Producer m a b -> Fold m b c -> StreamK m a -> m (c, StreamK m a)+ [A] foldBreak :: Monad m => Fold m a b -> StreamK m a -> m (b, StreamK m a)+ [A] fold :: Monad m => Fold m a b -> StreamK m a -> m b+ [A] findM :: Monad m => (a -> m Bool) -> StreamK m a -> m (Maybe a)+ [A] findIndices :: (a -> Bool) -> StreamK m a -> StreamK m Int+ [A] find :: Monad m => (a -> Bool) -> StreamK m a -> m (Maybe a)+ [A] filter :: (a -> Bool) -> StreamK m a -> StreamK m a+ [A] evalStateT :: Monad m => m s -> StreamK (StateT s m) a -> StreamK m a+ [A] elem :: (Monad m, Eq a) => a -> StreamK m a -> m Bool+ [A] dropWhile :: (a -> Bool) -> StreamK m a -> StreamK m a+ [A] drop :: Int -> StreamK m a -> StreamK m a+ [A] drain :: Monad m => StreamK m a -> m ()+ [A] deleteBy :: (a -> a -> Bool) -> a -> StreamK m a -> StreamK m a+ [A] crossWith :: Monad m => (a -> b -> c) -> StreamK m a -> StreamK m b -> StreamK m c+ [A] crossApplyWith :: (StreamK m b -> StreamK m b -> StreamK m b) -> StreamK m (a -> b) -> StreamK m a -> StreamK m b+ [A] crossApplySnd :: StreamK m a -> StreamK m b -> StreamK m b+ [A] crossApplyFst :: StreamK m a -> StreamK m b -> StreamK m a+ [A] crossApply :: StreamK m (a -> b) -> StreamK m a -> StreamK m b+ [A] cross :: Monad m => StreamK m a -> StreamK m b -> StreamK m (a, b)+ [A] consMBy :: Monad m => (StreamK m a -> StreamK m a -> StreamK m a) -> m a -> StreamK m a -> StreamK m a+ [A] consM :: Monad m => m a -> StreamK m a -> StreamK m a+ [A] consK :: YieldK m a -> StreamK m a -> StreamK m a+ [A] cons :: a -> StreamK m a -> StreamK m a+ [A] conjoin :: Monad m => StreamK m a -> StreamK m a -> StreamK m a+ [A] concatMapWith :: (StreamK m b -> StreamK m b -> StreamK m b) -> (a -> StreamK m b) -> StreamK m a -> StreamK m b+ [A] concatMapEffect :: Monad m => (b -> StreamK m a) -> m b -> StreamK m a+ [A] concatMap :: (a -> StreamK m b) -> StreamK m a -> StreamK m b+ [A] concatIterateWith :: (StreamK m a -> StreamK m a -> StreamK m a) -> (a -> StreamK m a) -> StreamK m a -> StreamK m a+ [A] concatIterateScanWith :: Monad m => (StreamK m a -> StreamK m a -> StreamK m a) -> (b -> a -> m (b, StreamK m a)) -> m b -> StreamK m a -> StreamK m a+ [A] concatIterateLeftsWith :: b ~ Either a c => (StreamK m b -> StreamK m b -> StreamK m b) -> (a -> StreamK m b) -> StreamK m b -> StreamK m b+ [A] concatEffect :: Monad m => m (StreamK m a) -> StreamK m a+ [A] buildSM :: Monad m => ((m a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a) -> StreamK m a+ [A] buildS :: ((a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a) -> StreamK m a+ [A] buildM :: Monad m => (forall r. (a -> StreamK m a -> m r) -> (a -> m r) -> m r -> m r) -> StreamK m a+ [A] build :: forall m a. (forall b. (a -> b -> b) -> b -> b) -> StreamK m a+ [A] bracketIO :: (MonadIO m, MonadCatch m) => IO b -> (b -> IO c) -> (b -> StreamK m a) -> StreamK m a+ [A] bindWith :: (StreamK m b -> StreamK m b -> StreamK m b) -> StreamK m a -> (a -> StreamK m b) -> StreamK m b+ [A] before :: Monad m => m b -> StreamK m a -> StreamK m a+ [A] augmentSM :: Monad m => ((m a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a+ [A] augmentS :: ((a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a+ [A] append :: StreamK m a -> StreamK m a -> StreamK m a+ [A] any :: Monad m => (a -> Bool) -> StreamK m a -> m Bool+ [A] all :: Monad m => (a -> Bool) -> StreamK m a -> m Bool+ [A] (.:) :: a -> StreamK m a -> StreamK m a+ [A] (!!) :: Monad m => StreamK m a -> Int -> m (Maybe a)+[R] Streamly.Internal.Data.Stream.StreamK.Type+[R] Streamly.Internal.Data.Stream.StreamK.Transformer+[R] Streamly.Internal.Data.Stream.StreamK+[R] Streamly.Internal.Data.Stream.StreamD.Type+[R] Streamly.Internal.Data.Stream.StreamD.Transformer+[R] Streamly.Internal.Data.Stream.StreamD.Transform+[R] Streamly.Internal.Data.Stream.StreamD.Top+[R] Streamly.Internal.Data.Stream.StreamD.Step+[R] Streamly.Internal.Data.Stream.StreamD.Nesting+[R] Streamly.Internal.Data.Stream.StreamD.Lift+[R] Streamly.Internal.Data.Stream.StreamD.Generate+[R] Streamly.Internal.Data.Stream.StreamD.Exception+[R] Streamly.Internal.Data.Stream.StreamD.Eliminate+[R] Streamly.Internal.Data.Stream.StreamD.Container+[D] Streamly.Internal.Data.Stream.StreamD+[R] Streamly.Internal.Data.Stream.Common+[R] Streamly.Internal.Data.Stream.Chunked+[C] Streamly.Internal.Data.Stream+ [A] class Enum a => Enumerable a+ [A] Stream+ [A] UnStream :: (State StreamK m a -> s -> m (Step s a)) -> s -> Stream m a+ [A] Step+ [A] Yield :: a -> s -> Step s a+ [A] Stop :: Step s a+ [A] Skip :: s -> Step s a+ [A] InterleaveState+ [A] InterleaveSecondOnly :: s2 -> InterleaveState s1 s2+ [A] InterleaveSecond :: s1 -> s2 -> InterleaveState s1 s2+ [A] InterleaveFirstOnly :: s1 -> InterleaveState s1 s2+ [A] InterleaveFirst :: s1 -> s2 -> InterleaveState s1 s2+ [A] FoldManyPost+ [A] FoldManyPostYield :: b -> FoldManyPost s fs b a -> FoldManyPost s fs b a+ [A] FoldManyPostStart :: s -> FoldManyPost s fs b a+ [A] FoldManyPostLoop :: s -> fs -> FoldManyPost s fs b a+ [A] FoldManyPostDone :: FoldManyPost s fs b a+ [A] FoldMany+ [A] FoldManyYield :: b -> FoldMany s fs b a -> FoldMany s fs b a+ [A] FoldManyStart :: s -> FoldMany s fs b a+ [A] FoldManyLoop :: s -> fs -> FoldMany s fs b a+ [A] FoldManyFirst :: fs -> s -> FoldMany s fs b a+ [A] FoldManyDone :: FoldMany s fs b a+ [A] CrossStream+ [A] ConcatUnfoldInterleaveState+ [A] ConcatUnfoldInterleaveOuter :: o -> [i] -> ConcatUnfoldInterleaveState o i+ [A] ConcatUnfoldInterleaveInnerR :: [i] -> [i] -> ConcatUnfoldInterleaveState o i+ [A] ConcatUnfoldInterleaveInnerL :: [i] -> [i] -> ConcatUnfoldInterleaveState o i+ [A] ConcatUnfoldInterleaveInner :: o -> [i] -> ConcatUnfoldInterleaveState o i+ [A] ConcatMapUState+ [A] ConcatMapUOuter :: o -> ConcatMapUState o i+ [A] ConcatMapUInner :: o -> i -> ConcatMapUState o i+ [A] AppendState+ [A] AppendSecond :: s2 -> AppendState s1 s2+ [A] AppendFirst :: s1 -> AppendState s1 s2+ [A] pattern Stream :: (State StreamK m a -> s -> m (Step s a)) -> s -> Stream m a+ [A] zipWithM :: Monad m => (a -> b -> m c) -> Stream m a -> Stream m b -> Stream m c+ [A] zipWith :: Monad m => (a -> b -> c) -> Stream m a -> Stream m b -> Stream m c+ [A] wordsBy :: Monad m => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+ [A] with :: Monad m => (Stream m a -> Stream m (s, a)) -> (((s, a) -> b) -> Stream m (s, a) -> Stream m (s, a)) -> ((s, a) -> b) -> Stream m a -> Stream m a+ [A] usingStateT :: Monad m => m s -> (Stream (StateT s m) a -> Stream (StateT s m) a) -> Stream m a -> Stream m a+ [A] usingReaderT :: Monad m => m r -> (Stream (ReaderT r m) a -> Stream (ReaderT r m) a) -> Stream m a -> Stream m a+ [A] uniqBy :: Monad m => (a -> a -> Bool) -> Stream m a -> Stream m a+ [A] uniq :: (Eq a, Monad m) => Stream m a -> Stream m a+ [A] unionWithStreamGenericBy :: MonadIO m => (a -> a -> Bool) -> Stream m a -> Stream m a -> Stream m a+ [A] unionWithStreamAscBy :: (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a+ [A] unfoldrM :: Monad m => (s -> m (Maybe (a, s))) -> s -> Stream m a+ [A] unfoldr :: Monad m => (s -> Maybe (a, s)) -> s -> Stream m a+ [A] unfoldRoundRobin :: Monad m => Unfold m a b -> Stream m a -> Stream m b+ [A] unfoldMany :: Monad m => Unfold m a b -> Stream m a -> Stream m b+ [A] unfoldIterateDfs :: Monad m => Unfold m a a -> Stream m a -> Stream m a+ [A] unfoldIterateBfsRev :: Monad m => Unfold m a a -> Stream m a -> Stream m a+ [A] unfoldIterateBfs :: Monad m => Unfold m a a -> Stream m a -> Stream m a+ [A] unfoldInterleave :: Monad m => Unfold m a b -> Stream m a -> Stream m b+ [A] unfold :: Applicative m => Unfold m a b -> a -> Stream m b+ [A] uncons :: Monad m => Stream m a -> m (Maybe (a, Stream m a))+ [A] unCross :: CrossStream m a -> Stream m a+ [A] transform :: Monad m => Pipe m a b -> Stream m a -> Stream m b+ [A] trace_ :: Monad m => m b -> Stream m a -> Stream m a+ [A] trace :: Monad m => (a -> m b) -> Stream m a -> Stream m a+ [A] toStreamK :: Monad m => Stream m a -> StreamK m a+ [A] toListRev :: Monad m => Stream m a -> m [a]+ [A] toList :: Monad m => Stream m a -> m [a]+ [A] timestamped :: MonadIO m => Stream m a -> Stream m (AbsTime, a)+ [A] timestampWith :: MonadIO m => Double -> Stream m a -> Stream m (AbsTime, a)+ [A] timesWith :: MonadIO m => Double -> Stream m (AbsTime, RelTime64)+ [A] times :: MonadIO m => Stream m (AbsTime, RelTime64)+ [A] timeout :: AbsTime -> t m ()+ [A] timeIndexed :: MonadIO m => Stream m a -> Stream m (RelTime64, a)+ [A] timeIndexWith :: MonadIO m => Double -> Stream m a -> Stream m (RelTime64, a)+ [A] the :: (Eq a, Monad m) => Stream m a -> m (Maybe a)+ [A] tapOffsetEvery :: Monad m => Int -> Int -> Fold m a b -> Stream m a -> Stream m a+ [A] tap :: Monad m => Fold m a b -> Stream m a -> Stream m a+ [A] takeWhileM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a+ [A] takeWhileLast :: (a -> Bool) -> Stream m a -> Stream m a+ [A] takeWhileAround :: (a -> Bool) -> Stream m a -> Stream m a+ [A] takeWhile :: Monad m => (a -> Bool) -> Stream m a -> Stream m a+ [A] takeEndByM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a+ [A] takeEndBy :: Monad m => (a -> Bool) -> Stream m a -> Stream m a+ [A] take :: Applicative m => Int -> Stream m a -> Stream m a+ [A] tail :: Monad m => Stream m a -> m (Maybe (Stream m a))+ [A] stripSuffixUnbox :: (MonadIO m, Eq a, Unbox a) => Stream m a -> Stream m a -> m (Maybe (Stream m a))+ [A] stripSuffix :: (Monad m, Eq a) => Stream m a -> Stream m a -> m (Maybe (Stream m a))+ [A] stripPrefix :: (Monad m, Eq a) => Stream m a -> Stream m a -> m (Maybe (Stream m a))+ [A] strideFromThen :: Monad m => Int -> Int -> Stream m a -> Stream m a+ [A] splitOnSuffixSeqAny :: [Array a] -> Fold m a b -> Stream m a -> Stream m b+ [A] splitOnSuffixSeq :: forall m a b. (MonadIO m, Storable a, Unbox a, Enum a, Eq a) => Bool -> Array a -> Fold m a b -> Stream m a -> Stream m b+ [A] splitOnSeq :: forall m a b. (MonadIO m, Storable a, Unbox a, Enum a, Eq a) => Array a -> Fold m a b -> Stream m a -> Stream m b+ [A] splitOnPrefix :: (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+ [A] splitOnAny :: [Array a] -> Fold m a b -> Stream m a -> Stream m b+ [A] splitOn :: Monad m => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+ [A] splitInnerBySuffix :: (Monad m, Eq (f a), Monoid (f a)) => (f a -> m (f a, Maybe (f a))) -> (f a -> f a -> m (f a)) -> Stream m (f a) -> Stream m (f a)+ [A] splitInnerBy :: Monad m => (f a -> m (f a, Maybe (f a))) -> (f a -> f a -> m (f a)) -> Stream m (f a) -> Stream m (f a)+ [A] slicesBy :: Monad m => (a -> Bool) -> Stream m a -> Stream m (Int, Int)+ [A] sliceOnSuffix :: Monad m => (a -> Bool) -> Stream m a -> Stream m (Int, Int)+ [A] sequence :: Monad m => Stream m (m a) -> Stream m a+ [A] scanlx' :: Monad m => (x -> a -> x) -> x -> (x -> b) -> Stream m a -> Stream m b+ [A] scanlMx' :: Monad m => (x -> a -> m x) -> m x -> (x -> m b) -> Stream m a -> Stream m b+ [A] scanlMAfter' :: Monad m => (b -> a -> m b) -> m b -> (b -> m b) -> Stream m a -> Stream m b+ [A] scanlM' :: Monad m => (b -> a -> m b) -> m b -> Stream m a -> Stream m b+ [A] scanlM :: Monad m => (b -> a -> m b) -> m b -> Stream m a -> Stream m b+ [A] scanl1M' :: Monad m => (a -> a -> m a) -> Stream m a -> Stream m a+ [A] scanl1M :: Monad m => (a -> a -> m a) -> Stream m a -> Stream m a+ [A] scanl1' :: Monad m => (a -> a -> a) -> Stream m a -> Stream m a+ [A] scanl1 :: Monad m => (a -> a -> a) -> Stream m a -> Stream m a+ [A] scanl' :: Monad m => (b -> a -> b) -> b -> Stream m a -> Stream m b+ [A] scanl :: Monad m => (b -> a -> b) -> b -> Stream m a -> Stream m b+ [A] scanMaybe :: Monad m => Fold m a (Maybe b) -> Stream m a -> Stream m b+ [A] scanMany :: Monad m => Fold m a b -> Stream m a -> Stream m b+ [A] scan :: Monad m => Fold m a b -> Stream m a -> Stream m b+ [A] runStateT :: Monad m => m s -> Stream (StateT s m) a -> Stream m (s, a)+ [A] runReaderT :: Monad m => m s -> Stream (ReaderT s m) a -> Stream m a+ [A] runInnerWithState :: Monad m => (forall b. s -> t m b -> m (b, s)) -> m s -> Stream (t m) a -> Stream m (s, a)+ [A] runInnerWith :: Monad m => (forall b. t m b -> m b) -> Stream (t m) a -> Stream m a+ [A] roundRobin :: Monad m => Stream m a -> Stream m a -> Stream m a+ [A] rollingMapM :: Monad m => (Maybe a -> a -> m b) -> Stream m a -> Stream m b+ [A] rollingMap2 :: Monad m => (a -> a -> b) -> Stream m a -> Stream m b+ [A] rollingMap :: Monad m => (Maybe a -> a -> b) -> Stream m a -> Stream m b+ [A] reverseUnbox :: (MonadIO m, Unbox a) => Stream m a -> Stream m a+ [A] reverse :: Monad m => Stream m a -> Stream m a+ [A] replicateM :: Monad m => Int -> m a -> Stream m a+ [A] replicate :: Monad m => Int -> a -> Stream m a+ [A] repeated :: Stream m a -> Stream m a+ [A] repeatM :: Monad m => m a -> Stream m a+ [A] repeat :: Monad m => a -> Stream m a+ [A] relTimesWith :: MonadIO m => Double -> Stream m RelTime64+ [A] relTimes :: MonadIO m => Stream m RelTime64+ [A] refoldMany :: Monad m => Refold m x a b -> m x -> Stream m a -> Stream m b+ [A] refoldIterateM :: Monad m => Refold m b a b -> m b -> Stream m a -> Stream m b+ [A] reduceIterateBfs :: Monad m => (a -> a -> m a) -> Stream m a -> m (Maybe a)+ [A] reassembleBy :: Fold m a b -> (a -> a -> Int) -> Stream m a -> Stream m b+ [A] prune :: (a -> Bool) -> Stream m a -> Stream m a+ [A] prescanlM' :: Monad m => (b -> a -> m b) -> m b -> Stream m a -> Stream m b+ [A] prescanl' :: Monad m => (b -> a -> b) -> b -> Stream m a -> Stream m b+ [A] postscanlx' :: Monad m => (x -> a -> x) -> x -> (x -> b) -> Stream m a -> Stream m b+ [A] postscanlMx' :: Monad m => (x -> a -> m x) -> m x -> (x -> m b) -> Stream m a -> Stream m b+ [A] postscanlMAfter' :: Monad m => (b -> a -> m b) -> m b -> (b -> m b) -> Stream m a -> Stream m b+ [A] postscanlM' :: Monad m => (b -> a -> m b) -> m b -> Stream m a -> Stream m b+ [A] postscanlM :: Monad m => (b -> a -> m b) -> m b -> Stream m a -> Stream m b+ [A] postscanl' :: Monad m => (a -> b -> a) -> a -> Stream m b -> Stream m a+ [A] postscanl :: Monad m => (a -> b -> a) -> a -> Stream m b -> Stream m a+ [A] postscan :: Monad m => Fold m a b -> Stream m a -> Stream m b+ [A] parseSequence :: Stream m (Parser a m b) -> Stream m a -> Stream m b+ [A] parseManyTill :: Parser a m b -> Parser a m x -> Stream m a -> Stream m b+ [A] parseManyD :: Monad m => Parser a m b -> Stream m a -> Stream m (Either ParseError b)+ [A] parseMany :: Monad m => Parser a m b -> Stream m a -> Stream m (Either ParseError b)+ [A] parseIterateD :: Monad m => (b -> Parser a m b) -> b -> Stream m a -> Stream m (Either ParseError b)+ [A] parseIterate :: Monad m => (b -> Parser a m b) -> b -> Stream m a -> Stream m (Either ParseError b)+ [A] parseD :: Monad m => Parser a m b -> Stream m a -> m (Either ParseError b)+ [A] parseBreakD :: Monad m => Parser a m b -> Stream m a -> m (Either ParseError b, Stream m a)+ [A] parseBreak :: Monad m => Parser a m b -> Stream m a -> m (Either ParseError b, Stream m a)+ [A] parse :: Monad m => Parser a m b -> Stream m a -> m (Either ParseError b)+ [A] onException :: MonadCatch m => m b -> Stream m a -> Stream m a+ [A] null :: Monad m => Stream m a -> m Bool+ [A] nub :: (Monad m, Ord a) => Stream m a -> Stream m a+ [A] notElem :: (Monad m, Eq a) => a -> Stream m a -> m Bool+ [A] nilM :: Applicative m => m b -> Stream m a+ [A] nil :: Applicative m => Stream m a+ [A] morphInner :: Monad n => (forall x. m x -> n x) -> Stream m a -> Stream n a+ [A] mkCross :: Stream m a -> CrossStream m a+ [A] minimumBy :: Monad m => (a -> a -> Ordering) -> Stream m a -> m (Maybe a)+ [A] minimum :: (Monad m, Ord a) => Stream m a -> m (Maybe a)+ [A] mergeMinBy :: (a -> a -> m Ordering) -> Stream m a -> Stream m a -> Stream m a+ [A] mergeFstBy :: (a -> a -> m Ordering) -> Stream m a -> Stream m a -> Stream m a+ [A] mergeByM :: Monad m => (a -> a -> m Ordering) -> Stream m a -> Stream m a -> Stream m a+ [A] mergeBy :: Monad m => (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a+ [A] maximumBy :: Monad m => (a -> a -> Ordering) -> Stream m a -> m (Maybe a)+ [A] maximum :: (Monad m, Ord a) => Stream m a -> m (Maybe a)+ [A] mapMaybeM :: Monad m => (a -> m (Maybe b)) -> Stream m a -> Stream m b+ [A] mapMaybe :: Monad m => (a -> Maybe b) -> Stream m a -> Stream m b+ [A] mapM_ :: Monad m => (a -> m b) -> Stream m a -> m ()+ [A] mapM :: Monad m => (a -> m b) -> Stream m a -> Stream m b+ [A] map :: Monad m => (a -> b) -> Stream m a -> Stream m b+ [A] lookup :: (Monad m, Eq a) => a -> Stream m (a, b) -> m (Maybe b)+ [A] liftInnerWith :: Monad (t m) => (forall b. m b -> t m b) -> Stream m a -> Stream (t m) a+ [A] liftInner :: (Monad m, MonadTrans t, Monad (t m)) => Stream m a -> Stream (t m) a+ [A] last :: Monad m => Stream m a -> m (Maybe a)+ [A] joinOuterGeneric :: MonadIO m => (a -> b -> Bool) -> Stream m a -> Stream m b -> Stream m (Maybe a, Maybe b)+ [A] joinOuterAscBy :: (a -> b -> Ordering) -> Stream m a -> Stream m b -> Stream m (Maybe a, Maybe b)+ [A] joinOuter :: (Ord k, MonadIO m) => Stream m (k, a) -> Stream m (k, b) -> Stream m (k, Maybe a, Maybe b)+ [A] joinLeftGeneric :: Monad m => (a -> b -> Bool) -> Stream m a -> Stream m b -> Stream m (a, Maybe b)+ [A] joinLeftAscBy :: (a -> b -> Ordering) -> Stream m a -> Stream m b -> Stream m (a, Maybe b)+ [A] joinLeft :: (Ord k, Monad m) => Stream m (k, a) -> Stream m (k, b) -> Stream m (k, a, Maybe b)+ [A] joinInnerGeneric :: Monad m => (a -> b -> Bool) -> Stream m a -> Stream m b -> Stream m (a, b)+ [A] joinInnerAscBy :: (a -> b -> Ordering) -> Stream m a -> Stream m b -> Stream m (a, b)+ [A] joinInner :: (Monad m, Ord k) => Stream m (k, a) -> Stream m (k, b) -> Stream m (k, a, b)+ [A] iterateM :: Monad m => (a -> m a) -> m a -> Stream m a+ [A] iterate :: Monad m => (a -> a) -> a -> Stream m a+ [A] isSuffixOfUnbox :: (MonadIO m, Eq a, Unbox a) => Stream m a -> Stream m a -> m Bool+ [A] isSuffixOf :: (Monad m, Eq a) => Stream m a -> Stream m a -> m Bool+ [A] isSubsequenceOf :: (Monad m, Eq a) => Stream m a -> Stream m a -> m Bool+ [A] isPrefixOf :: (Monad m, Eq a) => Stream m a -> Stream m a -> m Bool+ [A] isInfixOf :: (MonadIO m, Eq a, Enum a, Storable a, Unbox a) => Stream m a -> Stream m a -> m Bool+ [A] intersperseM_ :: Monad m => m b -> Stream m a -> Stream m a+ [A] intersperseMWith :: Int -> m a -> Stream m a -> Stream m a+ [A] intersperseMSuffix_ :: Monad m => m b -> Stream m a -> Stream m a+ [A] intersperseMSuffixWith :: forall m a. Monad m => Int -> m a -> Stream m a -> Stream m a+ [A] intersperseMSuffix :: forall m a. Monad m => m a -> Stream m a -> Stream m a+ [A] intersperseMPrefix_ :: Monad m => m b -> Stream m a -> Stream m a+ [A] intersperseM :: Monad m => m a -> Stream m a -> Stream m a+ [A] intersperse :: Monad m => a -> Stream m a -> Stream m a+ [A] intersectBySorted :: Monad m => (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a+ [A] interposeSuffixM :: Monad m => m c -> Unfold m b c -> Stream m b -> Stream m c+ [A] interposeSuffix :: Monad m => c -> Unfold m b c -> Stream m b -> Stream m c+ [A] interposeM :: Monad m => m c -> Unfold m b c -> Stream m b -> Stream m c+ [A] interpose :: Monad m => c -> Unfold m b c -> Stream m b -> Stream m c+ [A] interleaveMin :: Monad m => Stream m a -> Stream m a -> Stream m a+ [A] interleaveFstSuffix :: Monad m => Stream m a -> Stream m a -> Stream m a+ [A] interleaveFst :: Monad m => Stream m a -> Stream m a -> Stream m a+ [A] interleave :: Monad m => Stream m a -> Stream m a -> Stream m a+ [A] intercalateSuffix :: Monad m => Unfold m b c -> b -> Stream m b -> Stream m c+ [A] intercalate :: Monad m => Unfold m b c -> b -> Stream m b -> Stream m c+ [A] insertBy :: Monad m => (a -> a -> Ordering) -> a -> Stream m a -> Stream m a+ [A] indexedR :: Monad m => Int -> Stream m a -> Stream m (Int, a)+ [A] indexed :: Monad m => Stream m a -> Stream m (Int, a)+ [A] headElse :: Monad m => a -> Stream m a -> m a+ [A] head :: Monad m => Stream m a -> m (Maybe a)+ [A] handle :: (MonadCatch m, Exception e) => (e -> m (Stream m a)) -> Stream m a -> Stream m a+ [A] groupsWhile :: Monad m => (a -> a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+ [A] groupsRollingBy :: Monad m => (a -> a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+ [A] groupsOf :: Monad m => Int -> Fold m a b -> Stream m a -> Stream m b+ [A] gintercalateSuffix :: Monad m => Unfold m a c -> Stream m a -> Unfold m b c -> Stream m b -> Stream m c+ [A] gintercalate :: Monad m => Unfold m a c -> Stream m a -> Unfold m b c -> Stream m b -> Stream m c+ [A] ghandle :: (MonadCatch m, Exception e) => (e -> Stream m a -> m (Stream m a)) -> Stream m a -> Stream m a+ [A] generateM :: Monad m => Int -> (Int -> m a) -> Stream m a+ [A] generate :: Monad m => Int -> (Int -> a) -> Stream m a+ [A] generalizeInner :: Monad m => Stream Identity a -> Stream m a+ [A] gbracket_ :: Monad m => m c -> (c -> m d) -> (c -> e -> Stream m b -> m (Stream m b)) -> (forall s. m s -> m (Either e s)) -> (c -> Stream m b) -> Stream m b+ [A] gbracket :: MonadIO m => IO c -> (c -> IO d1) -> (c -> e -> Stream m b -> IO (Stream m b)) -> (c -> IO d2) -> (forall s. m s -> m (Either e s)) -> (c -> Stream m b) -> Stream m b+ [A] fromStreamK :: Applicative m => StreamK m a -> Stream m a+ [A] fromPure :: Applicative m => a -> Stream m a+ [A] fromPtrN :: (Monad m, Storable a) => Int -> Ptr a -> Stream m a+ [A] fromPtr :: forall m a. (Monad m, Storable a) => Ptr a -> Stream m a+ [A] fromListM :: Monad m => [m a] -> Stream m a+ [A] fromList :: Applicative m => [a] -> Stream m a+ [A] fromIndicesM :: Monad m => (Int -> m a) -> Stream m a+ [A] fromIndices :: Monad m => (Int -> a) -> Stream m a+ [A] fromFoldableM :: (Monad m, Foldable f) => f (m a) -> Stream m a+ [A] fromFoldable :: (Monad m, Foldable f) => f a -> Stream m a+ [A] fromEffect :: Applicative m => m a -> Stream m a+ [A] fromByteStr# :: Monad m => Addr# -> Stream m Word8+ [A] foldrT :: (Monad m, Monad (t m), MonadTrans t) => (a -> t m b -> t m b) -> t m b -> Stream m a -> t m b+ [A] foldrS :: Monad m => (a -> Stream m b -> Stream m b) -> Stream m b -> Stream m a -> Stream m b+ [A] foldrMx :: Monad m => (a -> m x -> m x) -> m x -> (m x -> m b) -> Stream m a -> m b+ [A] foldrM :: Monad m => (a -> m b -> m b) -> m b -> Stream m a -> m b+ [A] foldr1 :: Monad m => (a -> a -> a) -> Stream m a -> m (Maybe a)+ [A] foldr :: Monad m => (a -> b -> b) -> b -> Stream m a -> m b+ [A] foldlx' :: Monad m => (x -> a -> x) -> x -> (x -> b) -> Stream m a -> m b+ [A] foldlT :: (Monad m, Monad (s m), MonadTrans s) => (s m b -> a -> s m b) -> s m b -> Stream m a -> s m b+ [A] foldlS :: Monad m => (Stream m b -> a -> Stream m b) -> Stream m b -> Stream m a -> Stream m b+ [A] foldlMx' :: Monad m => (x -> a -> m x) -> m x -> (x -> m b) -> Stream m a -> m b+ [A] foldlM' :: Monad m => (b -> a -> m b) -> m b -> Stream m a -> m b+ [A] foldl' :: Monad m => (b -> a -> b) -> b -> Stream m a -> m b+ [A] foldSequence :: Stream m (Fold m a b) -> Stream m a -> Stream m b+ [A] foldManyPost :: Monad m => Fold m a b -> Stream m a -> Stream m b+ [A] foldMany :: Monad m => Fold m a b -> Stream m a -> Stream m b+ [A] foldIterateM :: Monad m => (b -> m (Fold m a b)) -> m b -> Stream m a -> Stream m b+ [A] foldIterateBfs :: Fold m a (Either a a) -> Stream m a -> m (Maybe a)+ [A] foldEither :: Monad m => Fold m a b -> Stream m a -> m (Either (Fold m a b) (b, Stream m a))+ [A] foldBreak :: Monad m => Fold m a b -> Stream m a -> m (b, Stream m a)+ [A] foldAddLazy :: Monad m => Fold m a b -> Stream m a -> Fold m a b+ [A] foldAdd :: Monad m => Fold m a b -> Stream m a -> m (Fold m a b)+ [A] fold :: Monad m => Fold m a b -> Stream m a -> m b+ [A] findM :: Monad m => (a -> m Bool) -> Stream m a -> m (Maybe a)+ [A] findIndices :: Monad m => (a -> Bool) -> Stream m a -> Stream m Int+ [A] find :: Monad m => (a -> Bool) -> Stream m a -> m (Maybe a)+ [A] finallyUnsafe :: MonadCatch m => m b -> Stream m a -> Stream m a+ [A] finallyIO :: (MonadIO m, MonadCatch m) => IO b -> Stream m a -> Stream m a+ [A] filterM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a+ [A] filterInStreamGenericBy :: Monad m => (a -> a -> Bool) -> Stream m a -> Stream m a -> Stream m a+ [A] filterInStreamAscBy :: Monad m => (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a+ [A] filter :: Monad m => (a -> Bool) -> Stream m a -> Stream m a+ [A] evalStateT :: Monad m => m s -> Stream (StateT s m) a -> Stream m a+ [A] eqBy :: Monad m => (a -> b -> Bool) -> Stream m a -> Stream m b -> m Bool+ [A] enumerateTo :: (Monad m, Bounded a, Enumerable a) => a -> Stream m a+ [A] enumerateFromToSmall :: (Monad m, Enum a) => a -> a -> Stream m a+ [A] enumerateFromToIntegral :: (Monad m, Integral a) => a -> a -> Stream m a+ [A] enumerateFromToFractional :: (Monad m, Fractional a, Ord a) => a -> a -> Stream m a+ [A] enumerateFromTo :: (Enumerable a, Monad m) => a -> a -> Stream m a+ [A] enumerateFromThenToSmall :: (Monad m, Enum a) => a -> a -> a -> Stream m a+ [A] enumerateFromThenToIntegral :: (Monad m, Integral a) => a -> a -> a -> Stream m a+ [A] enumerateFromThenToFractional :: (Monad m, Fractional a, Ord a) => a -> a -> a -> Stream m a+ [A] enumerateFromThenTo :: (Enumerable a, Monad m) => a -> a -> a -> Stream m a+ [A] enumerateFromThenSmallBounded :: (Monad m, Enumerable a, Bounded a) => a -> a -> Stream m a+ [A] enumerateFromThenNum :: (Monad m, Num a) => a -> a -> Stream m a+ [A] enumerateFromThenIntegral :: (Monad m, Integral a, Bounded a) => a -> a -> Stream m a+ [A] enumerateFromThenFractional :: (Monad m, Fractional a) => a -> a -> Stream m a+ [A] enumerateFromThen :: (Enumerable a, Monad m) => a -> a -> Stream m a+ [A] enumerateFromStepNum :: (Monad m, Num a) => a -> a -> Stream m a+ [A] enumerateFromStepIntegral :: (Integral a, Monad m) => a -> a -> Stream m a+ [A] enumerateFromNum :: (Monad m, Num a) => a -> Stream m a+ [A] enumerateFromIntegral :: (Monad m, Integral a, Bounded a) => a -> Stream m a+ [A] enumerateFromFractional :: (Monad m, Fractional a) => a -> Stream m a+ [A] enumerateFromBounded :: (Monad m, Enumerable a, Bounded a) => a -> Stream m a+ [A] enumerateFrom :: (Enumerable a, Monad m) => a -> Stream m a+ [A] enumerate :: (Monad m, Bounded a, Enumerable a) => Stream m a+ [A] elemIndices :: (Monad m, Eq a) => a -> Stream m a -> Stream m Int+ [A] elem :: (Monad m, Eq a) => a -> Stream m a -> m Bool+ [A] durations :: Double -> t m RelTime64+ [A] dropWhileM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a+ [A] dropWhileLast :: (a -> Bool) -> Stream m a -> Stream m a+ [A] dropWhileAround :: (a -> Bool) -> Stream m a -> Stream m a+ [A] dropWhile :: Monad m => (a -> Bool) -> Stream m a -> Stream m a+ [A] dropSuffix :: Stream m a -> Stream m a -> Stream m a+ [A] dropPrefix :: Stream m a -> Stream m a -> Stream m a+ [A] dropLast :: Int -> Stream m a -> Stream m a+ [A] dropInfix :: Stream m a -> Stream m a -> Stream m a+ [A] drop :: Monad m => Int -> Stream m a -> Stream m a+ [A] drain :: Monad m => Stream m a -> m ()+ [A] deleteInStreamGenericBy :: Monad m => (a -> a -> Bool) -> Stream m a -> Stream m a -> Stream m a+ [A] deleteInStreamAscBy :: (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a+ [A] deleteBy :: Monad m => (a -> a -> Bool) -> a -> Stream m a -> Stream m a+ [A] delayPre :: MonadIO m => Double -> Stream m a -> Stream m a+ [A] delayPost :: MonadIO m => Double -> Stream m a -> Stream m a+ [A] delay :: MonadIO m => Double -> Stream m a -> Stream m a+ [A] crossWith :: Monad m => (a -> b -> c) -> Stream m a -> Stream m b -> Stream m c+ [A] crossApplySnd :: Functor f => Stream f a -> Stream f b -> Stream f b+ [A] crossApplyFst :: Functor f => Stream f a -> Stream f b -> Stream f a+ [A] crossApply :: Functor f => Stream f (a -> b) -> Stream f a -> Stream f b+ [A] cross :: Monad m => Stream m a -> Stream m b -> Stream m (a, b)+ [A] consM :: Applicative m => m a -> Stream m a -> Stream m a+ [A] cons :: Applicative m => a -> Stream m a -> Stream m a+ [A] concatMapM :: Monad m => (a -> m (Stream m b)) -> Stream m a -> Stream m b+ [A] concatMap :: Monad m => (a -> Stream m b) -> Stream m a -> Stream m b+ [A] concatIterateScan :: Monad m => (b -> a -> m b) -> (b -> m (Maybe (b, Stream m a))) -> b -> Stream m a+ [A] concatIterateDfs :: Monad m => (a -> Maybe (Stream m a)) -> Stream m a -> Stream m a+ [A] concatIterateBfsRev :: Monad m => (a -> Maybe (Stream m a)) -> Stream m a -> Stream m a+ [A] concatIterateBfs :: Monad m => (a -> Maybe (Stream m a)) -> Stream m a -> Stream m a+ [A] concatEffect :: Monad m => m (Stream m a) -> Stream m a+ [A] concat :: Monad m => Stream m (Stream m a) -> Stream m a+ [A] cmpBy :: Monad m => (a -> b -> Ordering) -> Stream m a -> Stream m b -> m Ordering+ [A] catRights :: Monad m => Stream m (Either a b) -> Stream m b+ [A] catMaybes :: Monad m => Stream m (Maybe a) -> Stream m a+ [A] catLefts :: Monad m => Stream m (Either a b) -> Stream m a+ [A] catEithers :: Monad m => Stream m (Either a a) -> Stream m a+ [A] bracketUnsafe :: MonadCatch m => m b -> (b -> m c) -> (b -> Stream m a) -> Stream m a+ [A] bracketIO3 :: (MonadIO m, MonadCatch m) => IO b -> (b -> IO c) -> (b -> IO d) -> (b -> IO e) -> (b -> Stream m a) -> Stream m a+ [A] bracketIO :: (MonadIO m, MonadCatch m) => IO b -> (b -> IO c) -> (b -> Stream m a) -> Stream m a+ [A] before :: Monad m => m b -> Stream m a -> Stream m a+ [A] append :: Monad m => Stream m a -> Stream m a -> Stream m a+ [A] any :: Monad m => (a -> Bool) -> Stream m a -> m Bool+ [A] all :: Monad m => (a -> Bool) -> Stream m a -> m Bool+ [A] afterUnsafe :: Monad m => m b -> Stream m a -> Stream m a+ [A] afterIO :: MonadIO m => IO b -> Stream m a -> Stream m a+ [A] absTimesWith :: MonadIO m => Double -> Stream m AbsTime+ [A] absTimes :: MonadIO m => Stream m AbsTime+ [A] (!!) :: Monad m => Stream m a -> Int -> m (Maybe a)+[R] Streamly.Internal.Data.Ring.Unboxed+[A] Streamly.Internal.Data.Ring.Generic+ [A] Ring+ [A] [ringMax] :: Ring a -> !Int+ [A] [ringHead] :: Ring a -> !Int+ [A] [ringArr] :: Ring a -> MutArray a+ [A] Ring :: MutArray a -> !Int -> !Int -> Ring a+ [A] writeLastN :: MonadIO m => Int -> Fold m a (Ring a)+ [A] unsafeInsertRingWith :: Ring a -> a -> IO Int+ [A] toStreamWith :: Int -> Ring a -> Stream m a+ [A] toMutArray :: MonadIO m => Int -> Int -> Ring a -> m (MutArray a)+ [A] seek :: MonadIO m => Int -> Ring a -> m (Ring a)+ [A] createRing :: MonadIO m => Int -> m (Ring a)+ [A] copyToMutArray :: MonadIO m => Int -> Int -> Ring a -> m (MutArray a)+[C] Streamly.Internal.Data.Ring+ [C] Ring+ [A] [ringStart] :: Ring a -> {-# UNPACK #-} !ForeignPtr a+ [R] [ringMax] :: Ring a -> !Int+ [R] [ringHead] :: Ring a -> !Int+ [A] [ringBound] :: Ring a -> {-# UNPACK #-} !Ptr a+ [R] [ringArr] :: Ring a -> MutArray a+ [C] Ring+ [O] Ring :: MutArray a -> !Int -> !Int -> Ring a+ [N] Ring :: {-# UNPACK #-} !ForeignPtr a -> {-# UNPACK #-} !Ptr a -> Ring a+ [A] GHC.Show.Show+ [A] instance (GHC.Show.Show a, GHC.Show.Show b, GHC.Show.Show c, GHC.Show.Show d) => GHC.Show.Show (Streamly.Internal.Data.Ring.Tuple4' a b c d)+ [A] writeN :: Int -> Fold m a (Ring a)+ [R] writeLastN :: MonadIO m => Int -> Fold m a (Ring a)+ [R] unsafeInsertRingWith :: Ring a -> a -> IO Int+ [A] unsafeInsert :: Storable a => Ring a -> Ptr a -> a -> IO (Ptr a)+ [A] unsafeFoldRingNM :: forall m a b. (MonadIO m, Storable a) => Int -> Ptr a -> (b -> a -> m b) -> b -> Ring a -> m b+ [A] unsafeFoldRingM :: forall m a b. (MonadIO m, Storable a) => Ptr a -> (b -> a -> m b) -> b -> Ring a -> m b+ [A] unsafeFoldRingFullM :: forall m a b. (MonadIO m, Storable a) => Ptr a -> (b -> a -> m b) -> b -> Ring a -> m b+ [A] unsafeFoldRing :: forall a b. Storable a => Ptr a -> (b -> a -> b) -> b -> Ring a -> b+ [A] unsafeEqArrayN :: Ring a -> Ptr a -> Array a -> Int -> Bool+ [A] unsafeEqArray :: Ring a -> Ptr a -> Array a -> Bool+ [R] toStreamWith :: Int -> Ring a -> Stream m a+ [R] toMutArray :: MonadIO m => Int -> Int -> Ring a -> m (MutArray a)+ [A] startOf :: Ring a -> Ptr a+ [A] slidingWindowWith :: forall m a b. (MonadIO m, Storable a, Unbox a) => Int -> Fold m ((a, Maybe a), m (MutArray a)) b -> Fold m a b+ [A] slidingWindow :: forall m a b. (MonadIO m, Storable a, Unbox a) => Int -> Fold m (a, Maybe a) b -> Fold m a b+ [A] slide :: Ring a -> a -> m (Ring a)+ [R] seek :: MonadIO m => Int -> Ring a -> m (Ring a)+ [A] ringsOf :: Int -> Stream m a -> Stream m (MutArray a)+ [A] readRev :: Unfold m (MutArray a) a+ [A] read :: forall m a. (MonadIO m, Storable a) => Unfold m (Ring a, Ptr a, Int) a+ [A] putIndex :: Ring a -> Int -> a -> m ()+ [A] newRing :: Int -> m (Ring a)+ [A] new :: forall a. Storable a => Int -> IO (Ring a, Ptr a)+ [A] moveBy :: forall a. Storable a => Int -> Ring a -> Ptr a -> Ptr a+ [A] modifyIndex :: Ring a -> Int -> (a -> (a, b)) -> m b+ [A] length :: Ring a -> Int+ [A] getIndexUnsafe :: Ring a -> Int -> m a+ [A] getIndexRev :: Ring a -> Int -> m a+ [A] getIndex :: Ring a -> Int -> m a+ [A] fromArray :: MutArray a -> Ring a+ [R] createRing :: MonadIO m => Int -> m (Ring a)+ [A] castUnsafe :: Ring a -> Ring b+ [A] cast :: forall a b. Storable b => Ring a -> Maybe (Ring b)+ [A] bytesFree :: Ring a -> Int+ [A] byteLength :: Ring a -> Int+ [A] byteCapacity :: Ring a -> Int+ [A] asBytes :: Ring a -> Ring Word8+ [A] advance :: forall a. Storable a => Ring a -> Ptr a -> Ptr a+[R] Streamly.Internal.Data.Producer.Type+[R] Streamly.Internal.Data.Producer.Source+[C] Streamly.Internal.Data.Producer+ [A] Source+ [A] unread :: [b] -> Source a b -> Source a b+ [A] translate :: Functor m => (a -> c) -> (c -> a) -> Producer m c b -> Producer m a b+ [A] source :: Maybe a -> Source a b+ [A] producer :: Monad m => Producer m a b -> Producer m (Source a b) b+ [A] parseManyD :: Monad m => Parser a m b -> Producer m (Source x a) a -> Producer m (Source x a) (Either ParseError b)+ [A] parseMany :: Monad m => Parser a m b -> Producer m (Source x a) a -> Producer m (Source x a) (Either ParseError b)+ [A] parse :: Monad m => Parser a m b -> Producer m (Source s a) a -> Source s a -> m (Either ParseError b, Source s a)+ [A] lmap :: (a -> a) -> Producer m a b -> Producer m a b+ [A] isEmpty :: Source a b -> Bool+[R] Streamly.Internal.Data.Pipe.Type+[C] Streamly.Internal.Data.Pipe+ [A] Step+ [A] Yield :: a -> s -> Step s a+ [A] Continue :: s -> Step s a+ [A] PipeState+ [A] Produce :: s2 -> PipeState s1 s2+ [A] Consume :: s1 -> PipeState s1 s2+ [C] Pipe+ [A] Pipe :: (s1 -> a -> m (Step (PipeState s1 s2) b)) -> (s2 -> m (Step (PipeState s1 s2) b)) -> s1 -> Pipe m a b+[A] Streamly.Internal.Data.ParserK+ [A] Step+ [A] Partial :: !Int -> (Input a -> m (Step a m r)) -> Step a m r+ [A] Error :: !Int -> String -> Step a m r+ [A] Done :: !Int -> r -> Step a m r+ [A] Continue :: !Int -> (Input a -> m (Step a m r)) -> Step a m r+ [A] ParseResult+ [A] Success :: !Int -> !b -> ParseResult b+ [A] Failure :: !Int -> !String -> ParseResult b+ [A] Input+ [A] None :: Input a+ [A] Chunk :: a -> Input a+ [A] ParserK+ [A] [runParser] :: ParserK a m b -> forall r. (ParseResult b -> Int -> Input a -> m (Step a m r)) -> Int -> Int -> Input a -> m (Step a m r)+ [A] MkParser :: (forall r. (ParseResult b -> Int -> Input a -> m (Step a m r)) -> Int -> Int -> Input a -> m (Step a m r)) -> ParserK a m b+ [A] fromPure :: b -> ParserK a m b+ [A] fromEffect :: Monad m => m b -> ParserK a m b+ [A] die :: String -> ParserK a m b+ [A] adaptCG :: Monad m => Parser a m b -> ParserK (Array a) m b+ [A] adaptC :: (Monad m, Unbox a) => Parser a m b -> ParserK (Array a) m b+ [A] adapt :: Monad m => Parser a m b -> ParserK a m b+[R] Streamly.Internal.Data.Parser.ParserK.Type+[R] Streamly.Internal.Data.Parser.ParserD.Type+[R] Streamly.Internal.Data.Parser.ParserD.Tee+[R] Streamly.Internal.Data.Parser.ParserD+[C] Streamly.Internal.Data.Parser+ [A] Step+ [A] Partial :: !Int -> !s -> Step s b+ [A] Error :: !String -> Step s b+ [A] Done :: !Int -> !b -> Step s b+ [A] Continue :: !Int -> !s -> Step s b+ [A] Parser+ [A] Parser :: (s -> a -> m (Step s b)) -> m (Initial s b) -> (s -> m (Step s b)) -> Parser a m b+ [A] Initial+ [A] IPartial :: !s -> Initial s b+ [A] IError :: !String -> Initial s b+ [A] IDone :: !b -> Initial s b+ [A] GHC.Show.Show+ [A] instance (GHC.Show.Show a, GHC.Show.Show b) => GHC.Show.Show (Streamly.Internal.Data.Parser.Tuple'Fused a b)+ [A] ParseError+ [A] ParseError :: String -> ParseError+ [A] zipWithM :: Monad m => (a -> b -> m c) -> Stream m a -> Fold m c x -> Parser b m x+ [A] zip :: Monad m => Stream m a -> Fold m (a, b) x -> Parser b m x+ [A] wordWithQuotes :: (Monad m, Eq a) => Bool -> (a -> a -> Maybe a) -> a -> (a -> Maybe a) -> (a -> Bool) -> Fold m a b -> Parser a m b+ [A] wordProcessQuotes :: (Monad m, Eq a) => a -> (a -> Maybe a) -> (a -> Bool) -> Fold m a b -> Parser a m b+ [A] wordKeepQuotes :: (Monad m, Eq a) => a -> (a -> Maybe a) -> (a -> Bool) -> Fold m a b -> Parser a m b+ [A] wordFramedBy :: Monad m => (a -> Bool) -> (a -> Bool) -> (a -> Bool) -> (a -> Bool) -> Fold m a b -> Parser a m b+ [A] wordBy :: Monad m => (a -> Bool) -> Fold m a b -> Parser a m b+ [A] toFold :: Monad m => Parser a m b -> Fold m a b+ [A] takeWhileP :: Monad m => (a -> Bool) -> Parser a m b -> Parser a m b+ [A] takeWhile1 :: Monad m => (a -> Bool) -> Fold m a b -> Parser a m b+ [A] takeWhile :: Monad m => (a -> Bool) -> Fold m a b -> Parser a m b+ [A] takeStartBy_ :: Monad m => (a -> Bool) -> Fold m a b -> Parser a m b+ [A] takeStartBy :: Monad m => (a -> Bool) -> Fold m a b -> Parser a m b+ [A] takeP :: Monad m => Int -> Parser a m b -> Parser a m b+ [A] takeGE :: Monad m => Int -> Fold m a b -> Parser a m b+ [A] takeFramedBy_ :: Monad m => (a -> Bool) -> (a -> Bool) -> Fold m a b -> Parser a m b+ [A] takeFramedByGeneric :: Monad m => Maybe (a -> Bool) -> Maybe (a -> Bool) -> Maybe (a -> Bool) -> Fold m a b -> Parser a m b+ [A] takeFramedByEsc_ :: Monad m => (a -> Bool) -> (a -> Bool) -> (a -> Bool) -> Fold m a b -> Parser a m b+ [A] takeEndBy_ :: (a -> Bool) -> Parser a m b -> Parser a m b+ [A] takeEndByEsc :: Monad m => (a -> Bool) -> (a -> Bool) -> Parser a m b -> Parser a m b+ [A] takeEndBy :: Monad m => (a -> Bool) -> Parser a m b -> Parser a m b+ [A] takeEitherSepBy :: (a -> Bool) -> Fold m (Either a b) c -> Parser a m c+ [A] takeEQ :: Monad m => Int -> Fold m a b -> Parser a m b+ [A] takeBetween :: Monad m => Int -> Int -> Fold m a b -> Parser a m b+ [A] subsequenceBy :: (a -> a -> Bool) -> Stream m a -> Parser a m ()+ [A] streamEqBy :: Monad m => (a -> a -> Bool) -> Stream m a -> Parser a m ()+ [A] split_ :: Monad m => Parser x m a -> Parser x m b -> Parser x m b+ [A] splitWith :: Monad m => (a -> b -> c) -> Parser x m a -> Parser x m b -> Parser x m c+ [A] splitSome :: Monad m => Parser a m b -> Fold m b c -> Parser a m c+ [A] splitManyPost :: Monad m => Parser a m b -> Fold m b c -> Parser a m c+ [A] splitMany :: Monad m => Parser a m b -> Fold m b c -> Parser a m c+ [A] spanByRolling :: Monad m => (a -> a -> Bool) -> Fold m a b -> Fold m a c -> Parser a m (b, c)+ [A] spanBy :: Monad m => (a -> a -> Bool) -> Fold m a b -> Fold m a c -> Parser a m (b, c)+ [A] span :: Monad m => (a -> Bool) -> Fold m a b -> Fold m a c -> Parser a m (b, c)+ [A] some :: Monad m => Parser a m b -> Fold m b c -> Parser a m c+ [A] sequence :: Monad m => Stream m (Parser a m b) -> Fold m b c -> Parser a m c+ [A] sepByAll :: Monad m => Parser a m b -> Parser a m x -> Fold m b c -> Parser a m c+ [A] sepBy1 :: Monad m => Parser a m b -> Parser a m x -> Fold m b c -> Parser a m c+ [A] sepBy :: Monad m => Parser a m b -> Parser a m x -> Fold m b c -> Parser a m c+ [A] satisfy :: Monad m => (a -> Bool) -> Parser a m a+ [A] sampleFromthen :: Monad m => Int -> Int -> Fold m a b -> Parser a m b+ [A] roundRobin :: t (Parser a m b) -> Fold m b c -> Parser a m c+ [A] rmapM :: Monad m => (b -> m c) -> Parser a m b -> Parser a m c+ [A] retryMaxTotal :: Int -> Parser a m b -> Fold m b c -> Parser a m c+ [A] retryMaxSuccessive :: Int -> Parser a m b -> Fold m b c -> Parser a m c+ [A] retry :: Parser a m b -> Parser a m b+ [A] postscan :: Fold m a b -> Parser b m c -> Parser a m c+ [A] peek :: Monad m => Parser a m a+ [A] oneOf :: (Monad m, Eq a, Foldable f) => f a -> Parser a m a+ [A] oneNotEq :: (Monad m, Eq a) => a -> Parser a m a+ [A] oneEq :: (Monad m, Eq a) => a -> Parser a m a+ [A] one :: Monad m => Parser a m a+ [A] noneOf :: (Monad m, Eq a, Foldable f) => f a -> Parser a m a+ [A] noErrorUnsafeSplit_ :: Monad m => Parser x m a -> Parser x m b -> Parser x m b+ [A] noErrorUnsafeSplitWith :: Monad m => (a -> b -> c) -> Parser x m a -> Parser x m b -> Parser x m c+ [A] noErrorUnsafeConcatMap :: Monad m => (b -> Parser a m c) -> Parser a m b -> Parser a m c+ [A] maybe :: Monad m => (a -> Maybe b) -> Parser a m b+ [A] manyTillP :: Parser a m b -> Parser a m x -> Parser b m c -> Parser a m c+ [A] manyTill :: Monad m => Parser a m b -> Parser a m x -> Fold m b c -> Parser a m c+ [A] manyThen :: Parser a m b -> Parser a m x -> Fold m b c -> Parser a m c+ [A] manyP :: Parser a m b -> Parser b m c -> Parser a m c+ [A] many :: Monad m => Parser a m b -> Fold m b c -> Parser a m c+ [A] makeIndexFilter :: (Fold m (s, a) b -> Parser a m b) -> (((s, a) -> Bool) -> Fold m (s, a) b -> Fold m (s, a) b) -> ((s, a) -> Bool) -> Fold m a b -> Parser a m b+ [A] lookAhead :: Monad m => Parser a m b -> Parser a m b+ [A] lmapM :: Monad m => (a -> m b) -> Parser b m r -> Parser a m r+ [A] lmap :: (a -> b) -> Parser b m r -> Parser a m r+ [A] listEqBy :: Monad m => (a -> a -> Bool) -> [a] -> Parser a m [a]+ [A] listEq :: (Monad m, Eq a) => [a] -> Parser a m [a]+ [A] indexed :: forall m a b. Monad m => Fold m (Int, a) b -> Parser a m b+ [A] groupByRollingEither :: Monad m => (a -> a -> Bool) -> Fold m a b -> Fold m a c -> Parser a m (Either b c)+ [A] groupByRolling :: Monad m => (a -> a -> Bool) -> Fold m a b -> Parser a m b+ [A] groupBy :: Monad m => (a -> a -> Bool) -> Fold m a b -> Parser a m b+ [A] fromPure :: Monad m => b -> Parser a m b+ [A] fromFoldMaybe :: Monad m => String -> Fold m a (Maybe b) -> Parser a m b+ [A] fromFold :: Monad m => Fold m a b -> Parser a m b+ [A] fromEffect :: Monad m => m b -> Parser a m b+ [A] filter :: Monad m => (a -> Bool) -> Parser a m b -> Parser a m b+ [A] extractStep :: Monad m => (s -> m (Step s1 b)) -> Step s b -> m (Step s1 b)+ [A] eof :: Monad m => Parser a m ()+ [A] either :: Monad m => (a -> Either String b) -> Parser a m b+ [A] dropWhile :: Monad m => (a -> Bool) -> Parser a m ()+ [A] dieM :: Monad m => m String -> Parser a m b+ [A] die :: Monad m => String -> Parser a m b+ [A] deintercalateAll :: Monad m => Parser a m x -> Parser a m y -> Fold m (Either x y) z -> Parser a m z+ [A] deintercalate1 :: Monad m => Parser a m x -> Parser a m y -> Fold m (Either x y) z -> Parser a m z+ [A] deintercalate :: Monad m => Parser a m x -> Parser a m y -> Fold m (Either x y) z -> Parser a m z+ [A] countBetween :: Int -> Int -> Parser a m b -> Fold m b c -> Parser a m c+ [A] count :: Int -> Parser a m b -> Fold m b c -> Parser a m c+ [A] concatMap :: Monad m => (b -> Parser a m c) -> Parser a m b -> Parser a m c+ [A] blockWithQuotes :: (Monad m, Eq a) => (a -> Bool) -> (a -> Bool) -> a -> a -> Fold m a b -> Parser a m b+ [A] bimapOverrideCount :: Int -> (s -> s1) -> (b -> b1) -> Step s b -> Step s1 b1+ [A] alt :: Monad m => Parser x m a -> Parser x m a -> Parser x m a+[A] Streamly.Internal.Data.MutByteArray+ [A] class Unbox a+ [A] class SizeOfRep (f :: Type -> Type)+ [A] class Serialize a+ [A] TypeOfType+ [A] UnitType :: Name -> TypeOfType+ [A] TheType :: SimpleDataCon -> TypeOfType+ [A] MultiType :: [SimpleDataCon] -> TypeOfType+ [A] SimpleDataCon+ [A] SimpleDataCon :: Name -> [Field] -> SimpleDataCon+ [A] SerializeConfig+ [A] [cfgRecordSyntaxWithHeader] :: SerializeConfig -> Bool+ [A] [cfgInlineSize] :: SerializeConfig -> Maybe Inline+ [A] [cfgInlineSerialize] :: SerializeConfig -> Maybe Inline+ [A] [cfgInlineDeserialize] :: SerializeConfig -> Maybe Inline+ [A] [cfgConstructorTagAsString] :: SerializeConfig -> Bool+ [A] SerializeConfig :: Maybe Inline -> Maybe Inline -> Maybe Inline -> Bool -> Bool -> SerializeConfig+ [A] PinnedState+ [A] Unpinned :: PinnedState+ [A] Pinned :: PinnedState+ [A] MutByteArray+ [A] MutByteArray :: MutableByteArray# RealWorld -> MutByteArray+ [A] DataType+ [A] [dtTvs] :: DataType -> [Name]+ [A] [dtName] :: DataType -> Name+ [A] [dtCxt] :: DataType -> Cxt+ [A] [dtCons] :: DataType -> [DataCon]+ [A] DataType :: Name -> [Name] -> Cxt -> [DataCon] -> DataType+ [A] DataCon+ [A] [dcTvs] :: DataCon -> [Name]+ [A] [dcName] :: DataCon -> Name+ [A] [dcFields] :: DataCon -> [(Maybe Name, Type)]+ [A] [dcCxt] :: DataCon -> Cxt+ [A] DataCon :: Name -> [Name] -> Cxt -> [(Maybe Name, Type)] -> DataCon+ [A] BoundedPtr+ [A] BoundedPtr :: MutByteArray -> Int -> Int -> BoundedPtr+ [A] Streamly.Internal.Data.Serialize.Type.Serialize+ [A] instance Streamly.Internal.Data.Serialize.Type.Serialize Streamly.Internal.Data.MutByteArray.LiftedInteger+ [A] instance Streamly.Internal.Data.Serialize.Type.Serialize GHC.Num.Integer.Integer+ [A] instance Streamly.Internal.Data.Serialize.Type.Serialize a => Streamly.Internal.Data.Serialize.Type.Serialize (GHC.Maybe.Maybe a)+ [A] instance Streamly.Internal.Data.Serialize.Type.Serialize (Data.Proxy.Proxy a)+ [A] instance (Streamly.Internal.Data.Serialize.Type.Serialize a, Streamly.Internal.Data.Serialize.Type.Serialize b) => Streamly.Internal.Data.Serialize.Type.Serialize (Data.Either.Either a b)+ [A] Peeker+ [A] Peeker :: Builder BoundedPtr IO a -> Peeker a+ [A] type Field = (Maybe Name, Type)+ [A] xorCmp :: [Word8] -> Name -> Name -> Q Exp+ [A] wListToString :: [Word8] -> String+ [A] w8_int :: Word8 -> Int+ [A] w32_int :: Word32 -> Int+ [A] unpin :: MutByteArray -> IO MutByteArray+ [A] typeOfType :: Type -> [DataCon] -> TypeOfType+ [A] skipByte :: Peeker ()+ [A] sizeOfRep :: SizeOfRep f => f x -> Int+ [A] sizeOfMutableByteArray :: MutByteArray -> IO Int+ [A] sizeOf :: (Unbox a, SizeOfRep (Rep a)) => Proxy a -> Int+ [A] simplifyDataCon :: DataCon -> SimpleDataCon+ [A] serializeW8List :: Name -> Name -> [Word8] -> Q Exp+ [A] serializeConfig :: SerializeConfig+ [A] serializeAt :: Serialize a => Int -> MutByteArray -> a -> IO Int+ [A] runPeeker :: Peeker a -> BoundedPtr -> IO a+ [A] reifyDataType :: Name -> Q DataType+ [A] readUnsafe :: Unbox a => Peeker a+ [A] read :: Unbox a => Peeker a+ [A] putSliceUnsafe :: MonadIO m => MutByteArray -> Int -> MutByteArray -> Int -> Int -> m ()+ [A] pokeRep :: PokeRep f => f a -> BoundedPtr -> IO BoundedPtr+ [A] pokeBoundedPtrUnsafe :: forall a. Unbox a => a -> BoundedPtr -> IO BoundedPtr+ [A] pokeBoundedPtr :: forall a. Unbox a => a -> BoundedPtr -> IO BoundedPtr+ [A] pokeAt :: (Unbox a, Generic a, PokeRep (Rep a)) => Int -> MutByteArray -> a -> IO ()+ [A] pinnedNewAlignedBytes :: Int -> Int -> IO MutByteArray+ [A] pinnedNew :: Int -> IO MutByteArray+ [A] pinnedCloneSliceUnsafe :: MonadIO m => Int -> Int -> MutByteArray -> m MutByteArray+ [A] pin :: MutByteArray -> IO MutByteArray+ [A] peekRep :: PeekRep f => Peeker (f x)+ [A] peekAt :: (Unbox a, Generic a, PeekRep (Rep a)) => Int -> MutByteArray -> IO a+ [A] openConstructor :: Name -> Int -> Q Pat+ [A] nil :: MutByteArray+ [A] newBytesAs :: PinnedState -> Int -> IO MutByteArray+ [A] new :: Int -> IO MutByteArray+ [A] mkSerializeExprFields :: Name -> [Field] -> Q Exp+ [A] mkRecSizeOfExpr :: SimpleDataCon -> Q Exp+ [A] mkRecSerializeExpr :: Name -> SimpleDataCon -> Q Exp+ [A] mkRecDeserializeExpr :: Name -> Name -> Name -> SimpleDataCon -> Q Exp+ [A] mkFieldName :: Int -> Name+ [A] mkDeserializeKeysDec :: Name -> Name -> SimpleDataCon -> Q [Dec]+ [A] mkDeserializeExprOne :: Name -> SimpleDataCon -> Q Exp+ [A] matchConstructor :: Name -> Int -> Q Exp -> Q Match+ [A] makeN :: Int -> Name+ [A] makeI :: Int -> Name+ [A] makeA :: Int -> Name+ [A] litProxy :: Unbox a => Proxy a -> Q Exp+ [A] litIntegral :: Integral a => a -> Q Exp+ [A] isUnitType :: [DataCon] -> Bool+ [A] isRecordSyntax :: SimpleDataCon -> Bool+ [A] isPinned :: MutByteArray -> Bool+ [A] int_w8 :: Int -> Word8+ [A] int_w32 :: Int -> Word32+ [A] inlineSerializeAt :: Maybe Inline -> SerializeConfig -> SerializeConfig+ [A] inlineDeserializeAt :: Maybe Inline -> SerializeConfig -> SerializeConfig+ [A] inlineAddSizeTo :: Maybe Inline -> SerializeConfig -> SerializeConfig+ [A] getMutableByteArray# :: MutByteArray -> MutableByteArray# RealWorld+ [A] genericSizeOf :: forall a. SizeOfRep (Rep a) => Proxy a -> Int+ [A] genericPokeByteIndex :: (Generic a, PokeRep (Rep a)) => MutByteArray -> Int -> a -> IO ()+ [A] genericPeekByteIndex :: (Generic a, PeekRep (Rep a)) => MutByteArray -> Int -> IO a+ [A] errorUnsupported :: String -> a+ [A] errorUnimplemented :: a+ [A] encodeRecordFields :: Bool -> SerializeConfig -> SerializeConfig+ [A] encodeConstrNames :: Bool -> SerializeConfig -> SerializeConfig+ [A] deserializeAt :: Serialize a => Int -> MutByteArray -> Int -> IO (Int, a)+ [A] deriveUnbox :: Q [Dec] -> Q [Dec]+ [A] deriveSerializeWith :: (SerializeConfig -> SerializeConfig) -> Q [Dec] -> Q [Dec]+ [A] deriveSerialize :: Q [Dec] -> Q [Dec]+ [A] conUpdateFuncDec :: Name -> [Field] -> Q [Dec]+ [A] cloneSliceUnsafeAs :: MonadIO m => PinnedState -> Int -> Int -> MutByteArray -> m MutByteArray+ [A] cloneSliceUnsafe :: MonadIO m => Int -> Int -> MutByteArray -> m MutByteArray+ [A] c2w :: Char -> Word8+ [A] asPtrUnsafe :: MonadIO m => MutByteArray -> (Ptr a -> m b) -> m b+ [A] addSizeTo :: Serialize a => Int -> a -> Int+ [A] _x :: Name+ [A] _val :: Name+ [A] _tag :: Name+ [A] _initialOffset :: Name+ [A] _endOffset :: Name+ [A] _arr :: Name+ [A] _acc :: Name+[A] Streamly.Internal.Data.MutArray.Stream+ [A] SpliceState+ [A] SpliceYielding :: arr -> SpliceState s arr -> SpliceState s arr+ [A] SpliceInitial :: s -> SpliceState s arr+ [A] SpliceFinish :: SpliceState s arr+ [A] SpliceBuffering :: s -> arr -> SpliceState s arr+ [A] writeChunks :: (MonadIO m, Unbox a) => Int -> Fold m a (StreamK n (MutArray a))+ [A] splitOn :: (MonadIO m, Unbox a) => (a -> Bool) -> MutArray a -> Stream m (MutArray a)+ [A] pinnedChunksOf :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> Stream m (MutArray a)+ [A] packArraysChunksOf :: (MonadIO m, Unbox a) => Int -> Stream m (MutArray a) -> Stream m (MutArray a)+ [A] lpackArraysChunksOf :: (MonadIO m, Unbox a) => Int -> Fold m (MutArray a) () -> Fold m (MutArray a) ()+ [A] fromArrayStreamK :: (Unbox a, MonadIO m) => StreamK m (MutArray a) -> m (MutArray a)+ [A] flattenArraysRev :: forall m a. (MonadIO m, Unbox a) => Stream m (MutArray a) -> Stream m a+ [A] flattenArrays :: forall m a. (MonadIO m, Unbox a) => Stream m (MutArray a) -> Stream m a+ [A] compactLE :: (MonadIO m, Unbox a) => Int -> Stream m (MutArray a) -> Stream m (Either ParseError (MutArray a))+ [A] compactGE :: (MonadIO m, Unbox a) => Int -> Stream m (MutArray a) -> Stream m (MutArray a)+ [A] compactEQ :: Int -> Stream m (MutArray a) -> Stream m (MutArray a)+ [A] compact :: (MonadIO m, Unbox a) => Int -> Stream m (MutArray a) -> Stream m (MutArray a)+ [A] chunksOf :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> Stream m (MutArray a)+[A] Streamly.Internal.Data.MutArray.Generic+ [A] MutArray+ [A] [arrTrueLen] :: MutArray a -> {-# UNPACK #-} !Int+ [A] [arrStart] :: MutArray a -> {-# UNPACK #-} !Int+ [A] [arrLen] :: MutArray a -> {-# UNPACK #-} !Int+ [A] [arrContents#] :: MutArray a -> MutableArray# RealWorld a+ [A] MutArray :: MutableArray# RealWorld a -> {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> MutArray a+ [A] writeWith :: MonadIO m => Int -> Fold m a (MutArray a)+ [A] writeNUnsafe :: MonadIO m => Int -> Fold m a (MutArray a)+ [A] writeN :: MonadIO m => Int -> Fold m a (MutArray a)+ [A] write :: MonadIO m => Fold m a (MutArray a)+ [A] uninit :: MonadIO m => MutArray a -> Int -> m (MutArray a)+ [A] toStreamK :: MonadIO m => MutArray a -> StreamK m a+ [A] toList :: MonadIO m => MutArray a -> m [a]+ [A] strip :: MonadIO m => (a -> Bool) -> MutArray a -> m (MutArray a)+ [A] snocWith :: MonadIO m => (Int -> Int) -> MutArray a -> a -> m (MutArray a)+ [A] snocUnsafe :: MonadIO m => MutArray a -> a -> m (MutArray a)+ [A] snoc :: MonadIO m => MutArray a -> a -> m (MutArray a)+ [A] realloc :: MonadIO m => Int -> MutArray a -> m (MutArray a)+ [A] reader :: MonadIO m => Unfold m (MutArray a) a+ [A] readRev :: MonadIO m => MutArray a -> Stream m a+ [A] read :: MonadIO m => MutArray a -> Stream m a+ [A] putSliceUnsafe :: MonadIO m => MutArray a -> Int -> MutArray a -> Int -> Int -> m ()+ [A] putIndices :: MonadIO m => MutArray a -> Fold m (Int, a) ()+ [A] putIndexUnsafe :: forall m a. MonadIO m => Int -> MutArray a -> a -> m ()+ [A] putIndex :: MonadIO m => Int -> MutArray a -> a -> m ()+ [A] producerWith :: Monad m => (forall b. IO b -> m b) -> Producer m (MutArray a) a+ [A] producer :: MonadIO m => Producer m (MutArray a) a+ [A] nil :: MonadIO m => m (MutArray a)+ [A] new :: MonadIO m => Int -> m (MutArray a)+ [A] modifyIndexUnsafe :: MonadIO m => Int -> MutArray a -> (a -> (a, b)) -> m b+ [A] modifyIndex :: MonadIO m => Int -> MutArray a -> (a -> (a, b)) -> m b+ [A] length :: MutArray a -> Int+ [A] getSliceUnsafe :: Int -> Int -> MutArray a -> MutArray a+ [A] getSlice :: Int -> Int -> MutArray a -> MutArray a+ [A] getIndexUnsafeWith :: MonadIO m => MutableArray# RealWorld a -> Int -> m a+ [A] getIndexUnsafe :: MonadIO m => Int -> MutArray a -> m a+ [A] getIndex :: MonadIO m => Int -> MutArray a -> m (Maybe a)+ [A] fromStreamN :: MonadIO m => Int -> Stream m a -> m (MutArray a)+ [A] fromStream :: MonadIO m => Stream m a -> m (MutArray a)+ [A] fromPureStream :: MonadIO m => Stream Identity a -> m (MutArray a)+ [A] fromListN :: MonadIO m => Int -> [a] -> m (MutArray a)+ [A] fromList :: MonadIO m => [a] -> m (MutArray a)+ [A] eq :: (MonadIO m, Eq a) => MutArray a -> MutArray a -> m Bool+ [A] cmp :: (MonadIO m, Ord a) => MutArray a -> MutArray a -> m Ordering+ [A] clone :: MonadIO m => MutArray a -> m (MutArray a)+ [A] chunksOf :: forall m a. MonadIO m => Int -> Stream m a -> Stream m (MutArray a)+[A] Streamly.Internal.Data.MutArray+ [A] MutByteArray+ [A] MutArray+ [A] [arrStart] :: MutArray a -> {-# UNPACK #-} !Int+ [A] [arrEnd] :: MutArray a -> {-# UNPACK #-} !Int+ [A] [arrContents] :: MutArray a -> {-# UNPACK #-} !MutByteArray+ [A] [arrBound] :: MutArray a -> {-# UNPACK #-} !Int+ [A] MutArray :: {-# UNPACK #-} !MutByteArray -> {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> MutArray a+ [A] IORef+ [A] ArrayUnsafe+ [A] ArrayUnsafe :: {-# UNPACK #-} !MutByteArray -> {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> ArrayUnsafe a+ [A] writeWith :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [A] writeRevN :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [A] writeNWithUnsafe :: forall m a. (MonadIO m, Unbox a) => (Int -> m (MutArray a)) -> Int -> Fold m a (MutArray a)+ [A] writeNWith :: forall m a. (MonadIO m, Unbox a) => (Int -> m (MutArray a)) -> Int -> Fold m a (MutArray a)+ [A] writeNUnsafe :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [A] writeN :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [A] writeIORef :: Unbox a => IORef a -> a -> IO ()+ [A] writeChunks :: (MonadIO m, Unbox a) => Int -> Fold m a (StreamK n (MutArray a))+ [A] writeAppendWith :: forall m a. (MonadIO m, Unbox a) => (Int -> Int) -> m (MutArray a) -> Fold m a (MutArray a)+ [A] writeAppendNUnsafe :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a) -> Fold m a (MutArray a)+ [A] writeAppendN :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a) -> Fold m a (MutArray a)+ [A] writeAppend :: forall m a. (MonadIO m, Unbox a) => m (MutArray a) -> Fold m a (MutArray a)+ [A] write :: forall m a. (MonadIO m, Unbox a) => Fold m a (MutArray a)+ [A] unsafeSwapIndices :: forall m a. (MonadIO m, Unbox a) => Int -> Int -> MutArray a -> m ()+ [A] unpin :: MutArray a -> IO (MutArray a)+ [A] toStreamKWith :: forall m a. (Monad m, Unbox a) => (forall b. IO b -> m b) -> MutArray a -> StreamK m a+ [A] toStreamKRevWith :: forall m a. (Monad m, Unbox a) => (forall b. IO b -> m b) -> MutArray a -> StreamK m a+ [A] toStreamKRev :: forall m a. (MonadIO m, Unbox a) => MutArray a -> StreamK m a+ [A] toStreamK :: forall m a. (MonadIO m, Unbox a) => MutArray a -> StreamK m a+ [A] toStreamDWith :: forall m a. (Monad m, Unbox a) => (forall b. IO b -> m b) -> MutArray a -> Stream m a+ [A] toStreamDRevWith :: forall m a. (Monad m, Unbox a) => (forall b. IO b -> m b) -> MutArray a -> Stream m a+ [A] toList :: forall m a. (MonadIO m, Unbox a) => MutArray a -> m [a]+ [A] swapIndices :: forall m a. (MonadIO m, Unbox a) => Int -> Int -> MutArray a -> m ()+ [A] strip :: forall a m. (Unbox a, MonadIO m) => (a -> Bool) -> MutArray a -> m (MutArray a)+ [A] splitOn :: (MonadIO m, Unbox a) => (a -> Bool) -> MutArray a -> Stream m (MutArray a)+ [A] splitAt :: forall a. Unbox a => Int -> MutArray a -> (MutArray a, MutArray a)+ [A] spliceWith :: forall m a. (MonadIO m, Unbox a) => (Int -> Int -> Int) -> MutArray a -> MutArray a -> m (MutArray a)+ [A] spliceUnsafe :: MonadIO m => MutArray a -> MutArray a -> m (MutArray a)+ [A] spliceExp :: (MonadIO m, Unbox a) => MutArray a -> MutArray a -> m (MutArray a)+ [A] spliceCopy :: forall m a. MonadIO m => MutArray a -> MutArray a -> m (MutArray a)+ [A] splice :: (MonadIO m, Unbox a) => MutArray a -> MutArray a -> m (MutArray a)+ [A] snocWith :: forall m a. (MonadIO m, Unbox a) => (Int -> Int) -> MutArray a -> a -> m (MutArray a)+ [A] snocUnsafe :: forall m a. (MonadIO m, Unbox a) => MutArray a -> a -> m (MutArray a)+ [A] snocMay :: forall m a. (MonadIO m, Unbox a) => MutArray a -> a -> m (Maybe (MutArray a))+ [A] snocLinear :: forall m a. (MonadIO m, Unbox a) => MutArray a -> a -> m (MutArray a)+ [A] snoc :: forall m a. (MonadIO m, Unbox a) => MutArray a -> a -> m (MutArray a)+ [A] shuffleBy :: (a -> a -> m Bool) -> MutArray a -> MutArray a -> m ()+ [A] roundUpToPower2 :: Int -> Int+ [A] rightSize :: forall m a. (MonadIO m, Unbox a) => MutArray a -> m (MutArray a)+ [A] reverse :: forall m a. (MonadIO m, Unbox a) => MutArray a -> m ()+ [A] resizeExp :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m (MutArray a)+ [A] resize :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m (MutArray a)+ [A] realloc :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m (MutArray a)+ [A] readerRevWith :: forall m a. (Monad m, Unbox a) => (forall b. IO b -> m b) -> Unfold m (MutArray a) a+ [A] readerRev :: forall m a. (MonadIO m, Unbox a) => Unfold m (MutArray a) a+ [A] reader :: forall m a. (MonadIO m, Unbox a) => Unfold m (MutArray a) a+ [A] readRev :: forall m a. (MonadIO m, Unbox a) => MutArray a -> Stream m a+ [A] readIORef :: Unbox a => IORef a -> IO a+ [A] read :: forall m a. (MonadIO m, Unbox a) => MutArray a -> Stream m a+ [A] putIndices :: forall m a. (MonadIO m, Unbox a) => MutArray a -> Fold m (Int, a) ()+ [A] putIndexUnsafe :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> a -> m ()+ [A] putIndex :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> a -> m ()+ [A] producerWith :: forall m a. (Monad m, Unbox a) => (forall b. IO b -> m b) -> Producer m (MutArray a) a+ [A] producer :: forall m a. (MonadIO m, Unbox a) => Producer m (MutArray a) a+ [A] pollIntIORef :: (MonadIO m, Unbox a) => IORef a -> Stream m a+ [A] pinnedWriteNUnsafe :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [A] pinnedWriteNAligned :: forall m a. (MonadIO m, Unbox a) => Int -> Int -> Fold m a (MutArray a)+ [A] pinnedWriteN :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [A] pinnedWrite :: forall m a. (MonadIO m, Unbox a) => Fold m a (MutArray a)+ [A] pinnedNewBytes :: MonadIO m => Int -> m (MutArray a)+ [A] pinnedNewAligned :: (MonadIO m, Unbox a) => Int -> Int -> m (MutArray a)+ [A] pinnedNew :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a)+ [A] pinnedFromListN :: (MonadIO m, Unbox a) => Int -> [a] -> m (MutArray a)+ [A] pinnedFromList :: (MonadIO m, Unbox a) => [a] -> m (MutArray a)+ [A] pinnedClone :: MonadIO m => MutArray a -> m (MutArray a)+ [A] pinnedChunksOf :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> Stream m (MutArray a)+ [A] pin :: MutArray a -> IO (MutArray a)+ [A] permute :: MutArray a -> m Bool+ [A] partitionBy :: forall m a. (MonadIO m, Unbox a) => (a -> Bool) -> MutArray a -> m (MutArray a, MutArray a)+ [A] nil :: MutArray a+ [A] newIORef :: forall a. Unbox a => a -> IO (IORef a)+ [A] newArrayWith :: forall m a. (MonadIO m, Unbox a) => (Int -> Int -> m MutByteArray) -> Int -> Int -> m (MutArray a)+ [A] new :: (MonadIO m, Unbox a) => Int -> m (MutArray a)+ [A] modifyIndices :: forall m a. (MonadIO m, Unbox a) => MutArray a -> (Int -> a -> a) -> Fold m Int ()+ [A] modifyIndexUnsafe :: forall m a b. (MonadIO m, Unbox a) => Int -> MutArray a -> (a -> (a, b)) -> m b+ [A] modifyIndex :: forall m a b. (MonadIO m, Unbox a) => Int -> MutArray a -> (a -> (a, b)) -> m b+ [A] modifyIORef' :: Unbox a => IORef a -> (a -> a) -> IO ()+ [A] modify :: forall m a. (MonadIO m, Unbox a) => MutArray a -> (a -> a) -> m ()+ [A] mergeBy :: Int -> (MutArray a -> MutArray a -> m ()) -> MutArray a -> m ()+ [A] memcpy :: Ptr Word8 -> Ptr Word8 -> Int -> IO ()+ [A] memcmp :: Ptr Word8 -> Ptr Word8 -> Int -> IO Bool+ [A] length :: forall a. Unbox a => MutArray a -> Int+ [A] isPinned :: MutArray a -> Bool+ [A] getSlicesFromLen :: forall m a. (Monad m, Unbox a) => Int -> Int -> Unfold m (MutArray a) (MutArray a)+ [A] getSliceUnsafe :: forall a. Unbox a => Int -> Int -> MutArray a -> MutArray a+ [A] getSlice :: forall a. Unbox a => Int -> Int -> MutArray a -> MutArray a+ [A] getIndicesD :: (Monad m, Unbox a) => (forall b. IO b -> m b) -> Stream m Int -> Unfold m (MutArray a) a+ [A] getIndices :: (MonadIO m, Unbox a) => Stream m Int -> Unfold m (MutArray a) a+ [A] getIndexUnsafe :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m a+ [A] getIndexRev :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m a+ [A] getIndex :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m (Maybe a)+ [A] genSlicesFromLen :: forall m a. (Monad m, Unbox a) => Int -> Int -> Unfold m (MutArray a) (Int, Int)+ [A] fromStreamDN :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> m (MutArray a)+ [A] fromStreamD :: (MonadIO m, Unbox a) => Stream m a -> m (MutArray a)+ [A] fromStream :: (MonadIO m, Unbox a) => Stream m a -> m (MutArray a)+ [A] fromPureStream :: (MonadIO m, Unbox a) => Stream Identity a -> m (MutArray a)+ [A] fromListRevN :: (MonadIO m, Unbox a) => Int -> [a] -> m (MutArray a)+ [A] fromListRev :: (MonadIO m, Unbox a) => [a] -> m (MutArray a)+ [A] fromListN :: (MonadIO m, Unbox a) => Int -> [a] -> m (MutArray a)+ [A] fromList :: (MonadIO m, Unbox a) => [a] -> m (MutArray a)+ [A] fromArrayStreamK :: (Unbox a, MonadIO m) => StreamK m (MutArray a) -> m (MutArray a)+ [A] foldr :: (MonadIO m, Unbox a) => (a -> b -> b) -> b -> MutArray a -> m b+ [A] foldl' :: (MonadIO m, Unbox a) => (b -> a -> b) -> b -> MutArray a -> m b+ [A] flattenArraysRev :: forall m a. (MonadIO m, Unbox a) => Stream m (MutArray a) -> Stream m a+ [A] flattenArrays :: forall m a. (MonadIO m, Unbox a) => Stream m (MutArray a) -> Stream m a+ [A] divideBy :: Int -> (MutArray a -> m (MutArray a, MutArray a)) -> MutArray a -> m ()+ [A] cmp :: MonadIO m => MutArray a -> MutArray a -> m Ordering+ [A] clone :: MonadIO m => MutArray a -> m (MutArray a)+ [A] chunksOf :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> Stream m (MutArray a)+ [A] castUnsafe :: MutArray a -> MutArray b+ [A] cast :: forall a b. Unbox b => MutArray a -> Maybe (MutArray b)+ [A] c_memchr :: Ptr Word8 -> Word8 -> CSize -> IO (Ptr Word8)+ [A] bytesFree :: MutArray a -> Int+ [A] byteLength :: MutArray a -> Int+ [A] byteCapacity :: MutArray a -> Int+ [A] bubble :: (MonadIO m, Unbox a) => (a -> a -> Ordering) -> MutArray a -> m ()+ [A] breakOn :: MonadIO m => Word8 -> MutArray Word8 -> m (MutArray Word8, Maybe (MutArray Word8))+ [A] blockSize :: Int+ [A] asPtrUnsafe :: MonadIO m => MutArray a -> (Ptr a -> m b) -> m b+ [A] asBytes :: MutArray a -> MutArray Word8+ [A] arrayChunkBytes :: Int+ [A] allocBytesToElemCount :: Unbox a => a -> Int -> Int+[C] Streamly.Internal.Data.IsMap+ [A] mapTraverseWithKey :: (IsMap f, Applicative t) => (Key f -> a -> t b) -> f a -> t (f b)+[R] Streamly.Internal.Data.IORef.Unboxed+[R] Streamly.Internal.Data.Fold.Window+[R] Streamly.Internal.Data.Fold.Type+[R] Streamly.Internal.Data.Fold.Tee+[R] Streamly.Internal.Data.Fold.Step+[R] Streamly.Internal.Data.Fold.Container+[D] Streamly.Internal.Data.Fold.Chunked+[C] Streamly.Internal.Data.Fold+ [A] ManyState+ [C] Fold+ [C] Fold+ [O] Fold :: (s -> a -> m (Step s b)) -> m (Step s b) -> (s -> m b) -> Fold m a b+ [N] Fold :: (s -> a -> m (Step s b)) -> m (Step s b) -> (s -> m b) -> (s -> m b) -> Fold m a b+ [A] windowSumInt :: forall m a. (Monad m, Integral a) => Fold m (a, Maybe a) a+ [A] windowSum :: forall m a. (Monad m, Num a) => Fold m (a, Maybe a) a+ [A] windowRollingMapM :: Monad m => (Maybe a -> a -> m (Maybe b)) -> Fold m (a, Maybe a) (Maybe b)+ [A] windowRollingMap :: Monad m => (Maybe a -> a -> Maybe b) -> Fold m (a, Maybe a) (Maybe b)+ [A] windowRange :: (MonadIO m, Storable a, Ord a) => Int -> Fold m a (Maybe (a, a))+ [A] windowPowerSumFrac :: (Monad m, Floating a) => a -> Fold m (a, Maybe a) a+ [A] windowPowerSum :: (Monad m, Num a) => Int -> Fold m (a, Maybe a) a+ [A] windowMinimum :: (MonadIO m, Storable a, Ord a) => Int -> Fold m a (Maybe a)+ [A] windowMean :: forall m a. (Monad m, Fractional a) => Fold m (a, Maybe a) a+ [A] windowMaximum :: (MonadIO m, Storable a, Ord a) => Int -> Fold m a (Maybe a)+ [A] windowLmap :: (c -> a) -> Fold m (a, Maybe a) b -> Fold m (c, Maybe c) b+ [A] windowLength :: (Monad m, Num b) => Fold m (a, Maybe a) b+ [A] toSet :: (Monad m, Ord a) => Fold m a (Set a)+ [A] toMapIO :: (MonadIO m, Ord k) => (a -> k) -> Fold m a b -> Fold m a (Map k b)+ [A] toMap :: (Monad m, Ord k) => (a -> k) -> Fold m a b -> Fold m a (Map k b)+ [A] toIntSet :: Monad m => Fold m Int IntSet+ [A] toContainerIO :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) => (a -> Key f) -> Fold m a b -> Fold m a (f b)+ [A] toContainer :: (Monad m, IsMap f, Traversable f, Ord (Key f)) => (a -> Key f) -> Fold m a b -> Fold m a (f b)+ [A] nubInt :: Monad m => Fold m Int (Maybe Int)+ [A] nub :: (Monad m, Ord a) => Fold m a (Maybe a)+ [A] mapMStep :: Applicative m => (a -> m b) -> Step s a -> m (Step s b)+ [A] kvToMap :: (Monad m, Ord k) => Fold m a b -> Fold m (k, a) (Map k b)+ [A] frequency :: (Monad m, Ord a) => Fold m a (Map a Int)+ [A] demuxToMapIO :: (MonadIO m, Ord k) => (a -> k) -> (a -> m (Fold m a b)) -> Fold m a (Map k b)+ [A] demuxToMap :: (Monad m, Ord k) => (a -> k) -> (a -> m (Fold m a b)) -> Fold m a (Map k b)+ [A] demuxToContainerIO :: (MonadIO m, IsMap f, Traversable f) => (a -> Key f) -> (a -> m (Fold m a b)) -> Fold m a (f b)+ [A] demuxToContainer :: (Monad m, IsMap f, Traversable f) => (a -> Key f) -> (a -> m (Fold m a b)) -> Fold m a (f b)+ [A] demuxKvToMap :: (Monad m, Ord k) => (k -> m (Fold m a b)) -> Fold m (k, a) (Map k b)+ [A] demuxKvToContainer :: (Monad m, IsMap f, Traversable f) => (Key f -> m (Fold m a b)) -> Fold m (Key f, a) (f b)+ [A] demuxIO :: (MonadIO m, Ord k) => (a -> k) -> (a -> m (Fold m a b)) -> Fold m a (m (Map k b), Maybe (k, b))+ [A] demuxGenericIO :: (MonadIO m, IsMap f, Traversable f) => (a -> Key f) -> (a -> m (Fold m a b)) -> Fold m a (m (f b), Maybe (Key f, b))+ [A] demuxGeneric :: (Monad m, IsMap f, Traversable f) => (a -> Key f) -> (a -> m (Fold m a b)) -> Fold m a (m (f b), Maybe (Key f, b))+ [A] demux :: (Monad m, Ord k) => (a -> k) -> (a -> m (Fold m a b)) -> Fold m a (m (Map k b), Maybe (k, b))+ [A] cumulative :: Fold m (a, Maybe a) b -> Fold m a b+ [A] countDistinctInt :: Monad m => Fold m Int Int+ [A] countDistinct :: (Monad m, Ord a) => Fold m a Int+ [A] classifyIO :: (MonadIO m, Ord k) => (a -> k) -> Fold m a b -> Fold m a (m (Map k b), Maybe (k, b))+ [A] classifyGenericIO :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) => (a -> Key f) -> Fold m a b -> Fold m a (m (f b), Maybe (Key f, b))+ [A] classifyGeneric :: (Monad m, IsMap f, Traversable f, Ord (Key f)) => (a -> Key f) -> Fold m a b -> Fold m a (m (f b), Maybe (Key f, b))+ [A] classify :: (Monad m, Ord k) => (a -> k) -> Fold m a b -> Fold m a (m (Map k b), Maybe (k, b))+ [A] chainStepM :: Applicative m => (s1 -> m s2) -> (a -> m (Step s2 b)) -> Step s1 a -> m (Step s2 b)+[C] Streamly.Internal.Data.Builder+ [C] Builder+ [C] Builder+ [O] Builder :: (s -> m (s, a)) -> Builder s m a+ [N] Builder :: (s -> m (a, s)) -> Builder s m a+[A] Streamly.Internal.Data.Binary.Stream+ [A] class ToBytes a+ [A] word8 :: Applicative m => Word8 -> Stream m Word8+ [A] word64le :: Monad m => Word64 -> Stream m Word8+ [A] word64host :: Monad m => Word64 -> Stream m Word8+ [A] word64be :: Monad m => Word64 -> Stream m Word8+ [A] word32le :: Monad m => Word32 -> Stream m Word8+ [A] word32be :: Monad m => Word32 -> Stream m Word8+ [A] word16le :: Monad m => Word16 -> Stream m Word8+ [A] word16be :: Monad m => Word16 -> Stream m Word8+ [A] unit :: Applicative m => Stream m Word8+ [A] toBytes :: ToBytes a => a -> Stream m Word8+ [A] ordering :: Applicative m => Ordering -> Stream m Word8+ [A] int8 :: Applicative m => Int8 -> Stream m Word8+ [A] int64le :: Monad m => Int64 -> Stream m Word8+ [A] int64be :: Monad m => Int64 -> Stream m Word8+ [A] int32le :: Monad m => Int32 -> Stream m Word8+ [A] int32be :: Monad m => Int32 -> Stream m Word8+ [A] int16le :: Monad m => Int16 -> Stream m Word8+ [A] int16be :: Monad m => Int16 -> Stream m Word8+ [A] float32le :: Monad m => Float -> Stream m Word8+ [A] float32be :: Monad m => Float -> Stream m Word8+ [A] double64le :: Monad m => Double -> Stream m Word8+ [A] double64be :: Monad m => Double -> Stream m Word8+ [A] charUtf8 :: Monad m => Char -> Stream m Word8+ [A] charLatin1 :: Applicative m => Char -> Stream m Word8+ [A] bool :: Applicative m => Bool -> Stream m Word8+[A] Streamly.Internal.Data.Binary.Parser+ [A] class FromBytes a+ [A] word8 :: Monad m => Parser Word8 m Word8+ [A] word64le :: Monad m => Parser Word8 m Word64+ [A] word64host :: MonadIO m => Parser Word8 m Word64+ [A] word64be :: Monad m => Parser Word8 m Word64+ [A] word32le :: Monad m => Parser Word8 m Word32+ [A] word32be :: Monad m => Parser Word8 m Word32+ [A] word16le :: Monad m => Parser Word8 m Word16+ [A] word16be :: Monad m => Parser Word8 m Word16+ [A] unit :: Monad m => Parser Word8 m ()+ [A] ordering :: Monad m => Parser Word8 m Ordering+ [A] int8 :: Monad m => Parser Word8 m Int8+ [A] int64le :: Monad m => Parser Word8 m Int64+ [A] int64be :: Monad m => Parser Word8 m Int64+ [A] int32le :: Monad m => Parser Word8 m Int32+ [A] int32be :: Monad m => Parser Word8 m Int32+ [A] int16le :: Monad m => Parser Word8 m Int16+ [A] int16be :: Monad m => Parser Word8 m Int16+ [A] fromBytes :: FromBytes a => Parser Word8 m a+ [A] float32le :: MonadIO m => Parser Word8 m Float+ [A] float32be :: MonadIO m => Parser Word8 m Float+ [A] eqWord8 :: Monad m => Word8 -> Parser Word8 m Word8+ [A] double64le :: MonadIO m => Parser Word8 m Double+ [A] double64be :: MonadIO m => Parser Word8 m Double+ [A] charLatin1 :: Monad m => Parser Word8 m Char+ [A] bool :: Monad m => Parser Word8 m Bool+[R] Streamly.Internal.Data.Array.Type+[A] Streamly.Internal.Data.Array.Stream+ [A] unlines :: forall m a. (MonadIO m, Unbox a) => a -> Stream m (Array a) -> Stream m a+ [A] toArray :: (MonadIO m, Unbox a) => Stream m (Array a) -> m (Array a)+ [A] splitOnSuffix :: MonadIO m => Word8 -> Stream m (Array Word8) -> Stream m (Array Word8)+ [A] splitOn :: MonadIO m => Word8 -> Stream m (Array Word8) -> Stream m (Array Word8)+ [A] runArrayParserDBreak :: forall m a b. (MonadIO m, Unbox a) => Parser (Array a) m b -> Stream m (Array a) -> m (Either ParseError b, Stream m (Array a))+ [A] runArrayFoldMany :: (Monad m, Unbox a) => ChunkFold m a b -> StreamK m (Array a) -> StreamK m (Either ParseError b)+ [A] runArrayFoldBreak :: (MonadIO m, Unbox a) => ChunkFold m a b -> StreamK m (Array a) -> m (Either ParseError b, StreamK m (Array a))+ [A] runArrayFold :: (MonadIO m, Unbox a) => ChunkFold m a b -> StreamK m (Array a) -> m (Either ParseError b)+ [A] pinnedChunksOf :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> Stream m (Array a)+ [A] parseChunks :: (Monad m, Unbox a) => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b)+ [A] parseBreakChunks :: (Monad m, Unbox a) => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b, StreamK m (Array a))+ [A] parseBreak :: (MonadIO m, Unbox a) => Parser a m b -> StreamK m (Array a) -> m (Either ParseError b, StreamK m (Array a))+ [A] lpackArraysChunksOf :: (MonadIO m, Unbox a) => Int -> Fold m (Array a) () -> Fold m (Array a) ()+ [A] interposeSuffix :: (Monad m, Unbox a) => a -> Stream m (Array a) -> Stream m a+ [A] interpose :: (Monad m, Unbox a) => a -> Stream m (Array a) -> Stream m a+ [A] intercalateSuffix :: (Monad m, Unbox a) => Array a -> Stream m (Array a) -> Stream m a+ [A] foldBreakD :: forall m a b. (MonadIO m, Unbox a) => Fold m a b -> Stream m (Array a) -> m (b, Stream m (Array a))+ [A] foldBreak :: (MonadIO m, Unbox a) => Fold m a b -> StreamK m (Array a) -> m (b, StreamK m (Array a))+ [A] flattenArraysRev :: forall m a. (MonadIO m, Unbox a) => Stream m (Array a) -> Stream m a+ [A] flattenArrays :: forall m a. (MonadIO m, Unbox a) => Stream m (Array a) -> Stream m a+ [A] concatRev :: (Monad m, Unbox a) => Stream m (Array a) -> Stream m a+ [A] concat :: (Monad m, Unbox a) => Stream m (Array a) -> Stream m a+ [A] compact :: (MonadIO m, Unbox a) => Int -> Stream m (Array a) -> Stream m (Array a)+ [A] chunksOf :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> Stream m (Array a)+ [A] bufferChunks :: (MonadIO m, Unbox a) => Stream m a -> m (StreamK m (Array a))+[R] Streamly.Internal.Data.Array.Mut.Type+[R] Streamly.Internal.Data.Array.Mut.Stream+[R] Streamly.Internal.Data.Array.Mut+[R] Streamly.Internal.Data.Array.Generic.Mut.Type+[C] Streamly.Internal.Data.Array.Generic+ [A] getIndex :: Int -> Array a -> Maybe a+ [A] fromPureStream :: Stream Identity a -> Array a+ [A] fromByteStr# :: Addr# -> Array Word8+ [A] chunksOf :: forall m a. MonadIO m => Int -> Stream m a -> Stream m (Array a)+[C] Streamly.Internal.Data.Array+ [A] ArrayUnsafe+ [A] ArrayUnsafe :: {-# UNPACK #-} !MutByteArray -> {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> ArrayUnsafe a+ [C] Array+ [A] [arrStart] :: Array a -> {-# UNPACK #-} !Int+ [A] [arrEnd] :: Array a -> {-# UNPACK #-} !Int+ [A] [arrContents] :: Array a -> {-# UNPACK #-} !MutByteArray+ [A] Array :: {-# UNPACK #-} !MutByteArray -> {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> Array a+ [A] writeWith :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+ [A] writeNUnsafe :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+ [R] writeNAligned :: forall m a. (MonadIO m, Unbox a) => Int -> Int -> Fold m a (Array a)+ [A] unsafeMakePure :: Monad m => Fold IO a b -> Fold m a b+ [A] unsafeIndexIO :: forall a. Unbox a => Int -> Array a -> IO a+ [D] unsafeIndex :: forall a. Unbox a => Int -> Array a -> a+ [A] unsafeFreezeWithShrink :: Unbox a => MutArray a -> Array a+ [A] unpin :: Array a -> IO (Array a)+ [A] toStreamKRev :: forall m a. (Monad m, Unbox a) => Array a -> StreamK m a+ [A] toStreamK :: forall m a. (Monad m, Unbox a) => Array a -> StreamK m a+ [A] toStreamDRev :: forall m a. (Monad m, Unbox a) => Array a -> Stream m a+ [A] toStreamD :: forall m a. (Monad m, Unbox a) => Array a -> Stream m a+ [A] splitAt :: Unbox a => Int -> Array a -> (Array a, Array a)+ [A] splice :: (MonadIO m, Unbox a) => Array a -> Array a -> m (Array a)+ [A] serialize :: Serialize a => a -> Array Word8+ [A] pinnedWriteNUnsafe :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+ [A] pinnedWriteNAligned :: forall m a. (MonadIO m, Unbox a) => Int -> Int -> Fold m a (Array a)+ [A] pinnedWriteN :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+ [A] pinnedWrite :: forall m a. (MonadIO m, Unbox a) => Fold m a (Array a)+ [A] pinnedSerialize :: Serialize a => a -> Array Word8+ [A] pinnedFromListN :: Unbox a => Int -> [a] -> Array a+ [A] pinnedFromList :: Unbox a => [a] -> Array a+ [A] pinnedClone :: MonadIO m => Array a -> m (Array a)+ [A] pinnedChunksOf :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> Stream m (Array a)+ [A] pin :: Array a -> IO (Array a)+ [A] nil :: Array a+ [A] isPinned :: Array a -> Bool+ [A] getIndexUnsafe :: forall a. Unbox a => Int -> Array a -> a+ [A] fromStreamDN :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> m (Array a)+ [A] fromStreamD :: forall m a. (MonadIO m, Unbox a) => Stream m a -> m (Array a)+ [A] fromPureStream :: Unbox a => Stream Identity a -> Array a+ [A] fromListRevN :: Unbox a => Int -> [a] -> Array a+ [A] fromListRev :: Unbox a => [a] -> Array a+ [A] fromByteStr# :: Addr# -> Array Word8+ [A] foldr :: Unbox a => (a -> b -> b) -> b -> Array a -> b+ [A] foldl' :: forall a b. Unbox a => (b -> a -> b) -> b -> Array a -> b+ [A] flattenArraysRev :: forall m a. (MonadIO m, Unbox a) => Stream m (Array a) -> Stream m a+ [A] flattenArrays :: forall m a. (MonadIO m, Unbox a) => Stream m (Array a) -> Stream m a+ [A] encodeAs :: forall a. Serialize a => PinnedState -> a -> Array Word8+ [A] deserialize :: Serialize a => Array Word8 -> a+ [A] clone :: MonadIO m => Array a -> m (Array a)+ [A] chunksOf :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> Stream m (Array a)+ [A] byteLength :: Array a -> Int+ [A] bufferChunks :: (MonadIO m, Unbox a) => Stream m a -> m (StreamK m (Array a))+ [A] breakOn :: MonadIO m => Word8 -> Array Word8 -> m (Array Word8, Maybe (Array Word8))
+ docs/ApiChangelogs/0.2.0-0.2.2.txt view
@@ -0,0 +1,306 @@+---------------------------------+API Annotations+---------------------------------++[A] : Added+[R] : Removed+[C] : Changed+[O] : Old definition+[N] : New definition+[D] : Deprecated++---------------------------------+API diff+---------------------------------++[C] Streamly.Data.Stream+ [A] (FixityR,5)+ [A] (FixityR,5)+[C] Streamly.Data.MutArray.Generic+ [A] emptyOf :: MonadIO m => Int -> m (MutArray a)+ [A] createOf :: MonadIO m => Int -> Fold m a (MutArray a)+ [A] create :: MonadIO m => Fold m a (MutArray a)+[C] Streamly.Data.MutArray+ [A] pinnedEmptyOf :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a)+ [A] emptyOf :: (MonadIO m, Unbox a) => Int -> m (MutArray a)+ [A] createOf :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [A] create :: forall m a. (MonadIO m, Unbox a) => Fold m a (MutArray a)+ [A] appendN :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a) -> Fold m a (MutArray a)+ [A] append :: forall m a. (MonadIO m, Unbox a) => m (MutArray a) -> Fold m a (MutArray a)+[C] Streamly.Data.Array.Generic+ [A] createOf :: MonadIO m => Int -> Fold m a (Array a)+ [A] create :: MonadIO m => Fold m a (Array a)+[C] Streamly.Data.Array+ [A] class Serialize a+ [A] serializeAt :: Serialize a => Int -> MutByteArray -> a -> IO Int+ [A] pinnedSerialize :: Serialize a => a -> Array Word8+ [A] deserializeAt :: Serialize a => Int -> MutByteArray -> Int -> IO (Int, a)+ [A] deserialize :: Serialize a => Array Word8 -> a+ [A] createOf :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+ [A] create :: forall m a. (MonadIO m, Unbox a) => Fold m a (Array a)+ [A] addSizeTo :: Serialize a => Int -> a -> Int++---------------------------------+Internal API diff+---------------------------------++[C] Streamly.Internal.Unicode.Stream+ [A] encodeUtf16le' :: Stream m Char -> Stream m Word16+ [A] decodeUtf16le' :: Stream m Word16 -> Stream m Char+[A] Streamly.Internal.FileSystem.Path+ [A] class IsPath a+ [A] Rel+ [A] File+ [A] Dir+ [A] Abs+ [A] Streamly.Internal.FileSystem.Path.IsPath+ [A] instance Streamly.Internal.FileSystem.Path.IsPath Streamly.Internal.FileSystem.Path.Path+ [A] instance Streamly.Internal.FileSystem.Path.IsPath (Streamly.Internal.FileSystem.Path.Rel Streamly.Internal.FileSystem.Path.Path)+ [A] instance Streamly.Internal.FileSystem.Path.IsPath (Streamly.Internal.FileSystem.Path.Rel (Streamly.Internal.FileSystem.Path.File Streamly.Internal.FileSystem.Path.Path))+ [A] instance Streamly.Internal.FileSystem.Path.IsPath (Streamly.Internal.FileSystem.Path.Rel (Streamly.Internal.FileSystem.Path.Dir Streamly.Internal.FileSystem.Path.Path))+ [A] instance Streamly.Internal.FileSystem.Path.IsPath (Streamly.Internal.FileSystem.Path.File Streamly.Internal.FileSystem.Path.Path)+ [A] instance Streamly.Internal.FileSystem.Path.IsPath (Streamly.Internal.FileSystem.Path.Dir Streamly.Internal.FileSystem.Path.Path)+ [A] instance Streamly.Internal.FileSystem.Path.IsPath (Streamly.Internal.FileSystem.Path.Abs Streamly.Internal.FileSystem.Path.Path)+ [A] instance Streamly.Internal.FileSystem.Path.IsPath (Streamly.Internal.FileSystem.Path.Abs (Streamly.Internal.FileSystem.Path.File Streamly.Internal.FileSystem.Path.Path))+ [A] instance Streamly.Internal.FileSystem.Path.IsPath (Streamly.Internal.FileSystem.Path.Abs (Streamly.Internal.FileSystem.Path.Dir Streamly.Internal.FileSystem.Path.Path))+ [A] GHC.Show.Show+ [A] instance GHC.Show.Show Streamly.Internal.FileSystem.Path.PathException+ [A] GHC.Exception.Type.Exception+ [A] instance GHC.Exception.Type.Exception Streamly.Internal.FileSystem.Path.PathException+ [A] GHC.Classes.Eq+ [A] instance GHC.Classes.Eq Streamly.Internal.FileSystem.Path.PathException+ [A] Path+ [A] Path :: Array Word8 -> Path+ [A] toString :: Path -> [Char]+ [A] toPath :: IsPath a => a -> Path+ [A] toChunk :: Path -> Array Word8+ [A] toChars :: Monad m => Path -> Stream m Char+ [A] relfile :: QuasiQuoter+ [A] reldir :: QuasiQuoter+ [A] rel :: QuasiQuoter+ [A] primarySeparator :: Char+ [A] path :: QuasiQuoter+ [A] mkRelFile :: String -> Q Exp+ [A] mkRelDir :: String -> Q Exp+ [A] mkRel :: String -> Q Exp+ [A] mkPath :: String -> Q Exp+ [A] mkFile :: String -> Q Exp+ [A] mkDir :: String -> Q Exp+ [A] mkAbsFile :: String -> Q Exp+ [A] mkAbsDir :: String -> Q Exp+ [A] mkAbs :: String -> Q Exp+ [A] isSeparator :: Char -> Bool+ [A] fromString :: MonadThrow m => [Char] -> m Path+ [A] fromPathUnsafe :: IsPath a => Path -> a+ [A] fromPath :: (IsPath a, MonadThrow m) => Path -> m a+ [A] fromChunkUnsafe :: Array Word8 -> Path+ [A] fromChunk :: MonadThrow m => Array Word8 -> m Path+ [A] fromChars :: MonadThrow m => Stream Identity Char -> m Path+ [A] file :: QuasiQuoter+ [A] extendPath :: Path -> Path -> Path+ [A] extendDir :: (IsPath (a (Dir Path)), IsPath b, IsPath (a b)) => a (Dir Path) -> Rel b -> a b+ [A] dir :: QuasiQuoter+ [A] adaptPath :: (MonadThrow m, IsPath a, IsPath b) => a -> m b+ [A] absfile :: QuasiQuoter+ [A] absdir :: QuasiQuoter+ [A] abs :: QuasiQuoter+[C] Streamly.Internal.Data.Stream+ [A] (FixityR,5)+ [A] (FixityR,5)+ [C] splitInnerBySuffix+ [O] splitInnerBySuffix :: (Monad m, Eq (f a), Monoid (f a)) => (f a -> m (f a, Maybe (f a))) -> (f a -> f a -> m (f a)) -> Stream m (f a) -> Stream m (f a)+ [N] splitInnerBySuffix :: Monad m => (f a -> Bool) -> (f a -> m (f a, Maybe (f a))) -> (f a -> f a -> m (f a)) -> Stream m (f a) -> Stream m (f a)+ [D] sliceOnSuffix :: Monad m => (a -> Bool) -> Stream m a -> Stream m (Int, Int)+ [A] indexOnSuffix :: Monad m => (a -> Bool) -> Stream m a -> Stream m (Int, Int)+[C] Streamly.Internal.Data.MutByteArray+ [A] unsafePinnedAsPtr :: MonadIO m => MutByteArray -> (Ptr a -> m b) -> m b+ [A] unsafeAsPtr :: MonadIO m => MutByteArray -> (Ptr a -> m b) -> m b+ [D] nil :: MutByteArray+ [A] empty :: MutByteArray+ [D] asPtrUnsafe :: MonadIO m => MutByteArray -> (Ptr a -> m b) -> m b+[D] Streamly.Internal.Data.MutArray.Stream+ [D] writeChunks :: (MonadIO m, Unbox a) => Int -> Fold m a (StreamK n (MutArray a))+ [D] fromArrayStreamK :: (Unbox a, MonadIO m) => StreamK m (MutArray a) -> m (MutArray a)+ [D] flattenArraysRev :: forall m a. (MonadIO m, Unbox a) => Stream m (MutArray a) -> Stream m a+ [D] flattenArrays :: forall m a. (MonadIO m, Unbox a) => Stream m (MutArray a) -> Stream m a+[C] Streamly.Internal.Data.MutArray.Generic+ [D] writeWith :: MonadIO m => Int -> Fold m a (MutArray a)+ [D] writeNUnsafe :: MonadIO m => Int -> Fold m a (MutArray a)+ [A] unsafeCreateOf :: MonadIO m => Int -> Fold m a (MutArray a)+ [A] emptyOf :: MonadIO m => Int -> m (MutArray a)+ [A] createWith :: MonadIO m => Int -> Fold m a (MutArray a)+ [A] createOf :: MonadIO m => Int -> Fold m a (MutArray a)+ [A] create :: MonadIO m => Fold m a (MutArray a)+[C] Streamly.Internal.Data.MutArray+ [A] SpliceState+ [A] SpliceYielding :: arr -> SpliceState s arr -> SpliceState s arr+ [A] SpliceInitial :: s -> SpliceState s arr+ [A] SpliceFinish :: SpliceState s arr+ [A] SpliceBuffering :: s -> arr -> SpliceState s arr+ [R] MutByteArray+ [D] writeWith :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [D] writeRevN :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [D] writeNWithUnsafe :: forall m a. (MonadIO m, Unbox a) => (Int -> m (MutArray a)) -> Int -> Fold m a (MutArray a)+ [D] writeNUnsafe :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [D] writeChunks :: (MonadIO m, Unbox a) => Int -> Fold m a (StreamK n (MutArray a))+ [D] writeAppendWith :: forall m a. (MonadIO m, Unbox a) => (Int -> Int) -> m (MutArray a) -> Fold m a (MutArray a)+ [D] writeAppendNUnsafe :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a) -> Fold m a (MutArray a)+ [A] unsafePinnedCreateOf :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [A] unsafePinnedAsPtr :: MonadIO m => MutArray a -> (Ptr a -> m b) -> m b+ [A] unsafeCreateOfWith :: forall m a. (MonadIO m, Unbox a) => (Int -> m (MutArray a)) -> Int -> Fold m a (MutArray a)+ [A] unsafeCreateOf :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [A] unsafeAsPtr :: MonadIO m => MutArray a -> (Ptr a -> m b) -> m b+ [A] unsafeAppendN :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a) -> Fold m a (MutArray a)+ [A] toStreamWith :: forall m a. (Monad m, Unbox a) => (forall b. IO b -> m b) -> MutArray a -> Stream m a+ [A] toStreamRevWith :: forall m a. (Monad m, Unbox a) => (forall b. IO b -> m b) -> MutArray a -> Stream m a+ [R] toStreamDWith :: forall m a. (Monad m, Unbox a) => (forall b. IO b -> m b) -> MutArray a -> Stream m a+ [R] toStreamDRevWith :: forall m a. (Monad m, Unbox a) => (forall b. IO b -> m b) -> MutArray a -> Stream m a+ [A] slicerFromLen :: forall m a. (Monad m, Unbox a) => Int -> Int -> Unfold m (MutArray a) (MutArray a)+ [A] sliceIndexerFromLen :: forall m a. (Monad m, Unbox a) => Int -> Int -> Unfold m (MutArray a) (Int, Int)+ [A] revCreateOf :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [D] resizeExp :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m (MutArray a)+ [D] resize :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m (MutArray a)+ [A] pokeSkipUnsafe :: Int -> MutArray Word8 -> MutArray Word8+ [A] pokeAppendMay :: forall m a. (MonadIO m, Unbox a) => MutArray Word8 -> a -> m (Maybe (MutArray Word8))+ [A] pokeAppend :: forall m a. (MonadIO m, Unbox a) => MutArray Word8 -> a -> m (MutArray Word8)+ [D] pinnedWriteNUnsafe :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [D] pinnedWriteN :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [D] pinnedWrite :: forall m a. (MonadIO m, Unbox a) => Fold m a (MutArray a)+ [D] pinnedNewBytes :: MonadIO m => Int -> m (MutArray a)+ [A] pinnedEmptyOf :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a)+ [A] pinnedCreateOf :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [A] pinnedCreate :: forall m a. (MonadIO m, Unbox a) => Fold m a (MutArray a)+ [A] pinnedCompactLE :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m (MutArray a) -> Stream m (MutArray a)+ [A] peekUnconsUnsafe :: forall m a. (MonadIO m, Unbox a) => MutArray Word8 -> m (a, MutArray Word8)+ [A] peekUncons :: forall m a. (MonadIO m, Unbox a) => MutArray Word8 -> m (Maybe a, MutArray Word8)+ [A] peekSkipUnsafe :: Int -> MutArray Word8 -> MutArray Word8+ [A] pPinnedCompactLE :: forall m a. (MonadIO m, Unbox a) => Int -> Parser (MutArray a) m (MutArray a)+ [A] pCompactLE :: forall m a. (MonadIO m, Unbox a) => Int -> Parser (MutArray a) m (MutArray a)+ [D] nil :: MutArray a+ [A] lPinnedCompactGE :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m (MutArray a) () -> Fold m (MutArray a) ()+ [A] lCompactGE :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m (MutArray a) () -> Fold m (MutArray a) ()+ [A] indexReaderWith :: (Monad m, Unbox a) => (forall b. IO b -> m b) -> Stream m Int -> Unfold m (MutArray a) a+ [A] indexReader :: (MonadIO m, Unbox a) => Stream m Int -> Unfold m (MutArray a) a+ [A] growExp :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m (MutArray a)+ [A] grow :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m (MutArray a)+ [D] getSlicesFromLen :: forall m a. (Monad m, Unbox a) => Int -> Int -> Unfold m (MutArray a) (MutArray a)+ [R] getIndicesD :: (Monad m, Unbox a) => (forall b. IO b -> m b) -> Stream m Int -> Unfold m (MutArray a) a+ [D] getIndices :: (MonadIO m, Unbox a) => Stream m Int -> Unfold m (MutArray a) a+ [D] genSlicesFromLen :: forall m a. (Monad m, Unbox a) => Int -> Int -> Unfold m (MutArray a) (Int, Int)+ [A] fromStreamN :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> m (MutArray a)+ [D] fromStreamDN :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> m (MutArray a)+ [D] fromStreamD :: (MonadIO m, Unbox a) => Stream m a -> m (MutArray a)+ [A] fromPureStreamN :: (MonadIO m, Unbox a) => Int -> Stream Identity a -> m (MutArray a)+ [A] fromPtrN :: MonadIO m => Int -> Ptr Word8 -> m (MutArray Word8)+ [A] fromChunksRealloced :: forall m a. (MonadIO m, Unbox a) => Stream m (MutArray a) -> m (MutArray a)+ [A] fromChunksK :: (Unbox a, MonadIO m) => StreamK m (MutArray a) -> m (MutArray a)+ [A] fromByteStr# :: MonadIO m => Addr# -> m (MutArray Word8)+ [D] fromArrayStreamK :: (Unbox a, MonadIO m) => StreamK m (MutArray a) -> m (MutArray a)+ [D] flattenArraysRev :: forall m a. (MonadIO m, Unbox a) => Stream m (MutArray a) -> Stream m a+ [D] flattenArrays :: forall m a. (MonadIO m, Unbox a) => Stream m (MutArray a) -> Stream m a+ [A] fPinnedCompactGE :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m (MutArray a) (MutArray a)+ [A] fCompactGE :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m (MutArray a) (MutArray a)+ [A] emptyOf :: (MonadIO m, Unbox a) => Int -> m (MutArray a)+ [A] empty :: MutArray a+ [A] createWith :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [A] createOfWith :: forall m a. (MonadIO m, Unbox a) => (Int -> m (MutArray a)) -> Int -> Fold m a (MutArray a)+ [A] createOf :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [A] create :: forall m a. (MonadIO m, Unbox a) => Fold m a (MutArray a)+ [A] concatWith :: forall m a. (Monad m, Unbox a) => (forall b. IO b -> m b) -> Stream m (MutArray a) -> Stream m a+ [A] concatRevWith :: forall m a. (Monad m, Unbox a) => (forall b. IO b -> m b) -> Stream m (MutArray a) -> Stream m a+ [A] concatRev :: forall m a. (MonadIO m, Unbox a) => Stream m (MutArray a) -> Stream m a+ [A] concat :: forall m a. (MonadIO m, Unbox a) => Stream m (MutArray a) -> Stream m a+ [A] compactOnByteSuffix :: MonadIO m => Word8 -> Stream m (MutArray Word8) -> Stream m (MutArray Word8)+ [A] compactOnByte :: MonadIO m => Word8 -> Stream m (MutArray Word8) -> Stream m (MutArray Word8)+ [A] compactLeAs :: forall m a. (MonadIO m, Unbox a) => PinnedState -> Int -> Stream m (MutArray a) -> Stream m (MutArray a)+ [A] compactLE :: (MonadIO m, Unbox a) => Int -> Stream m (MutArray a) -> Stream m (MutArray a)+ [A] compactGE :: (MonadIO m, Unbox a) => Int -> Stream m (MutArray a) -> Stream m (MutArray a)+ [A] compactEQ :: Int -> Stream m (MutArray a) -> Stream m (MutArray a)+ [D] cmp :: MonadIO m => MutArray a -> MutArray a -> m Ordering+ [A] byteEq :: MonadIO m => MutArray a -> MutArray a -> m Bool+ [A] byteCmp :: MonadIO m => MutArray a -> MutArray a -> m Ordering+ [A] buildChunks :: (MonadIO m, Unbox a) => Int -> Fold m a (StreamK n (MutArray a))+ [D] asPtrUnsafe :: MonadIO m => MutArray a -> (Ptr a -> m b) -> m b+ [A] appendWith :: forall m a. (MonadIO m, Unbox a) => (Int -> Int) -> m (MutArray a) -> Fold m a (MutArray a)+ [A] appendN :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a) -> Fold m a (MutArray a)+ [A] append :: forall m a. (MonadIO m, Unbox a) => m (MutArray a) -> Fold m a (MutArray a)+[D] Streamly.Internal.Data.Array.Stream+ [C] interposeSuffix+ [O] interposeSuffix :: (Monad m, Unbox a) => a -> Stream m (Array a) -> Stream m a+ [N] interposeSuffix :: forall m a. (Monad m, Unbox a) => a -> Stream m (Array a) -> Stream m a+ [D] flattenArraysRev :: forall m a. (MonadIO m, Unbox a) => Stream m (Array a) -> Stream m a+ [D] flattenArrays :: forall m a. (MonadIO m, Unbox a) => Stream m (Array a) -> Stream m a+ [C] concatRev+ [O] concatRev :: (Monad m, Unbox a) => Stream m (Array a) -> Stream m a+ [N] concatRev :: forall m a. (Monad m, Unbox a) => Stream m (Array a) -> Stream m a+ [D] bufferChunks :: (MonadIO m, Unbox a) => Stream m a -> m (StreamK m (Array a))+[C] Streamly.Internal.Data.Array.Generic+ [A] createOf :: MonadIO m => Int -> Fold m a (Array a)+ [A] create :: MonadIO m => Fold m a (Array a)+[C] Streamly.Internal.Data.Array+ [R] ArrayUnsafe+ [D] writeWith :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+ [D] writeNUnsafe :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+ [A] unsafePinnedCreateOf :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+ [A] unsafePinnedAsPtr :: MonadIO m => Array a -> (Ptr a -> m b) -> m b+ [A] unsafeCreateOf :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+ [D] toStreamDRev :: forall m a. (Monad m, Unbox a) => Array a -> Stream m a+ [D] toStreamD :: forall m a. (Monad m, Unbox a) => Array a -> Stream m a+ [C] splice+ [O] splice :: (MonadIO m, Unbox a) => Array a -> Array a -> m (Array a)+ [N] splice :: MonadIO m => Array a -> Array a -> m (Array a)+ [A] slicerFromLen :: forall m a. (Monad m, Unbox a) => Int -> Int -> Unfold m (Array a) (Array a)+ [A] sliceIndexerFromLen :: forall m a. (Monad m, Unbox a) => Int -> Int -> Unfold m (Array a) (Int, Int)+ [D] pinnedWriteNUnsafe :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+ [D] pinnedWriteNAligned :: forall m a. (MonadIO m, Unbox a) => Int -> Int -> Fold m a (Array a)+ [D] pinnedWriteN :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+ [D] pinnedWrite :: forall m a. (MonadIO m, Unbox a) => Fold m a (Array a)+ [A] pinnedCreateOf :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+ [A] pinnedCreate :: forall m a. (MonadIO m, Unbox a) => Fold m a (Array a)+ [A] pinnedCompactLE :: (MonadIO m, Unbox a) => Int -> Stream m (Array a) -> Stream m (Array a)+ [A] parseBreakChunksK :: forall m a b. (MonadIO m, Unbox a) => Parser a m b -> StreamK m (Array a) -> m (Either ParseError b, StreamK m (Array a))+ [D] nil :: Array a+ [A] lPinnedCompactGE :: (MonadIO m, Unbox a) => Int -> Fold m (Array a) () -> Fold m (Array a) ()+ [A] lCompactGE :: (MonadIO m, Unbox a) => Int -> Fold m (Array a) () -> Fold m (Array a) ()+ [A] interposeSuffix :: forall m a. (Monad m, Unbox a) => a -> Stream m (Array a) -> Stream m a+ [A] interpose :: (Monad m, Unbox a) => a -> Stream m (Array a) -> Stream m a+ [A] intercalateSuffix :: (Monad m, Unbox a) => Array a -> Stream m (Array a) -> Stream m a+ [A] indexReaderFromThenTo :: Unfold m (Int, Int, Int, Array a) a+ [A] indexReader :: (Monad m, Unbox a) => Stream m Int -> Unfold m (Array a) a+ [A] indexFinder :: (a -> Bool) -> Unfold Identity (Array a) Int+ [D] getSlicesFromLen :: forall m a. (Monad m, Unbox a) => Int -> Int -> Unfold m (Array a) (Array a)+ [R] getIndicesFromThenTo :: Unfold m (Int, Int, Int, Array a) a+ [D] getIndices :: (Monad m, Unbox a) => Stream m Int -> Unfold m (Array a) a+ [D] genSlicesFromLen :: forall m a. (Monad m, Unbox a) => Int -> Int -> Unfold m (Array a) (Int, Int)+ [D] fromStreamDN :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> m (Array a)+ [D] fromStreamD :: forall m a. (MonadIO m, Unbox a) => Stream m a -> m (Array a)+ [A] fromPureStreamN :: Unbox a => Int -> Stream Identity a -> Array a+ [A] fromPtrN :: Int -> Ptr Word8 -> Array Word8+ [A] fromChunksK :: (MonadIO m, Unbox a) => StreamK m (Array a) -> m (Array a)+ [A] fromChunks :: (MonadIO m, Unbox a) => Stream m (Array a) -> m (Array a)+ [A] fromByteStr :: Ptr Word8 -> Array Word8+ [A] foldChunks :: (MonadIO m, Unbox a) => Fold m a b -> Stream m (Array a) -> m b+ [A] foldBreakChunksK :: forall m a b. (MonadIO m, Unbox a) => Fold m a b -> StreamK m (Array a) -> m (b, StreamK m (Array a))+ [A] foldBreakChunks :: forall m a b. (MonadIO m, Unbox a) => Fold m a b -> Stream m (Array a) -> m (b, Stream m (Array a))+ [D] flattenArraysRev :: forall m a. (MonadIO m, Unbox a) => Stream m (Array a) -> Stream m a+ [D] flattenArrays :: forall m a. (MonadIO m, Unbox a) => Stream m (Array a) -> Stream m a+ [C] findIndicesOf+ [O] findIndicesOf :: (a -> Bool) -> Unfold Identity (Array a) Int+ [N] findIndicesOf :: (a -> Bool) -> Array a -> Stream Identity Int+ [A] fPinnedCompactGE :: (MonadIO m, Unbox a) => Int -> Fold m (Array a) (Array a)+ [A] fCompactGE :: (MonadIO m, Unbox a) => Int -> Fold m (Array a) (Array a)+ [A] empty :: Array a+ [A] createWith :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+ [A] createOf :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+ [A] create :: forall m a. (MonadIO m, Unbox a) => Fold m a (Array a)+ [A] concatRev :: forall m a. (Monad m, Unbox a) => Stream m (Array a) -> Stream m a+ [A] concat :: (Monad m, Unbox a) => Stream m (Array a) -> Stream m a+ [A] compactOnByteSuffix :: MonadIO m => Word8 -> Stream m (Array Word8) -> Stream m (Array Word8)+ [A] compactOnByte :: MonadIO m => Word8 -> Stream m (Array Word8) -> Stream m (Array Word8)+ [A] compactLE :: (MonadIO m, Unbox a) => Int -> Stream m (Array a) -> Stream m (Array a)+ [A] compactGE :: (MonadIO m, Unbox a) => Int -> Stream m (Array a) -> Stream m (Array a)+ [A] byteEq :: Array a -> Array a -> Bool+ [A] byteCmp :: Array a -> Array a -> Ordering+ [A] buildChunks :: (MonadIO m, Unbox a) => Stream m a -> m (StreamK m (Array a))+ [D] bufferChunks :: (MonadIO m, Unbox a) => Stream m a -> m (StreamK m (Array a))+ [D] asPtrUnsafe :: MonadIO m => Array a -> (Ptr a -> m b) -> m b
+ docs/ApiChangelogs/0.2.2-0.3.0.txt view
@@ -0,0 +1,1889 @@+---------------------------------+API Annotations+---------------------------------++[A] : Added+[R] : Removed+[C] : Changed+[O] : Old definition+[N] : New definition+[D] : Deprecated++---------------------------------+API diff+---------------------------------++[C] Streamly.Unicode.Stream+ [A] encodeUtf16le' :: Monad m => Stream m Char -> Stream m Word16+ [A] encodeUtf16le :: Monad m => Stream m Char -> Stream m Word16+ [A] decodeUtf16le' :: Monad m => Stream m Word16 -> Stream m Char+ [A] decodeUtf16le :: Monad m => Stream m Word16 -> Stream m Char+[A] Streamly.FileSystem.Path+ [A] EqCfg+ [A] type Path = PosixPath+ [A] type OsWord = Word8+ [A] validatePath :: MonadThrow m => Array OsWord -> m ()+ [A] unsafeJoin :: Path -> Path -> Path+ [A] toString :: Path -> [Char]+ [A] toArray :: Path -> Array OsWord+ [A] takeFileName :: Path -> Maybe Path+ [A] takeFileBase :: Path -> Maybe Path+ [A] takeExtension :: Path -> Maybe Path+ [A] takeDirectory :: Path -> Maybe Path+ [A] splitRoot :: Path -> Maybe (Path, Maybe Path)+ [A] splitPath :: Monad m => Path -> Stream m Path+ [A] splitFile :: Path -> Maybe (Maybe Path, Path)+ [A] splitExtension :: Path -> Maybe (Path, Path)+ [A] pathE :: String -> Q Exp+ [A] path :: QuasiQuoter+ [A] joinStr :: Path -> [Char] -> Path+ [A] join :: Path -> Path -> Path+ [A] isUnrooted :: Path -> Bool+ [A] isRooted :: Path -> Bool+ [A] ignoreTrailingSeparators :: Bool -> EqCfg -> EqCfg+ [A] ignoreCase :: Bool -> EqCfg -> EqCfg+ [A] fromString_ :: [Char] -> Path+ [A] fromString :: MonadThrow m => [Char] -> m Path+ [A] fromArray :: MonadThrow m => Array OsWord -> m Path+ [A] eqPath :: (EqCfg -> EqCfg) -> Path -> Path -> Bool+ [A] dropExtension :: Path -> Path+ [A] allowRelativeEquality :: Bool -> EqCfg -> EqCfg+[C] Streamly.FileSystem.Handle+ [C] writeChunks+ [O] writeChunks :: MonadIO m => Handle -> Fold m (Array a) ()+ [N] writeChunks :: forall m (a :: Type). MonadIO m => Handle -> Fold m (Array a) ()+ [C] putChunk+ [O] putChunk :: MonadIO m => Handle -> Array a -> m ()+ [N] putChunk :: forall m (a :: Type). MonadIO m => Handle -> Array a -> m ()+[A] Streamly.FileSystem.FileIO+ [A] writeWith :: (MonadIO m, MonadCatch m) => Int -> Path -> Fold m Word8 ()+ [A] writeChunks :: (MonadIO m, MonadCatch m) => Path -> Fold m (Array a) ()+ [A] write :: (MonadIO m, MonadCatch m) => Path -> Fold m Word8 ()+ [A] withFile :: (MonadIO m, MonadCatch m) => Path -> IOMode -> (Handle -> Stream m a) -> Stream m a+ [A] readChunksWith :: (MonadIO m, MonadCatch m) => Int -> Path -> Stream m (Array Word8)+ [A] readChunks :: (MonadIO m, MonadCatch m) => Path -> Stream m (Array Word8)+ [A] read :: (MonadIO m, MonadCatch m) => Path -> Stream m Word8+[D] Streamly.FileSystem.File+ [C] writeChunks+ [O] writeChunks :: (MonadIO m, MonadCatch m) => FilePath -> Fold m (Array a) ()+ [N] writeChunks :: forall m (a :: Type). (MonadIO m, MonadCatch m) => FilePath -> Fold m (Array a) ()+[A] Streamly.FileSystem.DirIO+ [A] ReadOptions+ [A] readEither :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) -> Path -> Stream m (Either Path Path)+ [A] read :: (MonadIO m, MonadCatch m) => Path -> Stream m Path+ [A] ignoreSymlinkLoops :: Bool -> ReadOptions -> ReadOptions+ [A] ignoreMissing :: Bool -> ReadOptions -> ReadOptions+ [A] ignoreInaccessible :: Bool -> ReadOptions -> ReadOptions+ [A] followSymlinks :: Bool -> ReadOptions -> ReadOptions+[D] Streamly.FileSystem.Dir+[C] Streamly.Data.Unfold+ [A] unfoldEach :: Monad m => Unfold m b c -> Unfold m a b -> Unfold m a c+ [D] many :: Monad m => Unfold m b c -> Unfold m a b -> Unfold m a c+ [A] carry :: Functor m => Unfold m a b -> Unfold m a (a, b)+[C] Streamly.Data.StreamK+ [A] toParserK :: Monad m => Parser a m b -> ParserK a m b+ [A] toList :: Monad m => StreamK m a -> m [a]+ [A] parsePos :: Monad m => ParserK a m b -> StreamK m a -> m (Either ParseErrorPos b)+ [D] parseChunks :: (Monad m, Unbox a) => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b)+ [A] parseBreakPos :: forall m a b. Monad m => ParserK a m b -> StreamK m a -> m (Either ParseErrorPos b, StreamK m a)+ [D] parseBreakChunks :: (Monad m, Unbox a) => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b, StreamK m (Array a))+ [A] filter :: (a -> Bool) -> StreamK m a -> StreamK m a+ [A] fairConcatMap :: (a -> StreamK m b) -> StreamK m a -> StreamK m b+ [A] fairConcatForM :: Monad m => StreamK m a -> (a -> m (StreamK m b)) -> StreamK m b+ [A] fairConcatFor :: StreamK m a -> (a -> StreamK m b) -> StreamK m b+ [A] concatMap :: (a -> StreamK m b) -> StreamK m a -> StreamK m b+ [A] concatForM :: Monad m => StreamK m a -> (a -> m (StreamK m b)) -> StreamK m b+ [A] concatFor :: StreamK m a -> (a -> StreamK m b) -> StreamK m b+ [A] bfsConcatMap :: (a -> StreamK m b) -> StreamK m a -> StreamK m b+ [A] bfsConcatForM :: Monad m => StreamK m a -> (a -> m (StreamK m b)) -> StreamK m b+ [A] bfsConcatFor :: StreamK m a -> (a -> StreamK m b) -> StreamK m b+[C] Streamly.Data.Stream+ [A] unionBy :: MonadIO m => (a -> a -> Bool) -> Stream m a -> Stream m a -> Stream m a+ [D] unfoldMany :: Monad m => Unfold m a b -> Stream m a -> Stream m b+ [A] unfoldEachSepBySeq :: Monad m => b -> Unfold m b c -> Stream m b -> Stream m c+ [A] unfoldEachEndBySeq :: Monad m => b -> Unfold m b c -> Stream m b -> Stream m c+ [A] unfoldEach :: Monad m => Unfold m a b -> Stream m a -> Stream m b+ [A] splitSepBy_ :: Monad m => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+ [A] splitSepBySeq_ :: forall m a b. (MonadIO m, Unbox a, Enum a, Eq a) => Array a -> Fold m a b -> Stream m a -> Stream m b+ [D] splitOn :: Monad m => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+ [A] splitEndBySeq_ :: forall m a b. (MonadIO m, Unbox a, Enum a, Eq a) => Array a -> Fold m a b -> Stream m a -> Stream m b+ [A] splitEndBySeq :: forall m a b. (MonadIO m, Unbox a, Enum a, Eq a) => Array a -> Fold m a b -> Stream m a -> Stream m b+ [A] scanl :: Monad m => Scanl m a b -> Stream m a -> Stream m b+ [D] scanMaybe :: Monad m => Fold m a (Maybe b) -> Stream m a -> Stream m b+ [D] scan :: Monad m => Fold m a b -> Stream m a -> Stream m b+ [A] postscanl :: Monad m => Scanl m a b -> Stream m a -> Stream m b+ [D] postscan :: Monad m => Fold m a b -> Stream m a -> Stream m b+ [A] parsePos :: Monad m => Parser a m b -> Stream m a -> m (Either ParseErrorPos b)+ [A] parseBreakPos :: Monad m => Parser a m b -> Stream m a -> m (Either ParseErrorPos b, Stream m a)+ [A] parseBreak :: Monad m => Parser a m b -> Stream m a -> m (Either ParseError b, Stream m a)+ [A] isInfixOf :: (MonadIO m, Eq a, Enum a, Unbox a) => Stream m a -> Stream m a -> m Bool+ [D] intercalateSuffix :: Monad m => Unfold m b c -> b -> Stream m b -> Stream m c+ [D] intercalate :: Monad m => Unfold m b c -> b -> Stream m b -> Stream m c+ [A] finallyIO'' :: (MonadIO m, MonadCatch m) => AcquireIO -> IO b -> Stream m a -> Stream m a+ [A] finallyIO' :: MonadIO m => AcquireIO -> IO b -> Stream m a -> Stream m a+ [A] fairUnfoldEach :: Monad m => Unfold m a b -> Stream m a -> Stream m b+ [A] fairCross :: Monad m => Stream m a -> Stream m b -> Stream m (a, b)+ [A] fairConcatMap :: Monad m => (a -> Stream m b) -> Stream m a -> Stream m b+ [A] fairConcatForM :: Monad m => Stream m a -> (a -> m (Stream m b)) -> Stream m b+ [A] fairConcatFor :: Monad m => Stream m a -> (a -> Stream m b) -> Stream m b+ [A] cross :: Monad m => Stream m a -> Stream m b -> Stream m (a, b)+ [A] concatForM :: Monad m => Stream m a -> (a -> m (Stream m b)) -> Stream m b+ [A] concatFor :: Monad m => Stream m a -> (a -> Stream m b) -> Stream m b+ [D] chunksOf :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> Stream m (Array a)+ [A] bracketIO'' :: (MonadIO m, MonadCatch m) => AcquireIO -> IO b -> (b -> IO c) -> (b -> Stream m a) -> Stream m a+ [A] bracketIO' :: MonadIO m => AcquireIO -> IO b -> (b -> IO c) -> (b -> Stream m a) -> Stream m a+ [A] bfsUnfoldEach :: Monad m => Unfold m a b -> Stream m a -> Stream m b+[A] Streamly.Data.Scanl+ [A] Scanl+ [A] unzip :: Monad m => Scanl m a x -> Scanl m b y -> Scanl m (a, b) (x, y)+ [A] uniqBy :: Monad m => (a -> a -> Bool) -> Scanl m a (Maybe a)+ [A] topBy :: (MonadIO m, Unbox a) => (a -> a -> Ordering) -> Int -> Scanl m a (MutArray a)+ [A] toSet :: (Monad m, Ord a) => Scanl m a (Set a)+ [A] toListRev :: Monad m => Scanl m a [a]+ [A] toList :: Monad m => Scanl m a [a]+ [A] toIntSet :: Monad m => Scanl m Int IntSet+ [A] the :: (Monad m, Eq a) => Scanl m a (Maybe a)+ [A] teeWith :: Monad m => (b -> c -> d) -> Scanl m a b -> Scanl m a c -> Scanl m a d+ [A] tee :: Monad m => Scanl m a b -> Scanl m a c -> Scanl m a (b, c)+ [A] takeEndBy_ :: Monad m => (a -> Bool) -> Scanl m a b -> Scanl m a b+ [A] takeEndBy :: Monad m => (a -> Bool) -> Scanl m a b -> Scanl m a b+ [A] take :: Monad m => Int -> Scanl m a b -> Scanl m a b+ [A] sum :: (Monad m, Num a) => Scanl m a a+ [A] sconcat :: (Monad m, Semigroup a) => a -> Scanl m a a+ [A] scanl :: Monad m => Scanl m a b -> Scanl m b c -> Scanl m a c+ [A] rollingHashWithSalt :: (Monad m, Enum a) => Int64 -> Scanl m a Int64+ [A] rollingHash :: (Monad m, Enum a) => Scanl m a Int64+ [A] rmapM :: Monad m => (b -> m c) -> Scanl m a b -> Scanl m a c+ [A] product :: (Monad m, Num a, Eq a) => Scanl m a a+ [A] postscanlMaybe :: Monad m => Scanl m a (Maybe b) -> Scanl m b c -> Scanl m a c+ [A] postscanl :: Monad m => Scanl m a b -> Scanl m b c -> Scanl m a c+ [A] partition :: Monad m => Scanl m b x -> Scanl m c x -> Scanl m (Either b c) x+ [A] nubInt :: Monad m => Scanl m Int (Maybe Int)+ [A] nub :: (Monad m, Ord a) => Scanl m a (Maybe a)+ [A] morphInner :: (forall x. m x -> n x) -> Scanl m a b -> Scanl n a b+ [A] mkScanr :: Monad m => (a -> b -> b) -> b -> Scanl m a b+ [A] mkScanlM :: Monad m => (b -> a -> m b) -> m b -> Scanl m a b+ [A] mkScanl1M :: Monad m => (a -> a -> m a) -> Scanl m a (Maybe a)+ [A] mkScanl1 :: Monad m => (a -> a -> a) -> Scanl m a (Maybe a)+ [A] mkScanl :: Monad m => (b -> a -> b) -> b -> Scanl m a b+ [A] minimumBy :: Monad m => (a -> a -> Ordering) -> Scanl m a (Maybe a)+ [A] minimum :: (Monad m, Ord a) => Scanl m a (Maybe a)+ [A] mean :: (Monad m, Fractional a) => Scanl m a a+ [A] mconcat :: (Monad m, Monoid a) => Scanl m a a+ [A] maximumBy :: Monad m => (a -> a -> Ordering) -> Scanl m a (Maybe a)+ [A] maximum :: (Monad m, Ord a) => Scanl m a (Maybe a)+ [A] mapMaybe :: Monad m => (a -> Maybe b) -> Scanl m b r -> Scanl m a r+ [A] lmapM :: Monad m => (a -> m b) -> Scanl m b r -> Scanl m a r+ [A] lmap :: (a -> b) -> Scanl m b r -> Scanl m a r+ [A] length :: Monad m => Scanl m a Int+ [A] latest :: Monad m => Scanl m a (Maybe a)+ [A] foldMapM :: (Monad m, Monoid b) => (a -> m b) -> Scanl m a b+ [A] foldMap :: (Monad m, Monoid b) => (a -> b) -> Scanl m a b+ [A] findIndices :: Monad m => (a -> Bool) -> Scanl m a (Maybe Int)+ [A] filterM :: Monad m => (a -> m Bool) -> Scanl m a r -> Scanl m a r+ [A] filter :: Monad m => (a -> Bool) -> Scanl m a r -> Scanl m a r+ [A] elemIndices :: (Monad m, Eq a) => a -> Scanl m a (Maybe Int)+ [A] drain :: Monad m => Scanl m a ()+ [A] distribute :: Monad m => [Scanl m a b] -> Scanl m a [b]+ [A] demuxIO :: (MonadIO m, Ord k) => (a -> k) -> (k -> m (Maybe (Scanl m a b))) -> Scanl m a (Maybe (k, b))+ [A] demux :: (Monad m, Ord k) => (a -> k) -> (k -> m (Maybe (Scanl m a b))) -> Scanl m a (Maybe (k, b))+ [A] deleteBy :: Monad m => (a -> a -> Bool) -> a -> Scanl m a (Maybe a)+ [A] countDistinctInt :: Monad m => Scanl m Int Int+ [A] countDistinct :: (Monad m, Ord a) => Scanl m a Int+ [A] classifyIO :: (MonadIO m, Ord k) => (a -> k) -> Scanl m a b -> Scanl m a (Maybe (k, b))+ [A] classify :: (MonadIO m, Ord k) => (a -> k) -> Scanl m a b -> Scanl m a (Maybe (k, b))+ [A] catRights :: Monad m => Scanl m b c -> Scanl m (Either a b) c+ [A] catMaybes :: Monad m => Scanl m a b -> Scanl m (Maybe a) b+ [A] catLefts :: Monad m => Scanl m a c -> Scanl m (Either a b) c+ [A] catEithers :: Scanl m a b -> Scanl m (Either a a) b+[A] Streamly.Data.RingArray+ [A] RingArray+ [A] unsafeGetIndex :: forall m a. (MonadIO m, Unbox a) => Int -> RingArray a -> m a+ [A] unsafeGetHead :: (MonadIO m, Unbox a) => RingArray a -> m a+ [A] toMutArray :: (MonadIO m, Unbox a) => RingArray a -> m (MutArray a)+ [A] toList :: (MonadIO m, Unbox a) => RingArray a -> m [a]+ [A] scanRingsOf :: forall m a. (MonadIO m, Unbox a) => Int -> Scanl m a (RingArray a)+ [A] ringsOf :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> Stream m (RingArray a)+ [A] replace_ :: forall m a. (MonadIO m, Unbox a) => RingArray a -> a -> m (RingArray a)+ [A] replace :: forall m a. (MonadIO m, Unbox a) => RingArray a -> a -> m (RingArray a, a)+ [A] readerRev :: forall m a. (MonadIO m, Unbox a) => Unfold m (RingArray a) a+ [A] reader :: forall m a. (MonadIO m, Unbox a) => Unfold m (RingArray a) a+ [A] readRev :: forall m a. (MonadIO m, Unbox a) => RingArray a -> Stream m a+ [A] read :: forall m a. (MonadIO m, Unbox a) => RingArray a -> Stream m a+ [A] putIndex :: forall m a. (MonadIO m, Unbox a) => Int -> RingArray a -> a -> m ()+ [A] moveReverse :: forall a. Unbox a => RingArray a -> RingArray a+ [A] moveForward :: forall a. Unbox a => RingArray a -> RingArray a+ [A] modifyIndex :: Int -> RingArray a -> (a -> (a, b)) -> m b+ [A] length :: forall a. Unbox a => RingArray a -> Int+ [A] insert :: RingArray a -> a -> m (RingArray a)+ [A] getIndex :: forall m a. (MonadIO m, Unbox a) => Int -> RingArray a -> m (Maybe a)+ [A] fold :: forall m a b. (MonadIO m, Unbox a) => Fold m a b -> RingArray a -> m b+ [A] eqArrayN :: RingArray a -> Array a -> Int -> IO Bool+ [A] eqArray :: RingArray a -> Array a -> IO Bool+ [A] createOfLast :: (Unbox a, MonadIO m) => Int -> Fold m a (RingArray a)+ [A] castMutArrayWith :: forall a. Unbox a => Int -> MutArray a -> Maybe (RingArray a)+ [A] castMutArray :: forall a. Unbox a => MutArray a -> Maybe (RingArray a)+ [A] cast :: forall a b. Unbox b => RingArray a -> Maybe (RingArray b)+ [A] byteLength :: RingArray a -> Int+ [A] asMutArray :: RingArray a -> (MutArray a, Int)+ [A] asBytes :: RingArray a -> RingArray Word8+[C] Streamly.Data.ParserK+ [D] adaptCG :: Monad m => Parser a m b -> ParserK (Array a) m b+ [D] adaptC :: (Monad m, Unbox a) => Parser a m b -> ParserK (Array a) m b+ [D] adapt :: Monad m => Parser a m b -> ParserK a m b+[C] Streamly.Data.Parser+ [A] ParseErrorPos+ [A] ParseErrorPos :: Int -> String -> ParseErrorPos+ [A] ParseError+ [A] ParseError :: String -> ParseError+[C] Streamly.Data.MutByteArray+ [D] pinnedNew :: Int -> IO MutByteArray+[C] Streamly.Data.MutArray.Generic+ [D] writeN :: MonadIO m => Int -> Fold m a (MutArray a)+ [D] write :: MonadIO m => Fold m a (MutArray a)+ [A] unsafePutIndex :: forall m a. MonadIO m => Int -> MutArray a -> a -> m ()+ [A] unsafeModifyIndex :: MonadIO m => Int -> MutArray a -> (a -> (a, b)) -> m b+ [A] unsafeGetIndex :: MonadIO m => Int -> MutArray a -> m a+ [D] putIndexUnsafe :: forall m a. MonadIO m => Int -> MutArray a -> a -> m ()+ [D] new :: MonadIO m => Int -> m (MutArray a)+ [D] modifyIndexUnsafe :: MonadIO m => Int -> MutArray a -> (a -> (a, b)) -> m b+ [D] getIndexUnsafe :: MonadIO m => Int -> MutArray a -> m a+ [A] chunksOf :: forall m a. MonadIO m => Int -> Stream m a -> Stream m (MutArray a)+[C] Streamly.Data.MutArray+ [D] writeN :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [D] writeAppendN :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a) -> Fold m a (MutArray a)+ [D] writeAppend :: forall m a. (MonadIO m, Unbox a) => m (MutArray a) -> Fold m a (MutArray a)+ [D] write :: forall m a. (MonadIO m, Unbox a) => Fold m a (MutArray a)+ [A] unsafePutIndex :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> a -> m ()+ [A] unsafeModifyIndex :: forall m a b. (MonadIO m, Unbox a) => Int -> MutArray a -> (a -> (a, b)) -> m b+ [A] unsafeGetIndex :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m a+ [D] putIndexUnsafe :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> a -> m ()+ [D] pinnedNew :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a)+ [D] pinnedEmptyOf :: (MonadIO m, Unbox a) => Int -> m (MutArray a)+ [D] new :: (MonadIO m, Unbox a) => Int -> m (MutArray a)+ [D] modifyIndexUnsafe :: forall m a b. (MonadIO m, Unbox a) => Int -> MutArray a -> (a -> (a, b)) -> m b+ [D] getIndexUnsafe :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m a+ [A] emptyOf' :: (MonadIO m, Unbox a) => Int -> m (MutArray a)+ [A] chunksOf :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> Stream m (MutArray a)+ [D] appendN :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a) -> Fold m a (MutArray a)+ [A] append2 :: (MonadIO m, Unbox a) => MutArray a -> Fold m a (MutArray a)+ [D] append :: forall m a. (MonadIO m, Unbox a) => m (MutArray a) -> Fold m a (MutArray a)+[C] Streamly.Data.Fold+ [A] takeEndBySeq_ :: forall m a b. (MonadIO m, Unbox a, Enum a, Eq a) => Array a -> Fold m a b -> Fold m a b+ [A] takeEndBySeq :: forall m a b. (MonadIO m, Unbox a, Enum a, Eq a) => Array a -> Fold m a b -> Fold m a b+ [A] scanl :: Monad m => Scanl m a b -> Fold m b c -> Fold m a c+ [D] scanMaybe :: Monad m => Fold m a (Maybe b) -> Fold m b c -> Fold m a c+ [D] scan :: Monad m => Fold m a b -> Fold m b c -> Fold m a c+ [A] postscanl :: Monad m => Scanl m a b -> Fold m b c -> Fold m a c+ [D] postscan :: Monad m => Fold m a b -> Fold m b c -> Fold m a c+ [A] foldtM' :: (s -> a -> m (Step s b)) -> m (Step s b) -> (s -> m b) -> Fold m a b+ [D] foldlM1' :: Monad m => (a -> a -> m a) -> Fold m a (Maybe a)+ [A] foldl1M' :: Monad m => (a -> a -> m a) -> Fold m a (Maybe a)+ [A] demuxerToMapIO :: (MonadIO m, Ord k) => (a -> k) -> (k -> m (Maybe (Fold m a b))) -> Fold m a (Map k b)+ [A] demuxerToMap :: (Monad m, Ord k) => (a -> k) -> (k -> m (Maybe (Fold m a b))) -> Fold m a (Map k b)+ [D] demuxToMapIO :: (MonadIO m, Ord k) => (a -> k) -> (a -> m (Fold m a b)) -> Fold m a (Map k b)+ [D] demuxToMap :: (Monad m, Ord k) => (a -> k) -> (a -> m (Fold m a b)) -> Fold m a (Map k b)+ [D] demuxIO :: (MonadIO m, Ord k) => (a -> k) -> (a -> m (Fold m a b)) -> Fold m a (m (Map k b), Maybe (k, b))+ [D] demux :: (Monad m, Ord k) => (a -> k) -> (a -> m (Fold m a b)) -> Fold m a (m (Map k b), Maybe (k, b))+ [D] classifyIO :: (MonadIO m, Ord k) => (a -> k) -> Fold m a b -> Fold m a (m (Map k b), Maybe (k, b))+ [D] classify :: (Monad m, Ord k) => (a -> k) -> Fold m a b -> Fold m a (m (Map k b), Maybe (k, b))+[C] Streamly.Data.Array.Generic+ [D] writeN :: MonadIO m => Int -> Fold m a (Array a)+ [D] write :: MonadIO m => Fold m a (Array a)+ [A] toParserK :: Monad m => Parser a m b -> ParserK (Array a) m b+ [A] parsePos :: Monad m => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseErrorPos b)+ [A] parseBreakPos :: forall m a b. Monad m => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseErrorPos b, StreamK m (Array a))+ [A] parseBreak :: forall m a b. Monad m => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b, StreamK m (Array a))+ [A] parse :: Monad m => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b)+ [A] chunksOf :: forall m a. MonadIO m => Int -> Stream m a -> Stream m (Array a)+[C] Streamly.Data.Array+ [D] writeN :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+ [D] writeLastN :: (Unbox a, MonadIO m) => Int -> Fold m a (Array a)+ [D] write :: forall m a. (MonadIO m, Unbox a) => Fold m a (Array a)+ [A] toParserK :: (Monad m, Unbox a) => Parser a m b -> ParserK (Array a) m b+ [A] serialize' :: Serialize a => a -> Array Word8+ [D] pinnedSerialize :: Serialize a => a -> Array Word8+ [A] parsePos :: (Monad m, Unbox a) => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseErrorPos b)+ [A] parseBreakPos :: (Monad m, Unbox a) => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseErrorPos b, StreamK m (Array a))+ [A] parseBreak :: (Monad m, Unbox a) => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b, StreamK m (Array a))+ [A] parse :: (Monad m, Unbox a) => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b)+ [C] deserialize+ [O] deserialize :: Serialize a => Array Word8 -> a+ [N] deserialize :: Serialize a => Array Word8 -> (a, Array Word8)+ [A] createOfLast :: (Unbox a, MonadIO m) => Int -> Fold m a (Array a)+ [A] chunksOf :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> Stream m (Array a)+[A] Streamly.Control.Exception+ [A] AcquireIO+ [A] withAcquireIO :: (MonadIO m, MonadMask m) => (AcquireIO -> m a) -> m a+ [A] register :: AcquireIO -> IO () -> IO ()+ [A] hook :: AcquireIO -> IO () -> IO (IO ())+ [A] acquire :: AcquireIO -> IO b -> (b -> IO c) -> IO (b, IO ())+[C] Streamly.Console.Stdio+ [A] readChunks :: MonadIO m => Stream m (Array Word8)+ [A] readChars :: MonadIO m => Stream m Char+ [A] read :: MonadIO m => Stream m Word8+ [A] putChunks :: MonadIO m => Stream m (Array Word8) -> m ()++---------------------------------+Internal API diff+---------------------------------++[C] Streamly.Internal.Unicode.Stream+ [A] swapByteOrder :: Word16 -> Word16+ [A] mkEvenW8Chunks :: Monad m => Stream m (Array Word8) -> Stream m (Array Word8)+ [C] encodeUtf16le'+ [O] encodeUtf16le' :: Stream m Char -> Stream m Word16+ [N] encodeUtf16le' :: Monad m => Stream m Char -> Stream m Word16+ [A] encodeUtf16le :: Monad m => Stream m Char -> Stream m Word16+ [C] decodeUtf16le'+ [O] decodeUtf16le' :: Stream m Word16 -> Stream m Char+ [N] decodeUtf16le' :: Monad m => Stream m Word16 -> Stream m Char+ [A] decodeUtf16le :: Monad m => Stream m Word16 -> Stream m Char+[A] Streamly.Internal.FileSystem.WindowsPath.SegNode+ [A] Streamly.Internal.Data.Path.IsPath+ [A] instance Streamly.Internal.Data.Path.IsPath Streamly.Internal.FileSystem.WindowsPath.WindowsPath (Streamly.Internal.FileSystem.WindowsPath.Seg.Unrooted (Streamly.Internal.FileSystem.WindowsPath.Node.File Streamly.Internal.FileSystem.WindowsPath.WindowsPath))+ [A] instance Streamly.Internal.Data.Path.IsPath Streamly.Internal.FileSystem.WindowsPath.WindowsPath (Streamly.Internal.FileSystem.WindowsPath.Seg.Unrooted (Streamly.Internal.FileSystem.WindowsPath.Node.Dir Streamly.Internal.FileSystem.WindowsPath.WindowsPath))+ [A] instance Streamly.Internal.Data.Path.IsPath Streamly.Internal.FileSystem.WindowsPath.WindowsPath (Streamly.Internal.FileSystem.WindowsPath.Seg.Rooted (Streamly.Internal.FileSystem.WindowsPath.Node.File Streamly.Internal.FileSystem.WindowsPath.WindowsPath))+ [A] instance Streamly.Internal.Data.Path.IsPath Streamly.Internal.FileSystem.WindowsPath.WindowsPath (Streamly.Internal.FileSystem.WindowsPath.Seg.Rooted (Streamly.Internal.FileSystem.WindowsPath.Node.Dir Streamly.Internal.FileSystem.WindowsPath.WindowsPath))+ [A] urfileE :: String -> Q Exp+ [A] urfile :: QuasiQuoter+ [A] urdirE :: String -> Q Exp+ [A] urdir :: QuasiQuoter+ [A] rtfileE :: String -> Q Exp+ [A] rtfile :: QuasiQuoter+ [A] rtdirE :: String -> Q Exp+ [A] rtdir :: QuasiQuoter+ [A] join :: (IsPath WindowsPath (a (Dir WindowsPath)), IsPath WindowsPath (b WindowsPath), IsPath WindowsPath (a (b WindowsPath))) => a (Dir WindowsPath) -> Unrooted (b WindowsPath) -> a (b WindowsPath)+[A] Streamly.Internal.FileSystem.WindowsPath.Seg+ [A] class IsSeg a+ [A] Streamly.Internal.FileSystem.WindowsPath.Seg.IsSeg+ [A] instance Streamly.Internal.FileSystem.WindowsPath.Seg.IsSeg (Streamly.Internal.FileSystem.WindowsPath.Seg.Unrooted a)+ [A] instance Streamly.Internal.FileSystem.WindowsPath.Seg.IsSeg (Streamly.Internal.FileSystem.WindowsPath.Seg.Rooted a)+ [A] Streamly.Internal.Data.Path.IsPath+ [A] instance Streamly.Internal.Data.Path.IsPath Streamly.Internal.FileSystem.WindowsPath.WindowsPath (Streamly.Internal.FileSystem.WindowsPath.Seg.Unrooted Streamly.Internal.FileSystem.WindowsPath.WindowsPath)+ [A] instance Streamly.Internal.Data.Path.IsPath Streamly.Internal.FileSystem.WindowsPath.WindowsPath (Streamly.Internal.FileSystem.WindowsPath.Seg.Rooted Streamly.Internal.FileSystem.WindowsPath.WindowsPath)+ [A] Unrooted+ [A] Unrooted :: a -> Unrooted a+ [A] Rooted+ [A] Rooted :: a -> Rooted a+ [A] urE :: String -> Q Exp+ [A] ur :: QuasiQuoter+ [A] rtE :: String -> Q Exp+ [A] rt :: QuasiQuoter+ [A] join :: (IsSeg (a WindowsPath), IsPath WindowsPath (a WindowsPath)) => a WindowsPath -> Unrooted WindowsPath -> a WindowsPath+[A] Streamly.Internal.FileSystem.WindowsPath.Node+ [A] class IsNode a+ [A] Streamly.Internal.FileSystem.WindowsPath.Node.IsNode+ [A] instance Streamly.Internal.FileSystem.WindowsPath.Node.IsNode (Streamly.Internal.FileSystem.WindowsPath.Node.File a)+ [A] instance Streamly.Internal.FileSystem.WindowsPath.Node.IsNode (Streamly.Internal.FileSystem.WindowsPath.Node.Dir a)+ [A] Streamly.Internal.Data.Path.IsPath+ [A] instance Streamly.Internal.Data.Path.IsPath Streamly.Internal.FileSystem.WindowsPath.WindowsPath (Streamly.Internal.FileSystem.WindowsPath.Node.File Streamly.Internal.FileSystem.WindowsPath.WindowsPath)+ [A] instance Streamly.Internal.Data.Path.IsPath Streamly.Internal.FileSystem.WindowsPath.WindowsPath (Streamly.Internal.FileSystem.WindowsPath.Node.Dir Streamly.Internal.FileSystem.WindowsPath.WindowsPath)+ [A] File+ [A] File :: a -> File a+ [A] Dir+ [A] Dir :: a -> Dir a+ [A] join :: (IsPath WindowsPath (a WindowsPath), IsNode (a WindowsPath)) => Dir WindowsPath -> a WindowsPath -> a WindowsPath+ [A] fileE :: String -> Q Exp+ [A] file :: QuasiQuoter+ [A] dirE :: String -> Q Exp+ [A] dir :: QuasiQuoter+[A] Streamly.Internal.FileSystem.WindowsPath+ [A] class IsPath a b+ [A] EqCfg+ [A] Streamly.Internal.Data.Path.IsPath+ [A] instance Streamly.Internal.Data.Path.IsPath Streamly.Internal.FileSystem.WindowsPath.WindowsPath Streamly.Internal.FileSystem.WindowsPath.WindowsPath+ [A] WindowsPath+ [A] WindowsPath :: Array Word16 -> WindowsPath+ [A] wordToChar :: Word16 -> Char+ [A] validatePath' :: MonadThrow m => Array Word16 -> m ()+ [A] validatePath :: MonadThrow m => Array Word16 -> m ()+ [A] unsafeJoinPaths :: [WindowsPath] -> WindowsPath+ [A] unsafeJoin :: WindowsPath -> WindowsPath -> WindowsPath+ [A] unsafeFromString :: [Char] -> WindowsPath+ [A] unsafeFromPath :: IsPath a b => a -> b+ [A] unsafeFromArray :: Array Word16 -> WindowsPath+ [A] toString_ :: WindowsPath -> [Char]+ [A] toString :: WindowsPath -> [Char]+ [A] toPath :: IsPath a b => b -> a+ [A] toChars_ :: Monad m => WindowsPath -> Stream m Char+ [A] toChars :: Monad m => WindowsPath -> Stream m Char+ [A] toArray :: WindowsPath -> Array Word16+ [A] takeFileName :: WindowsPath -> Maybe WindowsPath+ [A] takeFileBase :: WindowsPath -> Maybe WindowsPath+ [A] takeExtension :: WindowsPath -> Maybe WindowsPath+ [A] takeDirectory :: WindowsPath -> Maybe WindowsPath+ [A] splitRoot :: WindowsPath -> Maybe (WindowsPath, Maybe WindowsPath)+ [A] splitPath_ :: Monad m => WindowsPath -> Stream m WindowsPath+ [A] splitPath :: Monad m => WindowsPath -> Stream m WindowsPath+ [A] splitLast :: WindowsPath -> (Maybe WindowsPath, WindowsPath)+ [A] splitFirst :: WindowsPath -> (WindowsPath, Maybe WindowsPath)+ [A] splitFile :: WindowsPath -> Maybe (Maybe WindowsPath, WindowsPath)+ [A] splitExtension :: WindowsPath -> Maybe (WindowsPath, WindowsPath)+ [A] showArray :: WindowsPath -> [Char]+ [A] separator :: Word16+ [A] replaceExtension :: WindowsPath -> WindowsPath -> WindowsPath+ [A] readArray :: [Char] -> WindowsPath+ [A] pathE :: String -> Q Exp+ [A] path :: QuasiQuoter+ [A] normalize :: EqCfg -> WindowsPath -> WindowsPath+ [A] joinStr :: WindowsPath -> [Char] -> WindowsPath+ [A] joinDir :: WindowsPath -> WindowsPath -> WindowsPath+ [A] join :: WindowsPath -> WindowsPath -> WindowsPath+ [A] isValidPath' :: Array Word16 -> Bool+ [A] isValidPath :: Array Word16 -> Bool+ [A] isUnrooted :: WindowsPath -> Bool+ [A] isSeparator :: Word16 -> Bool+ [A] isRooted :: WindowsPath -> Bool+ [A] ignoreTrailingSeparators :: Bool -> EqCfg -> EqCfg+ [A] ignoreCase :: Bool -> EqCfg -> EqCfg+ [A] hasTrailingSeparator :: WindowsPath -> Bool+ [A] fromString_ :: [Char] -> WindowsPath+ [A] fromString :: MonadThrow m => [Char] -> m WindowsPath+ [A] fromPath :: (IsPath a b, MonadThrow m) => a -> m b+ [A] fromChars :: MonadThrow m => Stream Identity Char -> m WindowsPath+ [A] fromArray :: MonadThrow m => Array Word16 -> m WindowsPath+ [A] extSeparator :: Word16+ [A] eqPathBytes :: WindowsPath -> WindowsPath -> Bool+ [A] eqPath :: (EqCfg -> EqCfg) -> WindowsPath -> WindowsPath -> Bool+ [A] encodeString :: [Char] -> Array Word16+ [A] dropTrailingSeparators :: WindowsPath -> WindowsPath+ [A] dropExtension :: WindowsPath -> WindowsPath+ [A] charToWord :: Char -> Word16+ [A] asCWString :: WindowsPath -> (CWString -> IO a) -> IO a+ [A] allowRelativeEquality :: Bool -> EqCfg -> EqCfg+ [A] addTrailingSeparator :: WindowsPath -> WindowsPath+ [A] addExtension :: WindowsPath -> WindowsPath -> WindowsPath+ [A] adapt :: (MonadThrow m, IsPath WindowsPath a, IsPath WindowsPath b) => a -> m b+[A] Streamly.Internal.FileSystem.Windows.ReadDir+[A] Streamly.Internal.FileSystem.Windows.File+[A] Streamly.Internal.FileSystem.PosixPath.SegNode+ [A] Streamly.Internal.Data.Path.IsPath+ [A] instance Streamly.Internal.Data.Path.IsPath Streamly.Internal.FileSystem.PosixPath.PosixPath (Streamly.Internal.FileSystem.PosixPath.Seg.Unrooted (Streamly.Internal.FileSystem.PosixPath.Node.File Streamly.Internal.FileSystem.PosixPath.PosixPath))+ [A] instance Streamly.Internal.Data.Path.IsPath Streamly.Internal.FileSystem.PosixPath.PosixPath (Streamly.Internal.FileSystem.PosixPath.Seg.Unrooted (Streamly.Internal.FileSystem.PosixPath.Node.Dir Streamly.Internal.FileSystem.PosixPath.PosixPath))+ [A] instance Streamly.Internal.Data.Path.IsPath Streamly.Internal.FileSystem.PosixPath.PosixPath (Streamly.Internal.FileSystem.PosixPath.Seg.Rooted (Streamly.Internal.FileSystem.PosixPath.Node.File Streamly.Internal.FileSystem.PosixPath.PosixPath))+ [A] instance Streamly.Internal.Data.Path.IsPath Streamly.Internal.FileSystem.PosixPath.PosixPath (Streamly.Internal.FileSystem.PosixPath.Seg.Rooted (Streamly.Internal.FileSystem.PosixPath.Node.Dir Streamly.Internal.FileSystem.PosixPath.PosixPath))+ [A] urfileE :: String -> Q Exp+ [A] urfile :: QuasiQuoter+ [A] urdirE :: String -> Q Exp+ [A] urdir :: QuasiQuoter+ [A] rtfileE :: String -> Q Exp+ [A] rtfile :: QuasiQuoter+ [A] rtdirE :: String -> Q Exp+ [A] rtdir :: QuasiQuoter+ [A] join :: (IsPath PosixPath (a (Dir PosixPath)), IsPath PosixPath (b PosixPath), IsPath PosixPath (a (b PosixPath))) => a (Dir PosixPath) -> Unrooted (b PosixPath) -> a (b PosixPath)+[A] Streamly.Internal.FileSystem.PosixPath.Seg+ [A] class IsSeg a+ [A] Streamly.Internal.FileSystem.PosixPath.Seg.IsSeg+ [A] instance Streamly.Internal.FileSystem.PosixPath.Seg.IsSeg (Streamly.Internal.FileSystem.PosixPath.Seg.Unrooted a)+ [A] instance Streamly.Internal.FileSystem.PosixPath.Seg.IsSeg (Streamly.Internal.FileSystem.PosixPath.Seg.Rooted a)+ [A] Streamly.Internal.Data.Path.IsPath+ [A] instance Streamly.Internal.Data.Path.IsPath Streamly.Internal.FileSystem.PosixPath.PosixPath (Streamly.Internal.FileSystem.PosixPath.Seg.Unrooted Streamly.Internal.FileSystem.PosixPath.PosixPath)+ [A] instance Streamly.Internal.Data.Path.IsPath Streamly.Internal.FileSystem.PosixPath.PosixPath (Streamly.Internal.FileSystem.PosixPath.Seg.Rooted Streamly.Internal.FileSystem.PosixPath.PosixPath)+ [A] Unrooted+ [A] Unrooted :: a -> Unrooted a+ [A] Rooted+ [A] Rooted :: a -> Rooted a+ [A] urE :: String -> Q Exp+ [A] ur :: QuasiQuoter+ [A] rtE :: String -> Q Exp+ [A] rt :: QuasiQuoter+ [A] join :: (IsSeg (a PosixPath), IsPath PosixPath (a PosixPath)) => a PosixPath -> Unrooted PosixPath -> a PosixPath+[A] Streamly.Internal.FileSystem.PosixPath.Node+ [A] class IsNode a+ [A] Streamly.Internal.FileSystem.PosixPath.Node.IsNode+ [A] instance Streamly.Internal.FileSystem.PosixPath.Node.IsNode (Streamly.Internal.FileSystem.PosixPath.Node.File a)+ [A] instance Streamly.Internal.FileSystem.PosixPath.Node.IsNode (Streamly.Internal.FileSystem.PosixPath.Node.Dir a)+ [A] Streamly.Internal.Data.Path.IsPath+ [A] instance Streamly.Internal.Data.Path.IsPath Streamly.Internal.FileSystem.PosixPath.PosixPath (Streamly.Internal.FileSystem.PosixPath.Node.File Streamly.Internal.FileSystem.PosixPath.PosixPath)+ [A] instance Streamly.Internal.Data.Path.IsPath Streamly.Internal.FileSystem.PosixPath.PosixPath (Streamly.Internal.FileSystem.PosixPath.Node.Dir Streamly.Internal.FileSystem.PosixPath.PosixPath)+ [A] File+ [A] File :: a -> File a+ [A] Dir+ [A] Dir :: a -> Dir a+ [A] join :: (IsPath PosixPath (a PosixPath), IsNode (a PosixPath)) => Dir PosixPath -> a PosixPath -> a PosixPath+ [A] fileE :: String -> Q Exp+ [A] file :: QuasiQuoter+ [A] dirE :: String -> Q Exp+ [A] dir :: QuasiQuoter+[A] Streamly.Internal.FileSystem.PosixPath+ [A] class IsPath a b+ [A] EqCfg+ [A] Streamly.Internal.Data.Path.IsPath+ [A] instance Streamly.Internal.Data.Path.IsPath Streamly.Internal.FileSystem.PosixPath.PosixPath Streamly.Internal.FileSystem.PosixPath.PosixPath+ [A] PosixPath+ [A] PosixPath :: Array Word8 -> PosixPath+ [A] wordToChar :: Word8 -> Char+ [A] validatePath :: MonadThrow m => Array Word8 -> m ()+ [A] unsafeJoinPaths :: [PosixPath] -> PosixPath+ [A] unsafeJoin :: PosixPath -> PosixPath -> PosixPath+ [A] unsafeFromString :: [Char] -> PosixPath+ [A] unsafeFromPath :: IsPath a b => a -> b+ [A] unsafeFromArray :: Array Word8 -> PosixPath+ [A] toString_ :: PosixPath -> [Char]+ [A] toString :: PosixPath -> [Char]+ [A] toPath :: IsPath a b => b -> a+ [A] toChars_ :: Monad m => PosixPath -> Stream m Char+ [A] toChars :: Monad m => PosixPath -> Stream m Char+ [A] toArray :: PosixPath -> Array Word8+ [A] takeFileName :: PosixPath -> Maybe PosixPath+ [A] takeFileBase :: PosixPath -> Maybe PosixPath+ [A] takeExtension :: PosixPath -> Maybe PosixPath+ [A] takeDirectory :: PosixPath -> Maybe PosixPath+ [A] splitRoot :: PosixPath -> Maybe (PosixPath, Maybe PosixPath)+ [A] splitPath_ :: Monad m => PosixPath -> Stream m PosixPath+ [A] splitPath :: Monad m => PosixPath -> Stream m PosixPath+ [A] splitLast :: PosixPath -> (Maybe PosixPath, PosixPath)+ [A] splitFirst :: PosixPath -> (PosixPath, Maybe PosixPath)+ [A] splitFile :: PosixPath -> Maybe (Maybe PosixPath, PosixPath)+ [A] splitExtension :: PosixPath -> Maybe (PosixPath, PosixPath)+ [A] showArray :: PosixPath -> [Char]+ [A] separator :: Word8+ [A] replaceExtension :: PosixPath -> PosixPath -> PosixPath+ [A] readArray :: [Char] -> PosixPath+ [A] pathE :: String -> Q Exp+ [A] path :: QuasiQuoter+ [A] normalize :: EqCfg -> PosixPath -> PosixPath+ [A] joinStr :: PosixPath -> [Char] -> PosixPath+ [A] joinDir :: PosixPath -> PosixPath -> PosixPath+ [A] joinCStr' :: PosixPath -> CString -> IO PosixPath+ [A] joinCStr :: PosixPath -> CString -> IO PosixPath+ [A] join :: PosixPath -> PosixPath -> PosixPath+ [A] isValidPath :: Array Word8 -> Bool+ [A] isUnrooted :: PosixPath -> Bool+ [A] isSeparator :: Word8 -> Bool+ [A] isRooted :: PosixPath -> Bool+ [A] ignoreTrailingSeparators :: Bool -> EqCfg -> EqCfg+ [A] ignoreCase :: Bool -> EqCfg -> EqCfg+ [A] hasTrailingSeparator :: PosixPath -> Bool+ [A] fromString_ :: [Char] -> PosixPath+ [A] fromString :: MonadThrow m => [Char] -> m PosixPath+ [A] fromPath :: (IsPath a b, MonadThrow m) => a -> m b+ [A] fromChars :: MonadThrow m => Stream Identity Char -> m PosixPath+ [A] fromArray :: MonadThrow m => Array Word8 -> m PosixPath+ [A] extSeparator :: Word8+ [A] eqPathBytes :: PosixPath -> PosixPath -> Bool+ [A] eqPath :: (EqCfg -> EqCfg) -> PosixPath -> PosixPath -> Bool+ [A] encodeString :: [Char] -> Array Word8+ [A] dropTrailingSeparators :: PosixPath -> PosixPath+ [A] dropExtension :: PosixPath -> PosixPath+ [A] charToWord :: Char -> Word8+ [A] asCString :: PosixPath -> (CString -> IO a) -> IO a+ [A] allowRelativeEquality :: Bool -> EqCfg -> EqCfg+ [A] addTrailingSeparator :: PosixPath -> PosixPath+ [A] addExtension :: PosixPath -> PosixPath -> PosixPath+ [A] adapt :: (MonadThrow m, IsPath PosixPath a, IsPath PosixPath b) => a -> m b+[A] Streamly.Internal.FileSystem.Posix.ReadDir+ [A] DirStream+ [A] DirStream :: Ptr CDir -> DirStream+ [A] reader :: (MonadIO m, MonadCatch m) => Unfold m Path Path+ [A] readScanWith_ :: Scanl m (Path, CString) a -> (ReadOptions -> ReadOptions) -> Path -> Stream m a+ [A] readScanWith :: Scanl m (Path, CString, Ptr CDirent) a -> (ReadOptions -> ReadOptions) -> Path -> Stream m a+ [A] readPlusScanWith :: Scanl m (Path, CString, Ptr CStat) a -> (ReadOptions -> ReadOptions) -> Path -> Stream m a+ [A] readEitherChunks :: MonadIO m => (ReadOptions -> ReadOptions) -> [PosixPath] -> Stream m (Either [PosixPath] [PosixPath])+ [A] readEitherByteChunksAt :: MonadIO m => (ReadOptions -> ReadOptions) -> (PosixPath, [PosixPath]) -> Stream m (Either (PosixPath, [PosixPath]) (Array Word8))+ [A] readEitherByteChunks :: MonadIO m => (ReadOptions -> ReadOptions) -> [PosixPath] -> Stream m (Either [PosixPath] (Array Word8))+ [A] readDirStreamEither :: (ReadOptions -> ReadOptions) -> (PosixPath, DirStream) -> IO (Maybe (Either PosixPath PosixPath))+ [A] openDirStreamCString :: CString -> IO DirStream+ [A] openDirStream :: PosixPath -> IO DirStream+ [A] eitherReader :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) -> Unfold m Path (Either Path Path)+ [A] closeDirStream :: DirStream -> IO ()+[A] Streamly.Internal.FileSystem.Posix.File+ [A] ()+ [A] OpenFlags+ [A] OpenFlags :: CInt -> OpenFlags+ [A] withFile :: PosixPath -> IOMode -> (Handle -> IO r) -> IO r+ [A] withBinaryFile :: PosixPath -> IOMode -> (Handle -> IO r) -> IO r+ [A] setUx :: FileMode -> FileMode+ [A] setUw :: FileMode -> FileMode+ [A] setUr :: FileMode -> FileMode+ [A] setTrunc :: Bool -> OpenFlags -> OpenFlags+ [A] setSync :: Bool -> OpenFlags -> OpenFlags+ [A] setSuid :: FileMode -> FileMode+ [A] setSticky :: FileMode -> FileMode+ [A] setSgid :: FileMode -> FileMode+ [A] setOx :: FileMode -> FileMode+ [A] setOw :: FileMode -> FileMode+ [A] setOr :: FileMode -> FileMode+ [A] setNonBlock :: Bool -> OpenFlags -> OpenFlags+ [A] setNoFollow :: Bool -> OpenFlags -> OpenFlags+ [A] setNoCtty :: Bool -> OpenFlags -> OpenFlags+ [A] setGx :: FileMode -> FileMode+ [A] setGw :: FileMode -> FileMode+ [A] setGr :: FileMode -> FileMode+ [A] setExcl :: Bool -> OpenFlags -> OpenFlags+ [A] setDirectory :: Bool -> OpenFlags -> OpenFlags+ [A] setCloExec :: Bool -> OpenFlags -> OpenFlags+ [A] setAppend :: Bool -> OpenFlags -> OpenFlags+ [A] openFile :: PosixPath -> IOMode -> IO Handle+ [A] openBinaryFile :: PosixPath -> IOMode -> IO Handle+ [A] openAt :: Maybe Fd -> PosixPath -> OpenFlags -> Maybe FileMode -> IO Fd+ [A] defaultOpenFlags :: OpenFlags+ [A] defaultCreateMode :: FileMode+ [A] clrUx :: FileMode -> FileMode+ [A] clrUw :: FileMode -> FileMode+ [A] clrUr :: FileMode -> FileMode+ [A] clrSuid :: FileMode -> FileMode+ [A] clrSticky :: FileMode -> FileMode+ [A] clrSgid :: FileMode -> FileMode+ [A] clrOx :: FileMode -> FileMode+ [A] clrOw :: FileMode -> FileMode+ [A] clrOr :: FileMode -> FileMode+ [A] clrGx :: FileMode -> FileMode+ [A] clrGw :: FileMode -> FileMode+ [A] clrGr :: FileMode -> FileMode+ [A] close :: Fd -> IO ()+[A] Streamly.Internal.FileSystem.Posix.Errno+ [A] throwErrnoPathIfRetry :: (a -> Bool) -> String -> PosixPath -> IO a -> IO a+ [A] throwErrnoPathIfNullRetry :: String -> PosixPath -> IO (Ptr a) -> IO (Ptr a)+ [A] throwErrnoPathIfMinus1Retry :: (Eq a, Num a) => String -> PosixPath -> IO a -> IO a+ [A] throwErrnoPath :: String -> PosixPath -> IO a+[A] Streamly.Internal.FileSystem.Path.SegNode+[A] Streamly.Internal.FileSystem.Path.Seg+[A] Streamly.Internal.FileSystem.Path.Node+[C] Streamly.Internal.FileSystem.Path+ [C] IsPath+ [O] class IsPath a+ [N] class IsPath a b+ [R] Rel+ [R] File+ [A] EqCfg+ [R] Dir+ [R] Abs+ [R] Streamly.Internal.FileSystem.Path.IsPath+ [R] GHC.Show.Show+ [R] GHC.Exception.Type.Exception+ [R] GHC.Classes.Eq+ [R] Path+ [A] type Path = PosixPath+ [A] type OsWord = Word8+ [A] type OsCString = CString+ [A] wordToChar :: OsWord -> Char+ [A] validatePath :: MonadThrow m => Array OsWord -> m ()+ [A] unsafeJoinPaths :: [Path] -> Path+ [A] unsafeJoin :: Path -> Path -> Path+ [A] unsafeFromString :: [Char] -> Path+ [A] unsafeFromPath :: IsPath a b => a -> b+ [A] unsafeFromArray :: Array OsWord -> Path+ [A] toString_ :: Path -> [Char]+ [C] toPath+ [O] toPath :: IsPath a => a -> Path+ [N] toPath :: IsPath a b => b -> a+ [R] toChunk :: Path -> Array Word8+ [A] toChars_ :: Monad m => Path -> Stream m Char+ [A] toArray :: Path -> Array OsWord+ [A] takeFileName :: Path -> Maybe Path+ [A] takeFileBase :: Path -> Maybe Path+ [A] takeExtension :: Path -> Maybe Path+ [A] takeDirectory :: Path -> Maybe Path+ [A] splitRoot :: Path -> Maybe (Path, Maybe Path)+ [A] splitPath_ :: Monad m => Path -> Stream m Path+ [A] splitPath :: Monad m => Path -> Stream m Path+ [A] splitLast :: Path -> (Maybe Path, Path)+ [A] splitFirst :: Path -> (Path, Maybe Path)+ [A] splitFile :: Path -> Maybe (Maybe Path, Path)+ [A] splitExtension :: Path -> Maybe (Path, Path)+ [A] showArray :: Path -> [Char]+ [A] separator :: OsWord+ [A] replaceExtension :: Path -> Path -> Path+ [R] relfile :: QuasiQuoter+ [R] reldir :: QuasiQuoter+ [R] rel :: QuasiQuoter+ [A] readArray :: [Char] -> Path+ [R] primarySeparator :: Char+ [A] pathE :: String -> Q Exp+ [A] normalize :: EqCfg -> Path -> Path+ [R] mkRelFile :: String -> Q Exp+ [R] mkRelDir :: String -> Q Exp+ [R] mkRel :: String -> Q Exp+ [R] mkPath :: String -> Q Exp+ [R] mkFile :: String -> Q Exp+ [R] mkDir :: String -> Q Exp+ [R] mkAbsFile :: String -> Q Exp+ [R] mkAbsDir :: String -> Q Exp+ [R] mkAbs :: String -> Q Exp+ [A] joinStr :: Path -> [Char] -> Path+ [A] joinDir :: Path -> Path -> Path+ [A] joinCStr' :: Path -> CString -> IO Path+ [A] joinCStr :: Path -> CString -> IO Path+ [A] join :: Path -> Path -> Path+ [A] isValidPath :: Array OsWord -> Bool+ [A] isUnrooted :: Path -> Bool+ [C] isSeparator+ [O] isSeparator :: Char -> Bool+ [N] isSeparator :: OsWord -> Bool+ [A] isRooted :: Path -> Bool+ [A] ignoreTrailingSeparators :: Bool -> EqCfg -> EqCfg+ [A] ignoreCase :: Bool -> EqCfg -> EqCfg+ [A] hasTrailingSeparator :: Path -> Bool+ [A] fromString_ :: [Char] -> Path+ [R] fromPathUnsafe :: IsPath a => Path -> a+ [C] fromPath+ [O] fromPath :: (IsPath a, MonadThrow m) => Path -> m a+ [N] fromPath :: (IsPath a b, MonadThrow m) => a -> m b+ [R] fromChunkUnsafe :: Array Word8 -> Path+ [R] fromChunk :: MonadThrow m => Array Word8 -> m Path+ [A] fromArray :: MonadThrow m => Array OsWord -> m Path+ [R] file :: QuasiQuoter+ [R] extendPath :: Path -> Path -> Path+ [R] extendDir :: (IsPath (a (Dir Path)), IsPath b, IsPath (a b)) => a (Dir Path) -> Rel b -> a b+ [A] extSeparator :: OsWord+ [A] eqPathBytes :: Path -> Path -> Bool+ [A] eqPath :: (EqCfg -> EqCfg) -> Path -> Path -> Bool+ [A] encodeString :: [Char] -> Array OsWord+ [A] dropTrailingSeparators :: Path -> Path+ [A] dropExtension :: Path -> Path+ [R] dir :: QuasiQuoter+ [A] charToWord :: Char -> OsWord+ [A] asOsCString :: Path -> (OsCString -> IO a) -> IO a+ [A] allowRelativeEquality :: Bool -> EqCfg -> EqCfg+ [A] addTrailingSeparator :: Path -> Path+ [A] addExtension :: Path -> Path -> Path+ [R] adaptPath :: (MonadThrow m, IsPath a, IsPath b) => a -> m b+ [A] adapt :: (MonadThrow m, IsPath Path a, IsPath Path b) => a -> m b+ [R] absfile :: QuasiQuoter+ [R] absdir :: QuasiQuoter+ [R] abs :: QuasiQuoter+[C] Streamly.Internal.FileSystem.Handle+ [C] writeChunks+ [O] writeChunks :: MonadIO m => Handle -> Fold m (Array a) ()+ [N] writeChunks :: forall m (a :: Type). MonadIO m => Handle -> Fold m (Array a) ()+ [C] putChunks+ [O] putChunks :: MonadIO m => Handle -> Stream m (Array a) -> m ()+ [N] putChunks :: forall m (a :: Type). MonadIO m => Handle -> Stream m (Array a) -> m ()+ [C] putChunk+ [O] putChunk :: MonadIO m => Handle -> Array a -> m ()+ [N] putChunk :: forall m (a :: Type). MonadIO m => Handle -> Array a -> m ()+ [C] chunkWriter+ [O] chunkWriter :: MonadIO m => Refold m Handle (Array a) ()+ [N] chunkWriter :: forall m (a :: Type). MonadIO m => Refold m Handle (Array a) ()+[A] Streamly.Internal.FileSystem.FileIO+ [A] writeWith :: (MonadIO m, MonadCatch m) => Int -> Path -> Fold m Word8 ()+ [A] writeChunks :: (MonadIO m, MonadCatch m) => Path -> Fold m (Array a) ()+ [A] writeAppendWith :: (MonadIO m, MonadCatch m) => Int -> Path -> Stream m Word8 -> m ()+ [A] writeAppendChunks :: (MonadIO m, MonadCatch m) => Path -> Stream m (Array a) -> m ()+ [A] writeAppendArray :: Path -> Array a -> IO ()+ [A] writeAppend :: (MonadIO m, MonadCatch m) => Path -> Stream m Word8 -> m ()+ [A] write :: (MonadIO m, MonadCatch m) => Path -> Fold m Word8 ()+ [A] withFile :: (MonadIO m, MonadCatch m) => Path -> IOMode -> (Handle -> Stream m a) -> Stream m a+ [A] readerWith :: (MonadIO m, MonadCatch m) => Unfold m (Int, Path) Word8+ [A] reader :: (MonadIO m, MonadCatch m) => Unfold m Path Word8+ [A] readChunksWith :: (MonadIO m, MonadCatch m) => Int -> Path -> Stream m (Array Word8)+ [A] readChunks :: (MonadIO m, MonadCatch m) => Path -> Stream m (Array Word8)+ [A] read :: (MonadIO m, MonadCatch m) => Path -> Stream m Word8+ [A] putChunk :: Path -> Array a -> IO ()+ [A] fromChunks :: (MonadIO m, MonadCatch m) => Path -> Stream m (Array a) -> m ()+ [A] fromBytesWith :: (MonadIO m, MonadCatch m) => Int -> Path -> Stream m Word8 -> m ()+ [A] fromBytes :: (MonadIO m, MonadCatch m) => Path -> Stream m Word8 -> m ()+ [A] chunkReaderWith :: (MonadIO m, MonadCatch m) => Unfold m (Int, Path) (Array Word8)+ [A] chunkReaderFromToWith :: (MonadIO m, MonadCatch m) => Unfold m (Int, Int, Int, Path) (Array Word8)+ [A] chunkReader :: (MonadIO m, MonadCatch m) => Unfold m Path (Array Word8)+[A] Streamly.Internal.FileSystem.File.Common+ [A] withFile :: Bool -> (Path -> IOMode -> IO Handle) -> Path -> IOMode -> (Handle -> IO r) -> IO r+ [A] openFile :: Bool -> (Path -> IOMode -> IO Handle) -> Path -> IOMode -> IO Handle+[D] Streamly.Internal.FileSystem.File+ [C] writeChunks+ [O] writeChunks :: (MonadIO m, MonadCatch m) => FilePath -> Fold m (Array a) ()+ [N] writeChunks :: forall m (a :: Type). (MonadIO m, MonadCatch m) => FilePath -> Fold m (Array a) ()+ [C] writeAppendChunks+ [O] writeAppendChunks :: (MonadIO m, MonadCatch m) => FilePath -> Stream m (Array a) -> m ()+ [N] writeAppendChunks :: forall m (a :: Type). (MonadIO m, MonadCatch m) => FilePath -> Stream m (Array a) -> m ()+ [C] writeAppendArray+ [O] writeAppendArray :: FilePath -> Array a -> IO ()+ [N] writeAppendArray :: forall (a :: Type). FilePath -> Array a -> IO ()+ [C] putChunk+ [O] putChunk :: FilePath -> Array a -> IO ()+ [N] putChunk :: forall (a :: Type). FilePath -> Array a -> IO ()+ [C] fromChunks+ [O] fromChunks :: (MonadIO m, MonadCatch m) => FilePath -> Stream m (Array a) -> m ()+ [N] fromChunks :: forall m (a :: Type). (MonadIO m, MonadCatch m) => FilePath -> Stream m (Array a) -> m ()+[A] Streamly.Internal.FileSystem.DirIO+ [A] ReadOptions+ [A] [_ignoreENOENT] :: ReadOptions -> Bool+ [A] [_ignoreELOOP] :: ReadOptions -> Bool+ [A] [_ignoreEACCESS] :: ReadOptions -> Bool+ [A] [_followSymlinks] :: ReadOptions -> Bool+ [A] ReadOptions :: Bool -> Bool -> Bool -> Bool -> ReadOptions+ [A] reader :: (MonadIO m, MonadCatch m) => Unfold m Path Path+ [A] readFiles :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) -> Path -> Stream m Path+ [A] readEitherPaths :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) -> Path -> Stream m (Either Path Path)+ [A] readEitherChunks :: MonadIO m => (ReadOptions -> ReadOptions) -> [PosixPath] -> Stream m (Either [PosixPath] [PosixPath])+ [A] readEither :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) -> Path -> Stream m (Either Path Path)+ [A] readDirs :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) -> Path -> Stream m Path+ [A] read :: (MonadIO m, MonadCatch m) => Path -> Stream m Path+ [A] ignoreSymlinkLoops :: Bool -> ReadOptions -> ReadOptions+ [A] ignoreMissing :: Bool -> ReadOptions -> ReadOptions+ [A] ignoreInaccessible :: Bool -> ReadOptions -> ReadOptions+ [A] followSymlinks :: Bool -> ReadOptions -> ReadOptions+ [A] fileReader :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) -> Unfold m Path Path+ [A] eitherReaderPaths :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) -> Unfold m Path (Either Path Path)+ [A] eitherReader :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) -> Unfold m Path (Either Path Path)+ [A] dirReader :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) -> Unfold m Path Path+ [A] defaultReadOptions :: ReadOptions+[D] Streamly.Internal.FileSystem.Dir+[C] Streamly.Internal.Data.Unfold+ [A] zipRepeat :: Functor m => Unfold m a b -> Unfold m (c, a) (c, b)+ [A] zipArrowWithM :: Monad m => (b -> c -> m d) -> Unfold m a1 b -> Unfold m a2 c -> Unfold m (a1, a2) d+ [A] zipArrowWith :: Monad m => (b -> c -> d) -> Unfold m a1 b -> Unfold m a2 c -> Unfold m (a1, a2) d+ [A] unfoldEachInterleave :: Monad m => Unfold m a b -> Unfold m c a -> Unfold m c b+ [A] unfoldEach :: Monad m => Unfold m b c -> Unfold m a b -> Unfold m a c+ [A] supply :: a -> Unfold m a b -> Unfold m () b+ [A] scanlMany :: Monad m => Scanl m b c -> Unfold m a b -> Unfold m a c+ [A] scanl :: Monad m => Scanl m b c -> Unfold m a b -> Unfold m a c+ [D] scanMany :: Monad m => Fold m b c -> Unfold m a b -> Unfold m a c+ [D] scan :: Monad m => Fold m b c -> Unfold m a b -> Unfold m a c+ [A] repeat :: Applicative m => Unfold m a a+ [D] mapM2 :: Monad m => (a -> b -> m c) -> Unfold m a b -> Unfold m a c+ [D] map2 :: Functor m => (a -> b -> c) -> Unfold m a b -> Unfold m a c+ [D] manyInterleave :: Monad m => Unfold m a b -> Unfold m c a -> Unfold m c b+ [D] many2 :: Monad m => Unfold m (a, b) c -> Unfold m a b -> Unfold m a c+ [D] many :: Monad m => Unfold m b c -> Unfold m a b -> Unfold m a c+ [R] joinInnerGeneric :: Monad m => (b -> c -> Bool) -> Unfold m a b -> Unfold m a c -> Unfold m a (b, c)+ [A] interleave :: Monad m => Unfold m a c -> Unfold m b c -> Unfold m (a, b) c+ [A] innerJoin :: Monad m => (b -> c -> Bool) -> Unfold m a b -> Unfold m a c -> Unfold m a (b, c)+ [A] fromTuple :: Applicative m => Unfold m (a, a) a+ [A] fairCrossWithM :: Monad m => (b -> c -> m d) -> Unfold m a b -> Unfold m a c -> Unfold m a d+ [A] fairCrossWith :: Monad m => (b -> c -> d) -> Unfold m a b -> Unfold m a c -> Unfold m a d+ [A] fairCross :: Monad m => Unfold m a b -> Unfold m a c -> Unfold m a (b, c)+ [A] carry :: Functor m => Unfold m a b -> Unfold m a (a, b)+ [D] both :: a -> Unfold m a b -> Unfold m Void b+[C] Streamly.Internal.Data.StreamK+ [R] CrossStreamK+ [A] Nested+ [A] [unNested] :: Nested m a -> StreamK m a+ [A] Nested :: StreamK m a -> Nested m a+ [A] FairNested+ [A] [unFairNested] :: FairNested m a -> StreamK m a+ [A] FairNested :: StreamK m a -> FairNested m a+ [A] toParserK :: Monad m => Parser a m b -> ParserK a m b+ [A] tailNonEmpty :: StreamK m a -> StreamK m a+ [A] sortOn :: (Monad m, Ord b) => (a -> b) -> StreamK m a -> StreamK m a+ [A] parsePos :: Monad m => ParserK a m b -> StreamK m a -> m (Either ParseErrorPos b)+ [C] parseDBreak+ [O] parseDBreak :: Monad m => Parser a m b -> StreamK m a -> m (Either ParseError b, StreamK m a)+ [N] parseDBreak :: Monad m => Parser a m b -> StreamK m a -> m (Either ParseErrorPos b, StreamK m a)+ [C] parseD+ [O] parseD :: Monad m => Parser a m b -> StreamK m a -> m (Either ParseError b)+ [N] parseD :: Monad m => Parser a m b -> StreamK m a -> m (Either ParseErrorPos b)+ [D] parseChunksGeneric :: Monad m => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b)+ [D] parseChunks :: (Monad m, Unbox a) => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b)+ [A] parseBreakPos :: forall m a b. Monad m => ParserK a m b -> StreamK m a -> m (Either ParseErrorPos b, StreamK m a)+ [D] parseBreakChunksGeneric :: forall m a b. Monad m => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b, StreamK m (Array a))+ [D] parseBreakChunks :: (Monad m, Unbox a) => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b, StreamK m (Array a))+ [A] morphInner :: (Monad m, Monad n) => (forall x. m x -> n x) -> StreamK m a -> StreamK n a+ [D] mkCross :: StreamK m a -> Nested m a+ [A] mapMAccum :: (s -> a -> m (s, b)) -> m s -> StreamK m a -> StreamK m b+ [A] localReaderT :: (r -> r) -> StreamK (ReaderT r m) a -> StreamK (ReaderT r m) a+ [A] interleaveSepBy :: StreamK m a -> StreamK m a -> StreamK m a+ [D] interleaveMin :: StreamK m a -> StreamK m a -> StreamK m a+ [D] interleaveFst :: StreamK m a -> StreamK m a -> StreamK m a+ [A] interleaveEndBy' :: StreamK m a -> StreamK m a -> StreamK m a+ [A] initNonEmpty :: Stream m a -> Stream m a+ [D] hoist :: (Monad m, Monad n) => (forall x. m x -> n x) -> StreamK m a -> StreamK n a+ [A] headNonEmpty :: Monad m => StreamK m a -> m a+ [A] fairConcatMap :: (a -> StreamK m b) -> StreamK m a -> StreamK m b+ [A] fairConcatForM :: Monad m => StreamK m a -> (a -> m (StreamK m b)) -> StreamK m b+ [A] fairConcatFor :: StreamK m a -> (a -> StreamK m b) -> StreamK m b+ [A] concatMapMAccum :: (StreamK m b -> StreamK m b -> StreamK m b) -> (s -> a -> m (s, StreamK m b)) -> m s -> StreamK m a -> StreamK m b+ [A] concatForWithM :: Monad m => (StreamK m b -> StreamK m b -> StreamK m b) -> StreamK m a -> (a -> m (StreamK m b)) -> StreamK m b+ [A] concatForWith :: (StreamK m b -> StreamK m b -> StreamK m b) -> StreamK m a -> (a -> StreamK m b) -> StreamK m b+ [A] concatForM :: Monad m => StreamK m a -> (a -> m (StreamK m b)) -> StreamK m b+ [A] concatFor :: StreamK m a -> (a -> StreamK m b) -> StreamK m b+ [D] bindWith :: (StreamK m b -> StreamK m b -> StreamK m b) -> StreamK m a -> (a -> StreamK m b) -> StreamK m b+ [A] bfsConcatMap :: (a -> StreamK m b) -> StreamK m a -> StreamK m b+ [A] bfsConcatForM :: Monad m => StreamK m a -> (a -> m (StreamK m b)) -> StreamK m b+ [A] bfsConcatFor :: StreamK m a -> (a -> StreamK m b) -> StreamK m b+[C] Streamly.Internal.Data.Stream+ [A] FairUnfoldState+ [A] FairUnfoldNext :: o -> ([i] -> [i]) -> [i] -> FairUnfoldState o i+ [A] FairUnfoldInit :: o -> ([i] -> [i]) -> FairUnfoldState o i+ [A] FairUnfoldDrain :: ([i] -> [i]) -> [i] -> FairUnfoldState o i+ [R] CrossStream+ [A] Nested+ [A] [unNested] :: Nested m a -> Stream m a+ [A] Nested :: Stream m a -> Nested m a+ [A] withReaderT :: Monad m => (r2 -> r1) -> Stream (ReaderT r1 m) a -> Stream (ReaderT r2 m) a+ [A] withAcquireIO' :: AcquireIO -> (AcquireIO -> Stream m a) -> Stream m a+ [A] withAcquireIO :: (MonadIO m, MonadCatch m) => (AcquireIO -> Stream m a) -> Stream m a+ [C] usingStateT+ [O] usingStateT :: Monad m => m s -> (Stream (StateT s m) a -> Stream (StateT s m) a) -> Stream m a -> Stream m a+ [N] usingStateT :: Monad m => m s -> (Stream (StateT s m) a -> Stream (StateT s m) b) -> Stream m a -> Stream m b+ [R] unionWithStreamGenericBy :: MonadIO m => (a -> a -> Bool) -> Stream m a -> Stream m a -> Stream m a+ [R] unionWithStreamAscBy :: (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a+ [A] unionBy :: MonadIO m => (a -> a -> Bool) -> Stream m a -> Stream m a -> Stream m a+ [A] unfoldSched :: Monad m => Unfold m a b -> Stream m a -> Stream m b+ [D] unfoldRoundRobin :: Monad m => Unfold m a b -> Stream m a -> Stream m b+ [D] unfoldMany :: Monad m => Unfold m a b -> Stream m a -> Stream m b+ [D] unfoldIterateDfs :: Monad m => Unfold m a a -> Stream m a -> Stream m a+ [D] unfoldIterateBfsRev :: Monad m => Unfold m a a -> Stream m a -> Stream m a+ [D] unfoldIterateBfs :: Monad m => Unfold m a a -> Stream m a -> Stream m a+ [A] unfoldIterate :: Monad m => Unfold m a a -> Stream m a -> Stream m a+ [D] unfoldInterleave :: Monad m => Unfold m a b -> Stream m a -> Stream m b+ [A] unfoldEachSepBySeq :: Monad m => b -> Unfold m b c -> Stream m b -> Stream m c+ [A] unfoldEachSepByM :: Monad m => m c -> Unfold m b c -> Stream m b -> Stream m c+ [A] unfoldEachSepBy :: Monad m => c -> Unfold m b c -> Stream m b -> Stream m c+ [A] unfoldEachFoldBy :: Fold m b c -> Unfold m a b -> Stream m a -> Stream m c+ [A] unfoldEachEndBySeq :: Monad m => b -> Unfold m b c -> Stream m b -> Stream m c+ [A] unfoldEachEndByM :: Monad m => m c -> Unfold m b c -> Stream m b -> Stream m c+ [A] unfoldEachEndBy :: Monad m => c -> Unfold m b c -> Stream m b -> Stream m c+ [A] unfoldEach :: Monad m => Unfold m a b -> Stream m a -> Stream m b+ [A] unfoldCross :: Monad m => Unfold m (a, b) c -> Stream m a -> Stream m b -> Stream m c+ [C] unCross+ [O] unCross :: CrossStream m a -> Stream m a+ [N] unCross :: Nested m a -> Stream m a+ [R] transform :: Monad m => Pipe m a b -> Stream m a -> Stream m b+ [A] takeEndBy_ :: Monad m => (a -> Bool) -> Stream m a -> Stream m a+ [A] takeEndBySeq_ :: forall m a. (MonadIO m, Unbox a, Enum a, Eq a) => Array a -> Stream m a -> Stream m a+ [A] takeEndBySeq :: forall m a. (MonadIO m, Unbox a, Enum a, Eq a) => Array a -> Stream m a -> Stream m a+ [A] tailNonEmpty :: Monad m => Stream m a -> Stream m a+ [D] strideFromThen :: Monad m => Int -> Int -> Stream m a -> Stream m a+ [A] splitSepBy_ :: Monad m => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+ [A] splitSepBySeq_ :: forall m a b. (MonadIO m, Unbox a, Enum a, Eq a) => Array a -> Fold m a b -> Stream m a -> Stream m b+ [A] splitSepBySeqOneOf :: [Array a] -> Fold m a b -> Stream m a -> Stream m b+ [R] splitOnSuffixSeqAny :: [Array a] -> Fold m a b -> Stream m a -> Stream m b+ [C] splitOnSuffixSeq+ [O] splitOnSuffixSeq :: forall m a b. (MonadIO m, Storable a, Unbox a, Enum a, Eq a) => Bool -> Array a -> Fold m a b -> Stream m a -> Stream m b+ [N] splitOnSuffixSeq :: forall m a b. (MonadIO m, Unbox a, Enum a, Eq a) => Bool -> Array a -> Fold m a b -> Stream m a -> Stream m b+ [D] splitOnSeq :: forall m a b. (MonadIO m, Unbox a, Enum a, Eq a) => Array a -> Fold m a b -> Stream m a -> Stream m b+ [R] splitOnPrefix :: (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+ [R] splitOnAny :: [Array a] -> Fold m a b -> Stream m a -> Stream m b+ [D] splitOn :: Monad m => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+ [A] splitEndBySeq_ :: forall m a b. (MonadIO m, Unbox a, Enum a, Eq a) => Array a -> Fold m a b -> Stream m a -> Stream m b+ [A] splitEndBySeqOneOf :: [Array a] -> Fold m a b -> Stream m a -> Stream m b+ [A] splitEndBySeq :: forall m a b. (MonadIO m, Unbox a, Enum a, Eq a) => Array a -> Fold m a b -> Stream m a -> Stream m b+ [A] splitBeginBy_ :: (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+ [A] splitAt :: String -> Int -> [a] -> ([a], [a])+ [A] sortedUnionBy :: (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a+ [A] sortedIntersectBy :: Monad m => (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a+ [A] sortedDeleteFirstsBy :: (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a+ [R] slicesBy :: Monad m => (a -> Bool) -> Stream m a -> Stream m (Int, Int)+ [A] schedMapM :: Monad m => (a -> m (Stream m b)) -> Stream m a -> Stream m b+ [A] schedMap :: Monad m => (a -> Stream m b) -> Stream m a -> Stream m b+ [A] schedForM :: Monad m => Stream m a -> (a -> m (Stream m b)) -> Stream m b+ [A] schedFor :: Monad m => Stream m a -> (a -> Stream m b) -> Stream m b+ [A] scanr :: Monad m => Scanr m a b -> Stream m a -> Stream m b+ [A] scanlMany :: Monad m => Scanl m a b -> Stream m a -> Stream m b+ [A] scanlBy :: Monad m => (b -> a -> b) -> b -> Stream m a -> Stream m b+ [C] scanl+ [O] scanl :: Monad m => (b -> a -> b) -> b -> Stream m a -> Stream m b+ [N] scanl :: Monad m => Scanl m a b -> Stream m a -> Stream m b+ [D] scanMaybe :: Monad m => Fold m a (Maybe b) -> Stream m a -> Stream m b+ [D] scanMany :: Monad m => Fold m a b -> Stream m a -> Stream m b+ [D] scan :: Monad m => Fold m a b -> Stream m a -> Stream m b+ [A] sampleFromThen :: Monad m => Int -> Int -> Stream m a -> Stream m a+ [D] reduceIterateBfs :: Monad m => (a -> a -> m a) -> Stream m a -> m (Maybe a)+ [A] postscanlMaybe :: Monad m => Scanl m a (Maybe b) -> Stream m a -> Stream m b+ [A] postscanlBy :: Monad m => (a -> b -> a) -> a -> Stream m b -> Stream m a+ [C] postscanl+ [O] postscanl :: Monad m => (a -> b -> a) -> a -> Stream m b -> Stream m a+ [N] postscanl :: Monad m => Scanl m a b -> Stream m a -> Stream m b+ [D] postscan :: Monad m => Fold m a b -> Stream m a -> Stream m b+ [A] pipe :: Monad m => Pipe m a b -> Stream m a -> Stream m b+ [A] parsePos :: Monad m => Parser a m b -> Stream m a -> m (Either ParseErrorPos b)+ [A] parseManyPos :: Monad m => Parser a m b -> Stream m a -> Stream m (Either ParseErrorPos b)+ [D] parseManyD :: Monad m => Parser a m b -> Stream m a -> Stream m (Either ParseError b)+ [A] parseIteratePos :: Monad m => (b -> Parser a m b) -> b -> Stream m a -> Stream m (Either ParseErrorPos b)+ [D] parseIterateD :: Monad m => (b -> Parser a m b) -> b -> Stream m a -> Stream m (Either ParseError b)+ [D] parseD :: Monad m => Parser a m b -> Stream m a -> m (Either ParseError b)+ [A] parseBreakPos :: Monad m => Parser a m b -> Stream m a -> m (Either ParseErrorPos b, Stream m a)+ [D] parseBreakD :: Monad m => Parser a m b -> Stream m a -> m (Either ParseError b, Stream m a)+ [A] outerSortedJoin :: (a -> b -> Ordering) -> Stream m a -> Stream m b -> Stream m (Maybe a, Maybe b)+ [A] outerOrdJoin :: (Ord k, MonadIO m) => Stream m (k, a) -> Stream m (k, b) -> Stream m (k, Maybe a, Maybe b)+ [A] outerJoin :: MonadIO m => (a -> b -> Bool) -> Stream m a -> Stream m b -> Stream m (Maybe a, Maybe b)+ [A] ordNub :: (Monad m, Ord a) => Stream m a -> Stream m a+ [R] nub :: (Monad m, Ord a) => Stream m a -> Stream m a+ [D] mkCross :: Stream m a -> Nested m a+ [A] loopBy :: Monad m => Unfold m x b -> x -> Stream m a -> Stream m (a, b)+ [A] loop :: Monad m => Stream m b -> Stream m a -> Stream m (a, b)+ [A] localReaderT :: Monad m => (r -> r) -> Stream (ReaderT r m) a -> Stream (ReaderT r m) a+ [A] leftSortedJoin :: (a -> b -> Ordering) -> Stream m a -> Stream m b -> Stream m (a, Maybe b)+ [A] leftOrdJoin :: (Ord k, Monad m) => Stream m (k, a) -> Stream m (k, b) -> Stream m (k, a, Maybe b)+ [A] leftJoin :: Monad m => (a -> b -> Bool) -> Stream m a -> Stream m b -> Stream m (a, Maybe b)+ [R] joinOuterGeneric :: MonadIO m => (a -> b -> Bool) -> Stream m a -> Stream m b -> Stream m (Maybe a, Maybe b)+ [R] joinOuterAscBy :: (a -> b -> Ordering) -> Stream m a -> Stream m b -> Stream m (Maybe a, Maybe b)+ [R] joinOuter :: (Ord k, MonadIO m) => Stream m (k, a) -> Stream m (k, b) -> Stream m (k, Maybe a, Maybe b)+ [R] joinLeftGeneric :: Monad m => (a -> b -> Bool) -> Stream m a -> Stream m b -> Stream m (a, Maybe b)+ [R] joinLeftAscBy :: (a -> b -> Ordering) -> Stream m a -> Stream m b -> Stream m (a, Maybe b)+ [R] joinLeft :: (Ord k, Monad m) => Stream m (k, a) -> Stream m (k, b) -> Stream m (k, a, Maybe b)+ [R] joinInnerGeneric :: Monad m => (a -> b -> Bool) -> Stream m a -> Stream m b -> Stream m (a, b)+ [R] joinInnerAscBy :: (a -> b -> Ordering) -> Stream m a -> Stream m b -> Stream m (a, b)+ [R] joinInner :: (Monad m, Ord k) => Stream m (k, a) -> Stream m (k, b) -> Stream m (k, a, b)+ [C] isInfixOf+ [O] isInfixOf :: (MonadIO m, Eq a, Enum a, Storable a, Unbox a) => Stream m a -> Stream m a -> m Bool+ [N] isInfixOf :: (MonadIO m, Eq a, Enum a, Unbox a) => Stream m a -> Stream m a -> m Bool+ [R] intersperseMWith :: Int -> m a -> Stream m a -> Stream m a+ [D] intersperseMSuffix_ :: Monad m => m b -> Stream m a -> Stream m a+ [D] intersperseMSuffixWith :: forall m a. Monad m => Int -> m a -> Stream m a -> Stream m a+ [D] intersperseMSuffix :: forall m a. Monad m => m a -> Stream m a -> Stream m a+ [D] intersperseMPrefix_ :: Monad m => m b -> Stream m a -> Stream m a+ [A] intersperseEveryM :: Int -> m a -> Stream m a -> Stream m a+ [A] intersperseEndByM_ :: Monad m => m b -> Stream m a -> Stream m a+ [A] intersperseEndByM :: forall m a. Monad m => m a -> Stream m a -> Stream m a+ [A] intersperseEndByEveryM :: forall m a. Monad m => Int -> m a -> Stream m a -> Stream m a+ [A] intersperseBeginByM_ :: Monad m => m b -> Stream m a -> Stream m a+ [R] intersectBySorted :: Monad m => (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a+ [A] intersectBy :: Monad m => (a -> a -> Bool) -> Stream m a -> Stream m a -> Stream m a+ [D] interposeSuffixM :: Monad m => m c -> Unfold m b c -> Stream m b -> Stream m c+ [D] interposeSuffix :: Monad m => c -> Unfold m b c -> Stream m b -> Stream m c+ [D] interposeM :: Monad m => m c -> Unfold m b c -> Stream m b -> Stream m c+ [D] interpose :: Monad m => c -> Unfold m b c -> Stream m b -> Stream m c+ [A] interleaveSepBy' :: Monad m => Stream m a -> Stream m a -> Stream m a+ [A] interleaveSepBy :: Monad m => Stream m a -> Stream m a -> Stream m a+ [D] interleaveMin :: Monad m => Stream m a -> Stream m a -> Stream m a+ [D] interleaveFstSuffix :: Monad m => Stream m a -> Stream m a -> Stream m a+ [D] interleaveFst :: Monad m => Stream m a -> Stream m a -> Stream m a+ [A] interleaveEndBy' :: Monad m => Stream m a -> Stream m a -> Stream m a+ [A] interleaveEndBy :: Monad m => Stream m a -> Stream m a -> Stream m a+ [A] interleaveBeginBy :: Stream m a -> Stream m a -> Stream m a+ [D] intercalateSuffix :: Monad m => Unfold m b c -> b -> Stream m b -> Stream m c+ [A] intercalateSepBy :: Monad m => Unfold m b c -> Stream m b -> Unfold m a c -> Stream m a -> Stream m c+ [A] intercalateEndBy :: Monad m => Unfold m a c -> Stream m a -> Unfold m b c -> Stream m b -> Stream m c+ [D] intercalate :: Monad m => Unfold m b c -> b -> Stream m b -> Stream m c+ [A] innerSortedJoin :: (a -> b -> Ordering) -> Stream m a -> Stream m b -> Stream m (a, b)+ [A] innerOrdJoin :: (Monad m, Ord k) => Stream m (k, a) -> Stream m (k, b) -> Stream m (k, a, b)+ [A] innerJoin :: Monad m => (a -> b -> Bool) -> Stream m a -> Stream m b -> Stream m (a, b)+ [A] initNonEmpty :: Monad m => Stream m a -> Stream m a+ [A] init :: Monad m => Stream m a -> m (Maybe (Stream m a))+ [D] indexOnSuffix :: Monad m => (a -> Bool) -> Stream m a -> Stream m (Int, Int)+ [A] indexEndBy_ :: Monad m => (a -> Bool) -> Stream m a -> Stream m (Int, Int)+ [A] indexEndBy :: Monad m => (a -> Bool) -> Stream m a -> Stream m (Int, Int)+ [D] gintercalateSuffix :: Monad m => Unfold m a c -> Stream m a -> Unfold m b c -> Stream m b -> Stream m c+ [D] gintercalate :: Monad m => Unfold m a c -> Stream m a -> Unfold m b c -> Stream m b -> Stream m c+ [A] fromW16CString# :: Monad m => Addr# -> Stream m Word16+ [A] fromCString# :: Monad m => Addr# -> Stream m Word8+ [D] fromByteStr# :: Monad m => Addr# -> Stream m Word8+ [A] foldManySepBy :: Fold m a b -> Fold m a b -> Stream m a -> Stream m b+ [R] foldIterateBfs :: Fold m a (Either a a) -> Stream m a -> m (Maybe a)+ [A] finallyIO'' :: (MonadIO m, MonadCatch m) => AcquireIO -> IO b -> Stream m a -> Stream m a+ [A] finallyIO' :: MonadIO m => AcquireIO -> IO b -> Stream m a -> Stream m a+ [R] filterInStreamGenericBy :: Monad m => (a -> a -> Bool) -> Stream m a -> Stream m a -> Stream m a+ [R] filterInStreamAscBy :: Monad m => (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a+ [A] fairUnfoldSched :: Monad m => Unfold m a b -> Stream m a -> Stream m b+ [A] fairUnfoldEach :: Monad m => Unfold m a b -> Stream m a -> Stream m b+ [A] fairSchedMapM :: Monad m => (a -> m (Stream m b)) -> Stream m a -> Stream m b+ [A] fairSchedMap :: Monad m => (a -> Stream m b) -> Stream m a -> Stream m b+ [A] fairSchedForM :: Monad m => Stream m a -> (a -> m (Stream m b)) -> Stream m b+ [A] fairSchedFor :: Monad m => Stream m a -> (a -> Stream m b) -> Stream m b+ [A] fairCrossWithM :: Monad m => (a -> b -> m c) -> Stream m a -> Stream m b -> Stream m c+ [A] fairCrossWith :: Monad m => (a -> b -> c) -> Stream m a -> Stream m b -> Stream m c+ [A] fairCross :: Monad m => Stream m a -> Stream m b -> Stream m (a, b)+ [A] fairConcatMapM :: Monad m => (a -> m (Stream m b)) -> Stream m a -> Stream m b+ [A] fairConcatMap :: Monad m => (a -> Stream m b) -> Stream m a -> Stream m b+ [A] fairConcatForM :: Monad m => Stream m a -> (a -> m (Stream m b)) -> Stream m b+ [A] fairConcatFor :: Monad m => Stream m a -> (a -> Stream m b) -> Stream m b+ [R] deleteInStreamGenericBy :: Monad m => (a -> a -> Bool) -> Stream m a -> Stream m a -> Stream m a+ [R] deleteInStreamAscBy :: (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a+ [A] deleteFirstsBy :: Monad m => (a -> a -> Bool) -> Stream m a -> Stream m a -> Stream m a+ [D] concatIterateDfs :: Monad m => (a -> Maybe (Stream m a)) -> Stream m a -> Stream m a+ [D] concatIterateBfsRev :: Monad m => (a -> Maybe (Stream m a)) -> Stream m a -> Stream m a+ [D] concatIterateBfs :: Monad m => (a -> Maybe (Stream m a)) -> Stream m a -> Stream m a+ [A] concatIterate :: Monad m => (a -> Maybe (Stream m a)) -> Stream m a -> Stream m a+ [A] concatForM :: Monad m => Stream m a -> (a -> m (Stream m b)) -> Stream m b+ [A] concatFor :: Monad m => Stream m a -> (a -> Stream m b) -> Stream m b+ [A] bracketIO'' :: (MonadIO m, MonadCatch m) => AcquireIO -> IO b -> (b -> IO c) -> (b -> Stream m a) -> Stream m a+ [A] bracketIO' :: MonadIO m => AcquireIO -> IO b -> (b -> IO c) -> (b -> Stream m a) -> Stream m a+ [A] bfsUnfoldIterate :: Monad m => Unfold m a a -> Stream m a -> Stream m a+ [A] bfsUnfoldEach :: Monad m => Unfold m a b -> Stream m a -> Stream m b+ [A] bfsReduceIterate :: Monad m => (a -> a -> m a) -> Stream m a -> m (Maybe a)+ [A] bfsFoldIterate :: Fold m a (Either a a) -> Stream m a -> m (Maybe a)+ [A] bfsConcatIterate :: Monad m => (a -> Maybe (Stream m a)) -> Stream m a -> Stream m a+ [A] altBfsUnfoldIterate :: Monad m => Unfold m a a -> Stream m a -> Stream m a+ [A] altBfsUnfoldEach :: Monad m => Unfold m a b -> Stream m a -> Stream m b+ [A] altBfsConcatIterate :: Monad m => (a -> Maybe (Stream m a)) -> Stream m a -> Stream m a+[A] Streamly.Internal.Data.Scanr+ [A] Scanr+ [A] Scanr :: (s -> a -> m (Step s b)) -> s -> Scanr m a b+ [A] GHC.Base.Functor+ [A] instance GHC.Base.Functor m => GHC.Base.Functor (Streamly.Internal.Data.Scanr.Scanr m a)+ [A] GHC.Base.Applicative+ [A] instance GHC.Base.Monad m => GHC.Base.Applicative (Streamly.Internal.Data.Scanr.Scanr m a)+ [A] Control.Category.Category+ [A] instance GHC.Base.Monad m => Control.Category.Category (Streamly.Internal.Data.Scanr.Scanr m)+ [A] Control.Arrow.Arrow+ [A] instance GHC.Base.Monad m => Control.Arrow.Arrow (Streamly.Internal.Data.Scanr.Scanr m)+ [A] teeWithMay :: Monad m => (Maybe b -> Maybe c -> d) -> Scanr m a b -> Scanr m a c -> Scanr m a d+ [A] teeWith :: Monad m => (b -> c -> d) -> Scanr m a b -> Scanr m a c -> Scanr m a d+ [A] tee :: Monad m => Scanr m a b -> Scanr m a c -> Scanr m a (b, c)+ [A] sum :: (Monad m, Num a) => Scanr m a a+ [A] length :: Monad m => Scanr m a Int+ [A] identity :: Monad m => Scanr m a a+ [A] functionM :: Monad m => (a -> m b) -> Scanr m a b+ [A] function :: Monad m => (a -> b) -> Scanr m a b+ [A] filterM :: Monad m => (a -> m Bool) -> Scanr m a a+ [A] filter :: Monad m => (a -> Bool) -> Scanr m a a+ [A] compose :: Monad m => Scanr m b c -> Scanr m a b -> Scanr m a c+[A] Streamly.Internal.Data.Scanl+ [A] Step+ [A] Partial :: !s -> Step s b+ [A] Done :: !b -> Step s b+ [A] Scanl+ [A] Scanl :: (s -> a -> m (Step s b)) -> m (Step s b) -> (s -> m b) -> (s -> m b) -> Scanl m a b+ [A] Incr+ [A] Replace :: !a -> !a -> Incr a+ [A] Insert :: !a -> Incr a+ [A] zipStreamWithM :: (a -> b -> m c) -> Stream m a -> Scanl m c x -> Scanl m b x+ [A] zipStream :: Monad m => Stream m a -> Scanl m (a, b) x -> Scanl m b x+ [A] with :: (Scanl m (s, a) b -> Scanl m a b) -> (((s, a) -> c) -> Scanl m (s, a) b -> Scanl m (s, a) b) -> ((s, a) -> c) -> Scanl m a b -> Scanl m a b+ [A] windowRange :: forall m a. (MonadIO m, Unbox a, Ord a) => Int -> Scanl m a (Maybe (a, a))+ [A] windowMinimum :: (MonadIO m, Unbox a, Ord a) => Int -> Scanl m a (Maybe a)+ [A] windowMaximum :: (MonadIO m, Unbox a, Ord a) => Int -> Scanl m a (Maybe a)+ [A] unzipWithM :: Monad m => (a -> m (b, c)) -> Scanl m b x -> Scanl m c y -> Scanl m a (x, y)+ [A] unzipWith :: Monad m => (a -> (b, c)) -> Scanl m b x -> Scanl m c y -> Scanl m a (x, y)+ [A] unzip :: Monad m => Scanl m a x -> Scanl m b y -> Scanl m (a, b) (x, y)+ [A] uniqBy :: Monad m => (a -> a -> Bool) -> Scanl m a (Maybe a)+ [A] uniq :: (Monad m, Eq a) => Scanl m a (Maybe a)+ [A] unfoldMany :: Monad m => Unfold m a b -> Scanl m b c -> Scanl m a c+ [A] topBy :: (MonadIO m, Unbox a) => (a -> a -> Ordering) -> Int -> Scanl m a (MutArray a)+ [A] top :: (MonadIO m, Unbox a, Ord a) => Int -> Scanl m a (MutArray a)+ [A] toStreamRev :: (Monad m, Monad n) => Scanl m a (Stream n a)+ [A] toStreamKRev :: Monad m => Scanl m a (StreamK n a)+ [A] toStreamK :: Monad m => Scanl m a (StreamK n a)+ [A] toStream :: (Monad m, Monad n) => Scanl m a (Stream n a)+ [A] toSet :: (Monad m, Ord a) => Scanl m a (Set a)+ [A] toListRev :: Monad m => Scanl m a [a]+ [A] toList :: Monad m => Scanl m a [a]+ [A] toIntSet :: Monad m => Scanl m Int IntSet+ [A] the :: (Monad m, Eq a) => Scanl m a (Maybe a)+ [A] teeWith :: Monad m => (b -> c -> d) -> Scanl m a b -> Scanl m a c -> Scanl m a d+ [A] tee :: Monad m => Scanl m a b -> Scanl m a c -> Scanl m a (b, c)+ [A] takingEndBy_ :: Monad m => (a -> Bool) -> Scanl m a (Maybe a)+ [A] takingEndByM_ :: Monad m => (a -> m Bool) -> Scanl m a (Maybe a)+ [A] takingEndByM :: Monad m => (a -> m Bool) -> Scanl m a (Maybe a)+ [A] takingEndBy :: Monad m => (a -> Bool) -> Scanl m a (Maybe a)+ [A] taking :: Monad m => Int -> Scanl m a (Maybe a)+ [A] takeEndBy_ :: Monad m => (a -> Bool) -> Scanl m a b -> Scanl m a b+ [A] takeEndBy :: Monad m => (a -> Bool) -> Scanl m a b -> Scanl m a b+ [A] take :: Monad m => Int -> Scanl m a b -> Scanl m a b+ [A] sum :: (Monad m, Num a) => Scanl m a a+ [A] sconcat :: (Monad m, Semigroup a) => a -> Scanl m a a+ [A] scanlMany :: Monad m => Scanl m a b -> Scanl m b c -> Scanl m a c+ [A] scanl :: Monad m => Scanl m a b -> Scanl m b c -> Scanl m a c+ [A] sampleFromthen :: Monad m => Int -> Int -> Scanl m a b -> Scanl m a b+ [A] rollingMapM :: Monad m => (Maybe a -> a -> m b) -> Scanl m a b+ [A] rollingMap :: Monad m => (Maybe a -> a -> b) -> Scanl m a b+ [A] rollingHashWithSalt :: (Monad m, Enum a) => Int64 -> Scanl m a Int64+ [A] rollingHashFirstN :: (Monad m, Enum a) => Int -> Scanl m a Int64+ [A] rollingHash :: (Monad m, Enum a) => Scanl m a Int64+ [A] rmapM :: Monad m => (b -> m c) -> Scanl m a b -> Scanl m a c+ [A] repeated :: Scanl m a (Maybe a)+ [A] rangeBy :: Monad m => (a -> a -> Ordering) -> Scanl m a (Maybe (a, a))+ [A] range :: (Monad m, Ord a) => Scanl m a (Maybe (a, a))+ [A] prune :: (a -> Bool) -> Scanl m a (Maybe a)+ [A] product :: (Monad m, Num a, Eq a) => Scanl m a a+ [A] postscanlMaybe :: Monad m => Scanl m a (Maybe b) -> Scanl m b c -> Scanl m a c+ [A] postscanl :: Monad m => Scanl m a b -> Scanl m b c -> Scanl m a c+ [A] pipe :: Monad m => Pipe m a b -> Scanl m b c -> Scanl m a c+ [A] partitionByM :: Monad m => (a -> m (Either b c)) -> Scanl m b x -> Scanl m c x -> Scanl m a x+ [A] partitionBy :: Monad m => (a -> Either b c) -> Scanl m b x -> Scanl m c x -> Scanl m a x+ [A] partition :: Monad m => Scanl m b x -> Scanl m c x -> Scanl m (Either b c) x+ [A] nubInt :: Monad m => Scanl m Int (Maybe Int)+ [A] nub :: (Monad m, Ord a) => Scanl m a (Maybe a)+ [A] morphInner :: (forall x. m x -> n x) -> Scanl m a b -> Scanl n a b+ [A] mkScantM :: (s -> a -> m (Step s b)) -> m (Step s b) -> (s -> m b) -> Scanl m a b+ [A] mkScant :: Monad m => (s -> a -> Step s b) -> Step s b -> (s -> b) -> Scanl m a b+ [A] mkScanrM :: Monad m => (a -> b -> m b) -> m b -> Scanl m a b+ [A] mkScanr :: Monad m => (a -> b -> b) -> b -> Scanl m a b+ [A] mkScanlM :: Monad m => (b -> a -> m b) -> m b -> Scanl m a b+ [A] mkScanl1M :: Monad m => (a -> a -> m a) -> Scanl m a (Maybe a)+ [A] mkScanl1 :: Monad m => (a -> a -> a) -> Scanl m a (Maybe a)+ [A] mkScanl :: Monad m => (b -> a -> b) -> b -> Scanl m a b+ [A] minimumBy :: Monad m => (a -> a -> Ordering) -> Scanl m a (Maybe a)+ [A] minimum :: (Monad m, Ord a) => Scanl m a (Maybe a)+ [A] mean :: (Monad m, Fractional a) => Scanl m a a+ [A] mconcat :: (Monad m, Monoid a) => Scanl m a a+ [A] maximumBy :: Monad m => (a -> a -> Ordering) -> Scanl m a (Maybe a)+ [A] maximum :: (Monad m, Ord a) => Scanl m a (Maybe a)+ [A] mapMaybeM :: Monad m => (a -> m (Maybe b)) -> Scanl m b r -> Scanl m a r+ [A] mapMaybe :: Monad m => (a -> Maybe b) -> Scanl m b r -> Scanl m a r+ [A] mapMStep :: Applicative m => (a -> m b) -> Step s a -> m (Step s b)+ [A] lmapM :: Monad m => (a -> m b) -> Scanl m b r -> Scanl m a r+ [A] lmap :: (a -> b) -> Scanl m b r -> Scanl m a r+ [A] length :: Monad m => Scanl m a Int+ [A] latest :: Monad m => Scanl m a (Maybe a)+ [A] indexingWith :: Monad m => Int -> (Int -> Int) -> Scanl m a (Maybe (Int, a))+ [A] indexingRev :: Monad m => Int -> Scanl m a (Maybe (Int, a))+ [A] indexing :: Monad m => Scanl m a (Maybe (Int, a))+ [A] indexed :: Monad m => Scanl m (Int, a) b -> Scanl m a b+ [A] incrSumInt :: forall m a. (Monad m, Integral a) => Scanl m (Incr a) a+ [A] incrSum :: forall m a. (Monad m, Num a) => Scanl m (Incr a) a+ [A] incrScanWith :: forall m a b. (MonadIO m, Unbox a) => Int -> Scanl m (Incr a, RingArray a) b -> Scanl m a b+ [A] incrScan :: forall m a b. (MonadIO m, Unbox a) => Int -> Scanl m (Incr a) b -> Scanl m a b+ [A] incrRollingMapM :: Monad m => (Maybe a -> a -> m (Maybe b)) -> Scanl m (Incr a) (Maybe b)+ [A] incrRollingMap :: Monad m => (Maybe a -> a -> Maybe b) -> Scanl m (Incr a) (Maybe b)+ [A] incrPowerSumFrac :: (Monad m, Floating a) => a -> Scanl m (Incr a) a+ [A] incrPowerSum :: (Monad m, Num a) => Int -> Scanl m (Incr a) a+ [A] incrMean :: forall m a. (Monad m, Fractional a) => Scanl m (Incr a) a+ [A] incrCount :: (Monad m, Num b) => Scanl m (Incr a) b+ [A] genericLength :: (Monad m, Num b) => Scanl m a b+ [A] generalizeInner :: Monad m => Scanl Identity a b -> Scanl m a b+ [A] functionM :: Monad m => (a -> m (Maybe b)) -> Scanl m a (Maybe b)+ [A] fromRefold :: Refold m c a b -> c -> Scanl m a b+ [A] foldMapM :: (Monad m, Monoid b) => (a -> m b) -> Scanl m a b+ [A] foldMap :: (Monad m, Monoid b) => (a -> b) -> Scanl m a b+ [A] findIndices :: Monad m => (a -> Bool) -> Scanl m a (Maybe Int)+ [A] filtering :: Monad m => (a -> Bool) -> Scanl m a (Maybe a)+ [A] filterM :: Monad m => (a -> m Bool) -> Scanl m a r -> Scanl m a r+ [A] filter :: Monad m => (a -> Bool) -> Scanl m a r -> Scanl m a r+ [A] elemIndices :: (Monad m, Eq a) => a -> Scanl m a (Maybe Int)+ [A] droppingWhileM :: Monad m => (a -> m Bool) -> Scanl m a (Maybe a)+ [A] droppingWhile :: Monad m => (a -> Bool) -> Scanl m a (Maybe a)+ [A] dropping :: Monad m => Int -> Scanl m a (Maybe a)+ [A] drainN :: Monad m => Int -> Scanl m a ()+ [A] drainMapM :: Monad m => (a -> m b) -> Scanl m a ()+ [A] drain :: Monad m => Scanl m a ()+ [A] distribute :: Monad m => [Scanl m a b] -> Scanl m a [b]+ [A] demuxIO :: (MonadIO m, Ord k) => (a -> k) -> (k -> m (Maybe (Scanl m a b))) -> Scanl m a (Maybe (k, b))+ [A] demuxGenericIO :: (MonadIO m, IsMap f, Traversable f) => (a -> Key f) -> (Key f -> m (Maybe (Scanl m a b))) -> Scanl m a (m (f b), Maybe (Key f, b))+ [A] demuxGeneric :: (Monad m, IsMap f, Traversable f) => (a -> Key f) -> (Key f -> m (Maybe (Scanl m a b))) -> Scanl m a (m (f b), Maybe (Key f, b))+ [A] demux :: (Monad m, Ord k) => (a -> k) -> (k -> m (Maybe (Scanl m a b))) -> Scanl m a (Maybe (k, b))+ [A] deleteBy :: Monad m => (a -> a -> Bool) -> a -> Scanl m a (Maybe a)+ [A] defaultSalt :: Int64+ [A] cumulativeScan :: Scanl m (Incr a) b -> Scanl m a b+ [A] countDistinctInt :: Monad m => Scanl m Int Int+ [A] countDistinct :: (Monad m, Ord a) => Scanl m a Int+ [A] constM :: Applicative m => m b -> Scanl m a b+ [A] const :: Applicative m => b -> Scanl m a b+ [A] classifyIO :: (MonadIO m, Ord k) => (a -> k) -> Scanl m a b -> Scanl m a (Maybe (k, b))+ [A] classifyGenericIO :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) => (a -> Key f) -> Scanl m a b -> Scanl m a (m (f b), Maybe (Key f, b))+ [A] classifyGeneric :: (Monad m, IsMap f, Traversable f, Ord (Key f)) => (a -> Key f) -> Scanl m a b -> Scanl m a (m (f b), Maybe (Key f, b))+ [A] classify :: (MonadIO m, Ord k) => (a -> k) -> Scanl m a b -> Scanl m a (Maybe (k, b))+ [A] chainStepM :: Applicative m => (s1 -> m s2) -> (a -> m (Step s2 b)) -> Step s1 a -> m (Step s2 b)+ [A] catRights :: Monad m => Scanl m b c -> Scanl m (Either a b) c+ [A] catMaybes :: Monad m => Scanl m a b -> Scanl m (Maybe a) b+ [A] catLefts :: Monad m => Scanl m a c -> Scanl m (Either a b) c+ [A] catEithers :: Scanl m a b -> Scanl m (Either a a) b+ [A] bottomBy :: (MonadIO m, Unbox a) => (a -> a -> Ordering) -> Int -> Scanl m a (MutArray a)+ [A] bottom :: (MonadIO m, Unbox a, Ord a) => Int -> Scanl m a (MutArray a)+[A] Streamly.Internal.Data.RingArray.Generic+ [A] RingArray+ [A] [ringMax] :: RingArray a -> !Int+ [A] [ringHead] :: RingArray a -> !Int+ [A] [ringArr] :: RingArray a -> MutArray a+ [A] RingArray :: MutArray a -> !Int -> !Int -> RingArray a+ [A] unsafeInsertRingWith :: RingArray a -> a -> IO Int+ [A] toStreamWith :: Int -> RingArray a -> Stream m a+ [A] toMutArray :: MonadIO m => Int -> Int -> RingArray a -> m (MutArray a)+ [A] seek :: MonadIO m => Int -> RingArray a -> m (RingArray a)+ [A] emptyOf :: MonadIO m => Int -> m (RingArray a)+ [A] createOf :: MonadIO m => Int -> Fold m a (RingArray a)+ [A] copyToMutArray :: MonadIO m => Int -> Int -> RingArray a -> m (MutArray a)+[A] Streamly.Internal.Data.RingArray+ [A] RingArray+ [A] [ringSize] :: RingArray a -> {-# UNPACK #-} !Int+ [A] [ringHead] :: RingArray a -> {-# UNPACK #-} !Int+ [A] [ringContents] :: RingArray a -> {-# UNPACK #-} !MutByteArray+ [A] RingArray :: {-# UNPACK #-} !MutByteArray -> {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> RingArray a+ [A] unsafeGetIndex :: forall m a. (MonadIO m, Unbox a) => Int -> RingArray a -> m a+ [A] unsafeGetHead :: (MonadIO m, Unbox a) => RingArray a -> m a+ [A] unsafeCastMutArrayWith :: forall a. Unbox a => Int -> MutArray a -> RingArray a+ [A] unsafeCastMutArray :: forall a. Unbox a => MutArray a -> RingArray a+ [A] unsafeCast :: RingArray a -> RingArray b+ [A] toMutArray :: (MonadIO m, Unbox a) => RingArray a -> m (MutArray a)+ [A] toList :: (MonadIO m, Unbox a) => RingArray a -> m [a]+ [A] showRing :: (Unbox a, Show a) => RingArray a -> IO String+ [A] scanRingsOf :: forall m a. (MonadIO m, Unbox a) => Int -> Scanl m a (RingArray a)+ [A] scanFoldRingsBy :: forall m a b. (MonadIO m, Unbox a) => Fold m a b -> Int -> Scanl m a b+ [A] scanCustomFoldRingsBy :: forall m a b. (MonadIO m, Unbox a) => (RingArray a -> m b) -> Int -> Scanl m a b+ [A] ringsOf :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> Stream m (RingArray a)+ [A] replace_ :: forall m a. (MonadIO m, Unbox a) => RingArray a -> a -> m (RingArray a)+ [A] replace :: forall m a. (MonadIO m, Unbox a) => RingArray a -> a -> m (RingArray a, a)+ [A] readerRev :: forall m a. (MonadIO m, Unbox a) => Unfold m (RingArray a) a+ [A] reader :: forall m a. (MonadIO m, Unbox a) => Unfold m (RingArray a) a+ [A] readRev :: forall m a. (MonadIO m, Unbox a) => RingArray a -> Stream m a+ [A] read :: forall m a. (MonadIO m, Unbox a) => RingArray a -> Stream m a+ [A] putIndex :: forall m a. (MonadIO m, Unbox a) => Int -> RingArray a -> a -> m ()+ [A] moveReverse :: forall a. Unbox a => RingArray a -> RingArray a+ [A] moveForward :: forall a. Unbox a => RingArray a -> RingArray a+ [A] moveBy :: forall a. Unbox a => Int -> RingArray a -> RingArray a+ [A] modifyIndex :: Int -> RingArray a -> (a -> (a, b)) -> m b+ [A] length :: forall a. Unbox a => RingArray a -> Int+ [A] insert :: RingArray a -> a -> m (RingArray a)+ [A] getIndex :: forall m a. (MonadIO m, Unbox a) => Int -> RingArray a -> m (Maybe a)+ [A] foldlM' :: forall m a b. (MonadIO m, Unbox a) => (b -> a -> m b) -> b -> RingArray a -> m b+ [A] fold :: forall m a b. (MonadIO m, Unbox a) => Fold m a b -> RingArray a -> m b+ [A] eqArrayN :: RingArray a -> Array a -> Int -> IO Bool+ [A] eqArray :: RingArray a -> Array a -> IO Bool+ [A] createOfLast :: (Unbox a, MonadIO m) => Int -> Fold m a (RingArray a)+ [A] castMutArrayWith :: forall a. Unbox a => Int -> MutArray a -> Maybe (RingArray a)+ [A] castMutArray :: forall a. Unbox a => MutArray a -> Maybe (RingArray a)+ [A] cast :: forall a b. Unbox b => RingArray a -> Maybe (RingArray b)+ [A] byteLength :: RingArray a -> Int+ [A] asMutArray_ :: RingArray a -> MutArray a+ [A] asMutArray :: RingArray a -> (MutArray a, Int)+ [A] asBytes :: RingArray a -> RingArray Word8+[R] Streamly.Internal.Data.Ring.Generic+[R] Streamly.Internal.Data.Ring+[C] Streamly.Internal.Data.Producer+ [C] parse+ [O] parse :: Monad m => Parser a m b -> Producer m (Source s a) a -> Source s a -> m (Either ParseError b, Source s a)+ [N] parse :: Monad m => Parser a m b -> Producer m (Source s a) a -> Source s a -> m (Either ParseErrorPos b, Source s a)+[C] Streamly.Internal.Data.Pipe+ [C] Step+ [A] YieldP :: ps -> b -> Step cs ps b+ [A] YieldC :: cs -> b -> Step cs ps b+ [R] Yield :: a -> s -> Step s a+ [A] Stop :: Step cs ps b+ [A] SkipP :: ps -> Step cs ps b+ [A] SkipC :: cs -> Step cs ps b+ [R] Continue :: s -> Step s a+ [R] PipeState+ [C] Pipe+ [C] Pipe+ [O] Pipe :: (s1 -> a -> m (Step (PipeState s1 s2) b)) -> (s2 -> m (Step (PipeState s1 s2) b)) -> s1 -> Pipe m a b+ [N] Pipe :: (cs -> a -> m (Step cs ps b)) -> (ps -> m (Step cs ps b)) -> cs -> Pipe m a b+ [R] zipWith :: Monad m => (a -> b -> c) -> Pipe m i a -> Pipe m i b -> Pipe m i c+ [A] teeMerge :: Monad m => Pipe m a b -> Pipe m a b -> Pipe m a b+ [R] tee :: Monad m => Pipe m a b -> Pipe m a b -> Pipe m a b+ [A] scanFold :: Monad m => Fold m a b -> Pipe m a b+ [A] identity :: Monad m => Pipe m a a+ [A] fromStream :: Monad m => Stream m a -> Pipe m () a+ [A] fromScanr :: Monad m => Scanr m a b -> Pipe m a b+ [A] fromFold :: Monad m => Fold m a b -> Pipe m a b+ [A] filterM :: Monad m => (a -> m Bool) -> Pipe m a a+ [A] filter :: Monad m => (a -> Bool) -> Pipe m a a+[A] Streamly.Internal.Data.Path+ [A] class IsPath a b+ [A] GHC.Show.Show+ [A] instance GHC.Show.Show Streamly.Internal.Data.Path.PathException+ [A] GHC.Exception.Type.Exception+ [A] instance GHC.Exception.Type.Exception Streamly.Internal.Data.Path.PathException+ [A] GHC.Classes.Eq+ [A] instance GHC.Classes.Eq Streamly.Internal.Data.Path.PathException+ [A] PathException+ [A] InvalidPath :: String -> PathException+ [A] unsafeFromPath :: IsPath a b => a -> b+ [A] toPath :: IsPath a b => b -> a+ [A] fromPath :: (IsPath a b, MonadThrow m) => a -> m b+[C] Streamly.Internal.Data.ParserK+ [C] Step+ [C] Partial+ [O] Partial :: !Int -> (Input a -> m (Step a m r)) -> Step a m r+ [N] Partial :: !Int -> StepParser a m r -> Step a m r+ [C] Continue+ [O] Continue :: !Int -> (Input a -> m (Step a m r)) -> Step a m r+ [N] Continue :: !Int -> StepParser a m r -> Step a m r+ [C] ParserK+ [C] [runParser]+ [O] [runParser] :: ParserK a m b -> forall r. (ParseResult b -> Int -> Input a -> m (Step a m r)) -> Int -> Int -> Input a -> m (Step a m r)+ [N] [runParser] :: ParserK a m b -> forall r. (ParseResult b -> Int -> StepParser a m r) -> Int -> Int -> StepParser a m r+ [C] MkParser+ [O] MkParser :: (forall r. (ParseResult b -> Int -> Input a -> m (Step a m r)) -> Int -> Int -> Input a -> m (Step a m r)) -> ParserK a m b+ [N] MkParser :: (forall r. (ParseResult b -> Int -> StepParser a m r) -> Int -> Int -> StepParser a m r) -> ParserK a m b+ [A] toParserK :: Monad m => Parser a m b -> ParserK a m b+ [A] toParser :: Monad m => ParserK a m b -> Parser a m b+ [A] parserDone :: Applicative m => ParseResult b -> Int -> Input a -> m (Step a m b)+ [A] chainr1 :: ParserK b IO a -> ParserK b IO (a -> a -> a) -> ParserK b IO a+ [A] chainr :: ParserK b IO a -> ParserK b IO (a -> a -> a) -> a -> ParserK b IO a+ [A] chainl1 :: ParserK b IO a -> ParserK b IO (a -> a -> a) -> ParserK b IO a+ [A] chainl :: ParserK b IO a -> ParserK b IO (a -> a -> a) -> a -> ParserK b IO a+ [D] adaptCG :: Monad m => Parser a m b -> ParserK (Array a) m b+ [D] adaptC :: (Monad m, Unbox a) => Parser a m b -> ParserK (Array a) m b+ [D] adapt :: Monad m => Parser a m b -> ParserK a m b+[C] Streamly.Internal.Data.Parser+ [C] Step+ [A] SPartial :: !Int -> !s -> Step s b+ [A] SError :: !String -> Step s b+ [A] SDone :: !Int -> !b -> Step s b+ [A] SContinue :: !Int -> !s -> Step s b+ [R] Partial :: !Int -> !s -> Step s b+ [R] Error :: !String -> Step s b+ [R] Done :: !Int -> !b -> Step s b+ [R] Continue :: !Int -> !s -> Step s b+ [C] Parser+ [C] Parser+ [O] Parser :: (s -> a -> m (Step s b)) -> m (Initial s b) -> (s -> m (Step s b)) -> Parser a m b+ [N] Parser :: (s -> a -> m (Step s b)) -> m (Initial s b) -> (s -> m (Final s b)) -> Parser a m b+ [A] ParseErrorPos+ [A] ParseErrorPos :: Int -> String -> ParseErrorPos+ [A] Final+ [A] FError :: !String -> Final s b+ [A] FDone :: !Int -> !b -> Final s b+ [A] FContinue :: !Int -> !s -> Final s b+ [C] ParseError+ [D] takeStartBy_ :: Monad m => (a -> Bool) -> Fold m a b -> Parser a m b+ [D] takeStartBy :: Monad m => (a -> Bool) -> Fold m a b -> Parser a m b+ [C] takeEndBy_+ [O] takeEndBy_ :: (a -> Bool) -> Parser a m b -> Parser a m b+ [N] takeEndBy_ :: Monad m => (a -> Bool) -> Parser a m b -> Parser a m b+ [A] takeBeginBy_ :: Monad m => (a -> Bool) -> Fold m a b -> Parser a m b+ [A] takeBeginBy :: Monad m => (a -> Bool) -> Fold m a b -> Parser a m b+ [A] mapCount :: (Int -> Int) -> Step s b -> Step s b+ [A] localReaderT :: (r -> r) -> Parser a (ReaderT r m) b -> Parser a (ReaderT r m) b+ [R] extractStep :: Monad m => (s -> m (Step s1 b)) -> Step s b -> m (Step s1 b)+ [A] bimapMorphOverrideCount :: Int -> (s -> s1) -> (b -> b1) -> Final s b -> Step s1 b1+[C] Streamly.Internal.Data.MutByteArray+ [A] unsafePutSlice :: MonadIO m => MutByteArray -> Int -> MutByteArray -> Int -> Int -> m ()+ [A] unsafePutPtrN :: MonadIO m => Ptr Word8 -> MutByteArray -> Int -> Int -> m ()+ [A] unsafePinnedCloneSlice :: MonadIO m => Int -> Int -> MutByteArray -> m MutByteArray+ [A] unsafeCloneSliceAs :: MonadIO m => PinnedState -> Int -> Int -> MutByteArray -> m MutByteArray+ [A] unsafeCloneSlice :: MonadIO m => Int -> Int -> MutByteArray -> m MutByteArray+ [A] unsafeByteCmp :: MutByteArray -> Int -> MutByteArray -> Int -> Int -> IO Int+ [C] unsafeAsPtr+ [O] unsafeAsPtr :: MonadIO m => MutByteArray -> (Ptr a -> m b) -> m b+ [N] unsafeAsPtr :: MonadIO m => MutByteArray -> (Ptr a -> IO b) -> m b+ [A] touch :: MutByteArray -> IO ()+ [D] sizeOfMutableByteArray :: MutByteArray -> IO Int+ [A] reallocSliceAs :: PinnedState -> Int -> MutByteArray -> Int -> Int -> IO MutByteArray+ [D] putSliceUnsafe :: MonadIO m => MutByteArray -> Int -> MutByteArray -> Int -> Int -> m ()+ [D] pinnedNewAlignedBytes :: Int -> Int -> IO MutByteArray+ [D] pinnedNew :: Int -> IO MutByteArray+ [D] pinnedCloneSliceUnsafe :: MonadIO m => Int -> Int -> MutByteArray -> m MutByteArray+ [D] newBytesAs :: PinnedState -> Int -> IO MutByteArray+ [A] newAs :: PinnedState -> Int -> IO MutByteArray+ [A] new' :: Int -> IO MutByteArray+ [A] length :: MutByteArray -> IO Int+ [A] largeObjectThreshold :: Int+ [D] getMutableByteArray# :: MutByteArray -> MutableByteArray# RealWorld+ [A] getMutByteArray# :: MutByteArray -> MutableByteArray# RealWorld+ [D] cloneSliceUnsafeAs :: MonadIO m => PinnedState -> Int -> Int -> MutByteArray -> m MutByteArray+ [D] cloneSliceUnsafe :: MonadIO m => Int -> Int -> MutByteArray -> m MutByteArray+ [A] blockSize :: Int+[C] Streamly.Internal.Data.MutArray.Generic+ [C] MutArray+ [R] [arrTrueLen] :: MutArray a -> {-# UNPACK #-} !Int+ [R] [arrLen] :: MutArray a -> {-# UNPACK #-} !Int+ [A] [arrEnd] :: MutArray a -> {-# UNPACK #-} !Int+ [A] [arrBound] :: MutArray a -> {-# UNPACK #-} !Int+ [D] writeN :: MonadIO m => Int -> Fold m a (MutArray a)+ [D] write :: MonadIO m => Fold m a (MutArray a)+ [A] unsafeSnoc :: MonadIO m => MutArray a -> a -> m (MutArray a)+ [A] unsafeSliceOffLen :: Int -> Int -> MutArray a -> MutArray a+ [A] unsafePutSlice :: MonadIO m => MutArray a -> Int -> MutArray a -> Int -> Int -> m ()+ [A] unsafePutIndex :: forall m a. MonadIO m => Int -> MutArray a -> a -> m ()+ [A] unsafeModifyIndex :: MonadIO m => Int -> MutArray a -> (a -> (a, b)) -> m b+ [A] unsafeGetIndexWith :: MonadIO m => MutableArray# RealWorld a -> Int -> m a+ [A] unsafeGetIndex :: MonadIO m => Int -> MutArray a -> m a+ [D] strip :: MonadIO m => (a -> Bool) -> MutArray a -> m (MutArray a)+ [D] snocUnsafe :: MonadIO m => MutArray a -> a -> m (MutArray a)+ [A] sliceOffLen :: Int -> Int -> MutArray a -> MutArray a+ [D] putSliceUnsafe :: MonadIO m => MutArray a -> Int -> MutArray a -> Int -> Int -> m ()+ [D] putIndexUnsafe :: forall m a. MonadIO m => Int -> MutArray a -> a -> m ()+ [D] new :: MonadIO m => Int -> m (MutArray a)+ [D] modifyIndexUnsafe :: MonadIO m => Int -> MutArray a -> (a -> (a, b)) -> m b+ [A] initializeOfFilledUpto :: MonadIO m => Int -> Int -> a -> m (MutArray a)+ [D] getSliceUnsafe :: Int -> Int -> MutArray a -> MutArray a+ [D] getSlice :: Int -> Int -> MutArray a -> MutArray a+ [D] getIndexUnsafeWith :: MonadIO m => MutableArray# RealWorld a -> Int -> m a+ [D] getIndexUnsafe :: MonadIO m => Int -> MutArray a -> m a+ [A] dropAround :: MonadIO m => (a -> Bool) -> MutArray a -> m (MutArray a)+[C] Streamly.Internal.Data.MutArray+ [R] IORef+ [D] writeNWith :: forall m a. (MonadIO m, Unbox a) => (Int -> m (MutArray a)) -> Int -> Fold m a (MutArray a)+ [D] writeN :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [D] writeIORef :: Unbox a => IORef a -> a -> IO ()+ [D] writeAppendN :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a) -> Fold m a (MutArray a)+ [D] writeAppend :: forall m a. (MonadIO m, Unbox a) => m (MutArray a) -> Fold m a (MutArray a)+ [D] write :: forall m a. (MonadIO m, Unbox a) => Fold m a (MutArray a)+ [A] vacate :: MutArray a -> MutArray a+ [A] unsafeSplice :: MonadIO m => MutArray a -> MutArray a -> m (MutArray a)+ [A] unsafeSnoc :: forall m a. (MonadIO m, Unbox a) => MutArray a -> a -> m (MutArray a)+ [A] unsafeSliceOffLen :: forall a. Unbox a => Int -> Int -> MutArray a -> MutArray a+ [A] unsafePutIndex :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> a -> m ()+ [A] unsafePokeSkip :: Int -> MutArray Word8 -> MutArray Word8+ [D] unsafePinnedCreateOf :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [A] unsafePeekSkip :: Int -> MutArray Word8 -> MutArray Word8+ [A] unsafePeek :: forall m a. (MonadIO m, Unbox a) => MutArray Word8 -> m (a, MutArray Word8)+ [A] unsafeModifyIndex :: forall m a b. (MonadIO m, Unbox a) => Int -> MutArray a -> (a -> (a, b)) -> m b+ [A] unsafeGetIndexRev :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m a+ [A] unsafeGetIndex :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m a+ [A] unsafeCreateWithPtr' :: MonadIO m => Int -> (Ptr Word8 -> IO Int) -> m (MutArray Word8)+ [A] unsafeCreateWithOf :: forall m a. (MonadIO m, Unbox a) => (Int -> m (MutArray a)) -> Int -> Fold m a (MutArray a)+ [R] unsafeCreateOfWith :: forall m a. (MonadIO m, Unbox a) => (Int -> m (MutArray a)) -> Int -> Fold m a (MutArray a)+ [A] unsafeCreateOf' :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [A] unsafeCast :: MutArray a -> MutArray b+ [A] unsafeBreakAt :: forall a. Unbox a => Int -> MutArray a -> (MutArray a, MutArray a)+ [C] unsafeAsPtr+ [O] unsafeAsPtr :: MonadIO m => MutArray a -> (Ptr a -> m b) -> m b+ [N] unsafeAsPtr :: MonadIO m => MutArray a -> (Ptr a -> Int -> IO b) -> m b+ [A] unsafeAppendPtrN :: MonadIO m => MutArray Word8 -> Ptr Word8 -> Int -> m (MutArray Word8)+ [D] unsafeAppendN :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a) -> Fold m a (MutArray a)+ [A] unsafeAppendMax :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> Fold m a (MutArray a)+ [A] toMutByteArray :: MutArray a -> (MutByteArray, Int, Int)+ [D] strip :: forall a m. (Unbox a, MonadIO m) => (a -> Bool) -> MutArray a -> m (MutArray a)+ [A] splitterFromLen :: forall m a. (Monad m, Unbox a) => Int -> Int -> Unfold m (MutArray a) (MutArray a)+ [D] splitOn :: (MonadIO m, Unbox a) => (a -> Bool) -> MutArray a -> Stream m (MutArray a)+ [A] splitEndBy_ :: (MonadIO m, Unbox a) => (a -> Bool) -> MutArray a -> Stream m (MutArray a)+ [A] splitEndBy :: (MonadIO m, Unbox a) => (a -> Bool) -> MutArray a -> Stream m (MutArray a)+ [D] splitAt :: forall a. Unbox a => Int -> MutArray a -> (MutArray a, MutArray a)+ [D] spliceUnsafe :: MonadIO m => MutArray a -> MutArray a -> m (MutArray a)+ [D] snocUnsafe :: forall m a. (MonadIO m, Unbox a) => MutArray a -> a -> m (MutArray a)+ [D] snocLinear :: forall m a. (MonadIO m, Unbox a) => MutArray a -> a -> m (MutArray a)+ [A] snocGrowBy :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> a -> m (MutArray a)+ [D] slicerFromLen :: forall m a. (Monad m, Unbox a) => Int -> Int -> Unfold m (MutArray a) (MutArray a)+ [A] sliceOffLen :: forall a. Unbox a => Int -> Int -> MutArray a -> MutArray a+ [D] sliceIndexerFromLen :: forall m a. (Monad m, Unbox a) => Int -> Int -> Unfold m (MutArray a) (Int, Int)+ [A] serializePtrN :: MutArray Word8 -> Ptr a -> Int -> m (MutArray Word8)+ [A] serialize :: forall m a. (MonadIO m, Serialize a) => MutArray Word8 -> a -> m (MutArray Word8)+ [A] scanCompactMin' :: forall m a. (MonadIO m, Unbox a) => Int -> Scanl m (MutArray a) (Maybe (MutArray a))+ [A] scanCompactMin :: forall m a. (MonadIO m, Unbox a) => Int -> Scanl m (MutArray a) (Maybe (MutArray a))+ [A] revDropWhile :: forall a m. (Unbox a, MonadIO m) => (a -> Bool) -> MutArray a -> m (MutArray a)+ [A] revBreakEndBy_ :: (MonadIO m, Unbox a) => (a -> Bool) -> MutArray a -> m (MutArray a, MutArray a)+ [A] revBreakEndBy :: (MonadIO m, Unbox a) => (a -> Bool) -> MutArray a -> m (MutArray a, MutArray a)+ [A] reallocBytesWith :: forall m a. (MonadIO m, Unbox a) => String -> (Int -> Int) -> Int -> MutArray a -> m (MutArray a)+ [A] reallocBytes :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m (MutArray a)+ [D] realloc :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m (MutArray a)+ [D] readIORef :: Unbox a => IORef a -> IO a+ [A] rangeBy :: (a -> a -> Ordering) -> MutArray a -> IO (Maybe (a, a))+ [D] putIndexUnsafe :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> a -> m ()+ [D] pollIntIORef :: (MonadIO m, Unbox a) => IORef a -> Stream m a+ [D] pokeSkipUnsafe :: Int -> MutArray Word8 -> MutArray Word8+ [A] pokeMay :: forall m a. (MonadIO m, Unbox a) => MutArray Word8 -> a -> m (Maybe (MutArray Word8))+ [D] pokeAppendMay :: forall m a. (MonadIO m, Unbox a) => MutArray Word8 -> a -> m (Maybe (MutArray Word8))+ [D] pokeAppend :: forall m a. (MonadIO m, Unbox a) => MutArray Word8 -> a -> m (MutArray Word8)+ [A] poke :: forall m a. (MonadIO m, Unbox a) => MutArray Word8 -> a -> m (MutArray Word8)+ [D] pinnedNewAligned :: (MonadIO m, Unbox a) => Int -> Int -> m (MutArray a)+ [D] pinnedNew :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a)+ [D] pinnedFromListN :: (MonadIO m, Unbox a) => Int -> [a] -> m (MutArray a)+ [D] pinnedFromList :: (MonadIO m, Unbox a) => [a] -> m (MutArray a)+ [D] pinnedEmptyOf :: (MonadIO m, Unbox a) => Int -> m (MutArray a)+ [D] pinnedCreateOf :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [D] pinnedCreate :: forall m a. (MonadIO m, Unbox a) => Fold m a (MutArray a)+ [D] pinnedCompactLE :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m (MutArray a) -> Stream m (MutArray a)+ [D] pinnedClone :: MonadIO m => MutArray a -> m (MutArray a)+ [D] pinnedChunksOf :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> Stream m (MutArray a)+ [D] peekUnconsUnsafe :: forall m a. (MonadIO m, Unbox a) => MutArray Word8 -> m (a, MutArray Word8)+ [D] peekUncons :: forall m a. (MonadIO m, Unbox a) => MutArray Word8 -> m (Maybe a, MutArray Word8)+ [D] peekSkipUnsafe :: Int -> MutArray Word8 -> MutArray Word8+ [A] peek :: forall m a. (MonadIO m, Unbox a) => MutArray Word8 -> m (Maybe a, MutArray Word8)+ [D] pPinnedCompactLE :: forall m a. (MonadIO m, Unbox a) => Int -> Parser (MutArray a) m (MutArray a)+ [D] pCompactLE :: forall m a. (MonadIO m, Unbox a) => Int -> Parser (MutArray a) m (MutArray a)+ [D] newIORef :: forall a. Unbox a => a -> IO (IORef a)+ [D] newArrayWith :: forall m a. (MonadIO m, Unbox a) => (Int -> Int -> IO MutByteArray) -> Int -> Int -> m (MutArray a)+ [D] new :: (MonadIO m, Unbox a) => Int -> m (MutArray a)+ [D] modifyIndexUnsafe :: forall m a b. (MonadIO m, Unbox a) => Int -> MutArray a -> (a -> (a, b)) -> m b+ [D] modifyIORef' :: Unbox a => IORef a -> (a -> a) -> IO ()+ [D] lPinnedCompactGE :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m (MutArray a) () -> Fold m (MutArray a) ()+ [D] lCompactGE :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m (MutArray a) () -> Fold m (MutArray a) ()+ [A] isPower2 :: Int -> Bool+ [A] indexerFromLen :: forall m a. (Monad m, Unbox a) => Int -> Int -> Unfold m (MutArray a) (Int, Int)+ [A] growTo :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m (MutArray a)+ [A] growBy :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m (MutArray a)+ [D] grow :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m (MutArray a)+ [D] getSliceUnsafe :: forall a. Unbox a => Int -> Int -> MutArray a -> MutArray a+ [D] getSlice :: forall a. Unbox a => Int -> Int -> MutArray a -> MutArray a+ [D] getIndexUnsafe :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m a+ [A] fromW16CString# :: MonadIO m => Addr# -> m (MutArray Word16)+ [A] fromMutByteArray :: MonadIO m => MutByteArray -> Int -> Int -> m (MutArray a)+ [A] fromListN' :: (MonadIO m, Unbox a) => Int -> [a] -> m (MutArray a)+ [A] fromList' :: (MonadIO m, Unbox a) => [a] -> m (MutArray a)+ [A] fromCString# :: MonadIO m => Addr# -> m (MutArray Word8)+ [D] fromByteStr# :: MonadIO m => Addr# -> m (MutArray Word8)+ [A] free :: forall a. Unbox a => MutArray a -> Int+ [A] foldRev :: (MonadIO m, Unbox a) => Fold m a b -> MutArray a -> m b+ [A] fold :: (MonadIO m, Unbox a) => Fold m a b -> MutArray a -> m b+ [D] fPinnedCompactGE :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m (MutArray a) (MutArray a)+ [D] fCompactGE :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m (MutArray a) (MutArray a)+ [A] emptyWithAligned :: forall m a. (MonadIO m, Unbox a) => (Int -> Int -> IO MutByteArray) -> Int -> Int -> m (MutArray a)+ [A] emptyOf' :: (MonadIO m, Unbox a) => Int -> m (MutArray a)+ [A] dropWhile :: forall a m. (Unbox a, MonadIO m) => (a -> Bool) -> MutArray a -> m (MutArray a)+ [A] dropAround :: forall a m. (Unbox a, MonadIO m) => (a -> Bool) -> MutArray a -> m (MutArray a)+ [A] deserializePtrN :: MutArray Word8 -> (Ptr a -> Int -> m b) -> m (a, MutArray Word8)+ [A] deserialize :: (MonadIO m, Serialize a) => MutArray Word8 -> m (a, MutArray Word8)+ [A] createWithOf :: forall m a. (MonadIO m, Unbox a) => (Int -> m (MutArray a)) -> Int -> Fold m a (MutArray a)+ [D] createWith :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [D] createOfWith :: forall m a. (MonadIO m, Unbox a) => (Int -> m (MutArray a)) -> Int -> Fold m a (MutArray a)+ [A] createOfLast :: (Unbox a, MonadIO m) => Int -> Fold m a (MutArray a)+ [A] createOf' :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [A] createMinOf :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+ [A] createCompactMin' :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m (MutArray a) (MutArray a)+ [A] createCompactMin :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m (MutArray a) (MutArray a)+ [A] createCompactMax' :: forall m a. (MonadIO m, Unbox a) => Int -> Parser (MutArray a) m (MutArray a)+ [A] createCompactMax :: forall m a. (MonadIO m, Unbox a) => Int -> Parser (MutArray a) m (MutArray a)+ [A] create' :: forall m a. (MonadIO m, Unbox a) => Fold m a (MutArray a)+ [A] compactSepByByte_ :: MonadIO m => Word8 -> Stream m (MutArray Word8) -> Stream m (MutArray Word8)+ [D] compactOnByteSuffix :: MonadIO m => Word8 -> Stream m (MutArray Word8) -> Stream m (MutArray Word8)+ [D] compactOnByte :: MonadIO m => Word8 -> Stream m (MutArray Word8) -> Stream m (MutArray Word8)+ [A] compactMin :: (MonadIO m, Unbox a) => Int -> Stream m (MutArray a) -> Stream m (MutArray a)+ [A] compactMax' :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m (MutArray a) -> Stream m (MutArray a)+ [A] compactMax :: (MonadIO m, Unbox a) => Int -> Stream m (MutArray a) -> Stream m (MutArray a)+ [D] compactLE :: (MonadIO m, Unbox a) => Int -> Stream m (MutArray a) -> Stream m (MutArray a)+ [D] compactGE :: (MonadIO m, Unbox a) => Int -> Stream m (MutArray a) -> Stream m (MutArray a)+ [A] compactExact :: Int -> Stream m (MutArray a) -> Stream m (MutArray a)+ [A] compactEndByLn_ :: MonadIO m => Stream m (MutArray Word8) -> Stream m (MutArray Word8)+ [A] compactEndByByte_ :: MonadIO m => Word8 -> Stream m (MutArray Word8) -> Stream m (MutArray Word8)+ [R] compactEQ :: Int -> Stream m (MutArray a) -> Stream m (MutArray a)+ [A] clone' :: MonadIO m => MutArray a -> m (MutArray a)+ [A] chunksOf' :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> Stream m (MutArray a)+ [A] chunksEndByLn' :: MonadIO m => Stream m Word8 -> Stream m (MutArray Word8)+ [A] chunksEndByLn :: MonadIO m => Stream m Word8 -> Stream m (MutArray Word8)+ [A] chunksEndBy' :: forall m a. (MonadIO m, Unbox a) => (a -> Bool) -> Stream m a -> Stream m (MutArray a)+ [A] chunksEndBy :: forall m a. (MonadIO m, Unbox a) => (a -> Bool) -> Stream m a -> Stream m (MutArray a)+ [D] castUnsafe :: MutArray a -> MutArray b+ [A] capacity :: forall a. Unbox a => MutArray a -> Int+ [D] breakOn :: MonadIO m => Word8 -> MutArray Word8 -> m (MutArray Word8, Maybe (MutArray Word8))+ [A] breakEndBy_ :: (MonadIO m, Unbox a) => (a -> Bool) -> MutArray a -> m (MutArray a, MutArray a)+ [A] breakEndByWord8_ :: MonadIO m => Word8 -> MutArray Word8 -> m (MutArray Word8, Maybe (MutArray Word8))+ [A] breakEndBy :: (MonadIO m, Unbox a) => (a -> Bool) -> MutArray a -> m (MutArray a, MutArray a)+ [A] breakAt :: forall a. Unbox a => Int -> MutArray a -> (MutArray a, MutArray a)+ [A] asCWString :: MutArray a -> (CWString -> IO b) -> IO b+ [A] asCString :: MutArray a -> (CString -> IO b) -> IO b+ [A] appendStreamN :: (MonadIO m, Unbox a) => Int -> MutArray a -> Stream m a -> m (MutArray a)+ [A] appendStream :: (MonadIO m, Unbox a) => MutArray a -> Stream m a -> m (MutArray a)+ [A] appendPtrN :: MonadIO m => MutArray Word8 -> Ptr Word8 -> Int -> m (MutArray Word8)+ [D] appendN :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a) -> Fold m a (MutArray a)+ [A] appendMax :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> Fold m a (MutArray a)+ [A] appendGrowBy :: (MonadIO m, Unbox a) => Int -> MutArray a -> Fold m a (MutArray a)+ [A] appendCString# :: MonadIO m => MutArray Word8 -> Addr# -> m (MutArray Word8)+ [A] appendCString :: MonadIO m => MutArray Word8 -> Ptr a -> m (MutArray Word8)+ [A] append2 :: (MonadIO m, Unbox a) => MutArray a -> Fold m a (MutArray a)+ [D] append :: forall m a. (MonadIO m, Unbox a) => m (MutArray a) -> Fold m a (MutArray a)+[A] Streamly.Internal.Data.IORef+ [A] IORef+ [A] writeIORef :: Unbox a => IORef a -> a -> IO ()+ [A] readIORef :: Unbox a => IORef a -> IO a+ [A] pollIORefInt :: MonadIO m => IORef Int -> Stream m Int+ [A] pollGenericIORef :: (MonadIO m, Unbox a) => IORef a -> Stream m a+ [A] newIORef :: forall a. Unbox a => a -> IO (IORef a)+ [A] modifyIORef' :: Unbox a => IORef a -> (a -> a) -> IO ()+[C] Streamly.Internal.Data.Fold+ [C] windowRange+ [O] windowRange :: (MonadIO m, Storable a, Ord a) => Int -> Fold m a (Maybe (a, a))+ [N] windowRange :: forall m a. (MonadIO m, Unbox a, Ord a) => Int -> Fold m a (Maybe (a, a))+ [C] windowMinimum+ [O] windowMinimum :: (MonadIO m, Storable a, Ord a) => Int -> Fold m a (Maybe a)+ [N] windowMinimum :: (MonadIO m, Unbox a, Ord a) => Int -> Fold m a (Maybe a)+ [C] windowMaximum+ [O] windowMaximum :: (MonadIO m, Storable a, Ord a) => Int -> Fold m a (Maybe a)+ [N] windowMaximum :: (MonadIO m, Unbox a, Ord a) => Int -> Fold m a (Maybe a)+ [R] transform :: Monad m => Pipe m a b -> Fold m b c -> Fold m a c+ [C] toContainerIO+ [O] toContainerIO :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) => (a -> Key f) -> Fold m a b -> Fold m a (f b)+ [N] toContainerIO :: (MonadIO m, IsMap f, Traversable f) => (a -> Key f) -> Fold m a b -> Fold m a (f b)+ [C] toContainer+ [O] toContainer :: (Monad m, IsMap f, Traversable f, Ord (Key f)) => (a -> Key f) -> Fold m a b -> Fold m a (f b)+ [N] toContainer :: (Monad m, IsMap f, Traversable f) => (a -> Key f) -> Fold m a b -> Fold m a (f b)+ [C] takeEndBySeq_+ [O] takeEndBySeq_ :: forall m a b. (MonadIO m, Storable a, Unbox a, Enum a, Eq a) => Array a -> Fold m a b -> Fold m a b+ [N] takeEndBySeq_ :: forall m a b. (MonadIO m, Unbox a, Enum a, Eq a) => Array a -> Fold m a b -> Fold m a b+ [C] takeEndBySeq+ [O] takeEndBySeq :: forall m a b. (MonadIO m, Storable a, Unbox a, Enum a, Eq a) => Array a -> Fold m a b -> Fold m a b+ [N] takeEndBySeq :: forall m a b. (MonadIO m, Unbox a, Enum a, Eq a) => Array a -> Fold m a b -> Fold m a b+ [A] scanlMany :: Monad m => Scanl m a b -> Fold m b c -> Fold m a c+ [A] scanl :: Monad m => Scanl m a b -> Fold m b c -> Fold m a c+ [D] scanMaybe :: Monad m => Fold m a (Maybe b) -> Fold m b c -> Fold m a c+ [D] scanMany :: Monad m => Fold m a b -> Fold m b c -> Fold m a c+ [D] scan :: Monad m => Fold m a b -> Fold m b c -> Fold m a c+ [A] rollingMap :: Monad m => (Maybe a -> a -> b) -> Fold m a b+ [A] rangeBy :: Monad m => (a -> a -> Ordering) -> Fold m a (Maybe (a, a))+ [A] range :: (Monad m, Ord a) => Fold m a (Maybe (a, a))+ [A] postscanlMaybe :: Monad m => Scanl m a (Maybe b) -> Fold m b c -> Fold m a c+ [A] postscanl :: Monad m => Scanl m a b -> Fold m b c -> Fold m a c+ [D] postscan :: Monad m => Fold m a b -> Fold m b c -> Fold m a c+ [A] pipe :: Monad m => Pipe m a b -> Fold m b c -> Fold m a c+ [A] onException :: MonadCatch m => m x -> Fold m a b -> Fold m a b+ [R] lengthGeneric :: (Monad m, Num b) => Fold m a b+ [D] indexingWith :: Monad m => Int -> (Int -> Int) -> Fold m a (Maybe (Int, a))+ [D] indexingRev :: Monad m => Int -> Fold m a (Maybe (Int, a))+ [D] indexing :: Monad m => Fold m a (Maybe (Int, a))+ [R] indexGeneric :: (Integral i, Monad m) => i -> Fold m a (Maybe a)+ [A] ifThen :: Monad m => m Bool -> Fold m a b -> Fold m a b -> Fold m a b+ [A] genericLength :: (Monad m, Num b) => Fold m a b+ [A] genericIndex :: (Integral i, Monad m) => i -> Fold m a (Maybe a)+ [A] fromScanl :: Scanl m a b -> Fold m a b+ [D] foldlM1' :: Monad m => (a -> a -> m a) -> Fold m a (Maybe a)+ [A] foldl1M' :: Monad m => (a -> a -> m a) -> Fold m a (Maybe a)+ [A] finallyIO :: (MonadIO m, MonadCatch m) => IO b -> Fold m a b -> Fold m a b+ [A] finalM :: Monad m => Fold m a b -> m b+ [D] extractM :: Monad m => Fold m a b -> m b+ [A] distributeScan :: Monad m => m [Fold m a b] -> Scanl m a [b]+ [A] demuxerToMapIO :: (MonadIO m, Ord k) => (a -> k) -> (k -> m (Maybe (Fold m a b))) -> Fold m a (Map k b)+ [A] demuxerToMap :: (Monad m, Ord k) => (a -> k) -> (k -> m (Maybe (Fold m a b))) -> Fold m a (Map k b)+ [A] demuxerToContainerIO :: (MonadIO m, IsMap f, Traversable f) => (a -> Key f) -> (Key f -> m (Maybe (Fold m a b))) -> Fold m a (f b)+ [A] demuxerToContainer :: (Monad m, IsMap f, Traversable f) => (a -> Key f) -> (Key f -> m (Maybe (Fold m a b))) -> Fold m a (f b)+ [D] demuxToMapIO :: (MonadIO m, Ord k) => (a -> k) -> (a -> m (Fold m a b)) -> Fold m a (Map k b)+ [D] demuxToMap :: (Monad m, Ord k) => (a -> k) -> (a -> m (Fold m a b)) -> Fold m a (Map k b)+ [D] demuxToContainerIO :: (MonadIO m, IsMap f, Traversable f) => (a -> Key f) -> (a -> m (Fold m a b)) -> Fold m a (f b)+ [D] demuxToContainer :: (Monad m, IsMap f, Traversable f) => (a -> Key f) -> (a -> m (Fold m a b)) -> Fold m a (f b)+ [A] demuxScanIO :: (MonadIO m, Ord k) => (a -> k) -> (k -> m (Maybe (Fold m a b))) -> Scanl m a (Maybe (k, b))+ [A] demuxScanGenericIO :: (MonadIO m, IsMap f, Traversable f) => (a -> Key f) -> (Key f -> m (Maybe (Fold m a b))) -> Scanl m a (m (f b), Maybe (Key f, b))+ [A] demuxScanGeneric :: (Monad m, IsMap f, Traversable f) => (a -> Key f) -> (Key f -> m (Maybe (Fold m a b))) -> Scanl m a (m (f b), Maybe (Key f, b))+ [A] demuxScan :: (Monad m, Ord k) => (a -> k) -> (k -> m (Maybe (Fold m a b))) -> Scanl m a (Maybe (k, b))+ [C] demuxKvToMap+ [O] demuxKvToMap :: (Monad m, Ord k) => (k -> m (Fold m a b)) -> Fold m (k, a) (Map k b)+ [N] demuxKvToMap :: (Monad m, Ord k) => (k -> m (Maybe (Fold m a b))) -> Fold m (k, a) (Map k b)+ [C] demuxKvToContainer+ [O] demuxKvToContainer :: (Monad m, IsMap f, Traversable f) => (Key f -> m (Fold m a b)) -> Fold m (Key f, a) (f b)+ [N] demuxKvToContainer :: (Monad m, IsMap f, Traversable f) => (Key f -> m (Maybe (Fold m a b))) -> Fold m (Key f, a) (f b)+ [D] demuxIO :: (MonadIO m, Ord k) => (a -> k) -> (a -> m (Fold m a b)) -> Fold m a (m (Map k b), Maybe (k, b))+ [D] demuxGenericIO :: (MonadIO m, IsMap f, Traversable f) => (a -> Key f) -> (a -> m (Fold m a b)) -> Fold m a (m (f b), Maybe (Key f, b))+ [D] demuxGeneric :: (Monad m, IsMap f, Traversable f) => (a -> Key f) -> (a -> m (Fold m a b)) -> Fold m a (m (f b), Maybe (Key f, b))+ [D] demux :: (Monad m, Ord k) => (a -> k) -> (a -> m (Fold m a b)) -> Fold m a (m (Map k b), Maybe (k, b))+ [A] classifyScanIO :: (MonadIO m, Ord k) => (a -> k) -> Fold m a b -> Scanl m a (Maybe (k, b))+ [A] classifyScanGenericIO :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) => (a -> Key f) -> Fold m a b -> Scanl m a (m (f b), Maybe (Key f, b))+ [A] classifyScanGeneric :: (Monad m, IsMap f, Traversable f, Ord (Key f)) => (a -> Key f) -> Fold m a b -> Scanl m a (m (f b), Maybe (Key f, b))+ [A] classifyScan :: (MonadIO m, Ord k) => (a -> k) -> Fold m a b -> Scanl m a (Maybe (k, b))+ [D] classifyIO :: (MonadIO m, Ord k) => (a -> k) -> Fold m a b -> Fold m a (m (Map k b), Maybe (k, b))+ [D] classifyGenericIO :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) => (a -> Key f) -> Fold m a b -> Fold m a (m (f b), Maybe (Key f, b))+ [D] classifyGeneric :: (Monad m, IsMap f, Traversable f, Ord (Key f)) => (a -> Key f) -> Fold m a b -> Fold m a (m (f b), Maybe (Key f, b))+ [D] classify :: (Monad m, Ord k) => (a -> k) -> Fold m a b -> Fold m a (m (Map k b), Maybe (k, b))+ [A] bracketIO :: (MonadIO m, MonadCatch m) => IO x -> (x -> IO c) -> (x -> Fold m a b) -> Fold m a b+ [A] before :: Monad m => m x -> Fold m a b -> Fold m a b+[A] Streamly.Internal.Data.CString+ [A] splicePtrN :: MutByteArray -> Ptr Word8 -> Int -> IO Int+ [A] spliceCString :: MutByteArray -> CString -> IO Int+ [A] splice :: MutByteArray -> MutByteArray -> IO Int+ [A] putCString :: MutByteArray -> Int -> CString -> IO Int+ [A] length :: MutByteArray -> IO Int+[C] Streamly.Internal.Data.Array.Generic+ [C] Array+ [R] [arrLen] :: Array a -> {-# UNPACK #-} !Int+ [A] [arrEnd] :: Array a -> {-# UNPACK #-} !Int+ [R] GHC.Show.Show+ [R] GHC.Read.Read+ [R] GHC.Classes.Ord+ [R] GHC.Classes.Eq+ [R] writeWith :: MonadIO m => Int -> Fold m a (Array a)+ [D] writeN :: MonadIO m => Int -> Fold m a (Array a)+ [R] writeLastN :: MonadIO m => Int -> Fold m a (Array a)+ [D] write :: MonadIO m => Fold m a (Array a)+ [A] unsafeThaw :: Array a -> MutArray a+ [A] unsafeSliceOffLen :: Int -> Int -> Array a -> Array a+ [A] unsafeGetIndex :: Int -> Array a -> a+ [A] unsafeFreeze :: MutArray a -> Array a+ [A] toParserK :: Monad m => Parser a m b -> ParserK (Array a) m b+ [D] strip :: (a -> Bool) -> Array a -> Array a+ [A] parsePos :: Monad m => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseErrorPos b)+ [A] parseBreakPos :: forall m a b. Monad m => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseErrorPos b, StreamK m (Array a))+ [A] parseBreak :: forall m a b. Monad m => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b, StreamK m (Array a))+ [A] parse :: Monad m => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b)+ [D] getSliceUnsafe :: Int -> Int -> Array a -> Array a+ [D] getIndexUnsafe :: Int -> Array a -> a+ [A] fromCString# :: MonadIO m => Addr# -> m (Array Word8)+ [D] fromByteStr# :: Addr# -> Array Word8+ [A] dropAround :: (a -> Bool) -> Array a -> Array a+ [A] createWith :: MonadIO m => Int -> Fold m a (Array a)+ [A] createOfLast :: MonadIO m => Int -> Fold m a (Array a)+[C] Streamly.Internal.Data.Array+ [D] writeN :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+ [D] writeLastN :: (Unbox a, MonadIO m) => Int -> Fold m a (Array a)+ [D] write :: forall m a. (MonadIO m, Unbox a) => Fold m a (Array a)+ [A] unsnoc :: Unbox a => Array a -> Maybe (Array a, a)+ [A] unsafeSliceOffLen :: forall a. Unbox a => Int -> Int -> Array a -> Array a+ [A] unsafeReader :: forall m a. (Monad m, Unbox a) => Unfold m (Array a) a+ [D] unsafePinnedCreateOf :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+ [C] unsafePinnedAsPtr+ [O] unsafePinnedAsPtr :: MonadIO m => Array a -> (Ptr a -> m b) -> m b+ [N] unsafePinnedAsPtr :: MonadIO m => Array a -> (Ptr a -> Int -> IO b) -> m b+ [D] unsafeIndexIO :: forall a. Unbox a => Int -> Array a -> IO a+ [A] unsafeGetIndexRevIO :: forall a. Unbox a => Int -> Array a -> IO a+ [A] unsafeGetIndexRev :: forall a. Unbox a => Int -> Array a -> a+ [A] unsafeGetIndexIO :: forall a. Unbox a => Int -> Array a -> IO a+ [A] unsafeGetIndex :: forall a. Unbox a => Int -> Array a -> a+ [A] unsafeFromForeignPtr :: MonadIO m => ForeignPtr Word8 -> Int -> m (Array Word8)+ [A] unsafeCreateOf' :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+ [A] unsafeCast :: Array a -> Array b+ [A] unsafeBreakAt :: Unbox a => Int -> Array a -> (Array a, Array a)+ [A] unsafeAsForeignPtr :: MonadIO m => Array a -> (ForeignPtr a -> Int -> IO b) -> m b+ [A] uncons :: Unbox a => Array a -> Maybe (a, Array a)+ [A] toParserK :: (Monad m, Unbox a) => Parser a m b -> ParserK (Array a) m b+ [A] tail :: Unbox a => Array a -> Array a+ [A] splitterFromLen :: forall m a. (Monad m, Unbox a) => Int -> Int -> Unfold m (Array a) (Array a)+ [D] splitOn :: (Monad m, Unbox a) => (a -> Bool) -> Array a -> Stream m (Array a)+ [A] splitEndBy_ :: (Monad m, Unbox a) => (a -> Bool) -> Array a -> Stream m (Array a)+ [A] splitEndBy :: (MonadIO m, Unbox a) => (a -> Bool) -> Array a -> Stream m (Array a)+ [D] splitAt :: Unbox a => Int -> Array a -> (Array a, Array a)+ [D] slicerFromLen :: forall m a. (Monad m, Unbox a) => Int -> Int -> Unfold m (Array a) (Array a)+ [D] sliceIndexerFromLen :: forall m a. (Monad m, Unbox a) => Int -> Int -> Unfold m (Array a) (Int, Int)+ [A] serialize' :: Serialize a => a -> Array Word8+ [A] scanCompactMin' :: forall m a. (MonadIO m, Unbox a) => Int -> Scanl m (Array a) (Maybe (Array a))+ [A] scanCompactMin :: forall m a. (MonadIO m, Unbox a) => Int -> Scanl m (Array a) (Maybe (Array a))+ [A] revDropWhile :: Unbox a => (a -> Bool) -> Array a -> Array a+ [A] revBreakEndBy_ :: Unbox a => (a -> Bool) -> Array a -> (Array a, Array a)+ [A] revBreakEndBy :: Unbox a => (a -> Bool) -> Array a -> (Array a, Array a)+ [D] readerUnsafe :: forall m a. (Monad m, Unbox a) => Unfold m (Array a) a+ [D] pinnedSerialize :: Serialize a => a -> Array Word8+ [D] pinnedFromListN :: Unbox a => Int -> [a] -> Array a+ [D] pinnedFromList :: Unbox a => [a] -> Array a+ [D] pinnedCreateOf :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+ [D] pinnedCreate :: forall m a. (MonadIO m, Unbox a) => Fold m a (Array a)+ [D] pinnedCompactLE :: (MonadIO m, Unbox a) => Int -> Stream m (Array a) -> Stream m (Array a)+ [R] pinnedClone :: MonadIO m => Array a -> m (Array a)+ [D] pinnedChunksOf :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> Stream m (Array a)+ [A] parsePos :: (Monad m, Unbox a) => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseErrorPos b)+ [A] parseBreakPos :: (Monad m, Unbox a) => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseErrorPos b, StreamK m (Array a))+ [R] parseBreakChunksK :: forall m a b. (MonadIO m, Unbox a) => Parser a m b -> StreamK m (Array a) -> m (Either ParseError b, StreamK m (Array a))+ [A] parseBreak :: (Monad m, Unbox a) => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b, StreamK m (Array a))+ [A] parse :: (Monad m, Unbox a) => ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b)+ [A] listEq :: (Unbox a, Ord a) => [a] -> Array a -> Bool+ [A] listCmp :: (Unbox a, Ord a) => [a] -> Array a -> Ordering+ [D] lPinnedCompactGE :: (MonadIO m, Unbox a) => Int -> Fold m (Array a) () -> Fold m (Array a) ()+ [D] lCompactGE :: (MonadIO m, Unbox a) => Int -> Fold m (Array a) () -> Fold m (Array a) ()+ [D] interposeSuffix :: forall m a. (Monad m, Unbox a) => a -> Stream m (Array a) -> Stream m a+ [D] interpose :: (Monad m, Unbox a) => a -> Stream m (Array a) -> Stream m a+ [D] intercalateSuffix :: (Monad m, Unbox a) => Array a -> Stream m (Array a) -> Stream m a+ [A] init :: Unbox a => Array a -> Array a+ [A] indexerFromLen :: forall m a. (Monad m, Unbox a) => Int -> Int -> Unfold m (Array a) (Int, Int)+ [A] head :: Unbox a => Array a -> Maybe a+ [D] getSliceUnsafe :: forall a. Unbox a => Int -> Int -> Array a -> Array a+ [D] getIndexUnsafe :: forall a. Unbox a => Int -> Array a -> a+ [A] fromW16CString# :: MonadIO m => Addr# -> m (Array Word16)+ [A] fromW16CString :: MonadIO m => Ptr Word8 -> m (Array Word16)+ [C] fromPtrN+ [O] fromPtrN :: Int -> Ptr Word8 -> Array Word8+ [N] fromPtrN :: MonadIO m => Int -> Ptr Word8 -> m (Array Word8)+ [A] fromListN' :: Unbox a => Int -> [a] -> Array a+ [A] fromList' :: Unbox a => [a] -> Array a+ [A] fromCString# :: MonadIO m => Addr# -> m (Array Word8)+ [A] fromCString :: MonadIO m => Ptr Word8 -> m (Array Word8)+ [D] fromByteStr# :: Addr# -> Array Word8+ [D] fromByteStr :: Ptr Word8 -> Array Word8+ [A] foldRev :: Unbox a => Fold Identity a b -> Array a -> b+ [A] foldM :: (Monad m, Unbox a) => Fold m a b -> Array a -> m b+ [D] foldBreakChunksK :: forall m a b. (MonadIO m, Unbox a) => Fold m a b -> StreamK m (Array a) -> m (b, StreamK m (Array a))+ [A] foldBreak :: forall m a b. (MonadIO m, Unbox a) => Fold m a b -> StreamK m (Array a) -> m (b, StreamK m (Array a))+ [D] fold :: (Monad m, Unbox a) => Fold m a b -> Array a -> m b+ [D] fPinnedCompactGE :: (MonadIO m, Unbox a) => Int -> Fold m (Array a) (Array a)+ [D] fCompactGE :: (MonadIO m, Unbox a) => Int -> Fold m (Array a) (Array a)+ [A] dropWhile :: Unbox a => (a -> Bool) -> Array a -> Array a+ [A] dropAround :: Unbox a => (a -> Bool) -> Array a -> Array a+ [C] deserialize+ [O] deserialize :: Serialize a => Array Word8 -> a+ [N] deserialize :: Serialize a => Array Word8 -> (a, Array Word8)+ [A] createOfLast :: (Unbox a, MonadIO m) => Int -> Fold m a (Array a)+ [A] createOf' :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+ [A] createCompactMin' :: (MonadIO m, Unbox a) => Int -> Fold m (Array a) (Array a)+ [A] createCompactMin :: (MonadIO m, Unbox a) => Int -> Fold m (Array a) (Array a)+ [A] create' :: forall m a. (MonadIO m, Unbox a) => Fold m a (Array a)+ [A] concatSepBy :: (Monad m, Unbox a) => a -> Stream m (Array a) -> Stream m a+ [A] concatEndBySeq :: (Monad m, Unbox a) => Array a -> Stream m (Array a) -> Stream m a+ [A] concatEndBy :: forall m a. (Monad m, Unbox a) => a -> Stream m (Array a) -> Stream m a+ [A] compactSepByByte_ :: MonadIO m => Word8 -> Stream m (Array Word8) -> Stream m (Array Word8)+ [D] compactOnByteSuffix :: MonadIO m => Word8 -> Stream m (Array Word8) -> Stream m (Array Word8)+ [D] compactOnByte :: MonadIO m => Word8 -> Stream m (Array Word8) -> Stream m (Array Word8)+ [A] compactMin :: (MonadIO m, Unbox a) => Int -> Stream m (Array a) -> Stream m (Array a)+ [A] compactMax' :: (MonadIO m, Unbox a) => Int -> Stream m (Array a) -> Stream m (Array a)+ [A] compactMax :: (MonadIO m, Unbox a) => Int -> Stream m (Array a) -> Stream m (Array a)+ [D] compactLE :: (MonadIO m, Unbox a) => Int -> Stream m (Array a) -> Stream m (Array a)+ [D] compactGE :: (MonadIO m, Unbox a) => Int -> Stream m (Array a) -> Stream m (Array a)+ [A] compactEndByLn_ :: MonadIO m => Stream m (Array Word8) -> Stream m (Array Word8)+ [A] compactEndByByte_ :: MonadIO m => Word8 -> Stream m (Array Word8) -> Stream m (Array Word8)+ [R] clone :: MonadIO m => Array a -> m (Array a)+ [A] chunksOf' :: forall m a. (MonadIO m, Unbox a) => Int -> Stream m a -> Stream m (Array a)+ [A] chunksEndByLn' :: MonadIO m => Stream m Word8 -> Stream m (Array Word8)+ [A] chunksEndByLn :: MonadIO m => Stream m Word8 -> Stream m (Array Word8)+ [A] chunksEndBy' :: forall m a. (MonadIO m, Unbox a) => (a -> Bool) -> Stream m a -> Stream m (Array a)+ [A] chunksEndBy :: forall m a. (MonadIO m, Unbox a) => (a -> Bool) -> Stream m a -> Stream m (Array a)+ [D] castUnsafe :: Array a -> Array b+ [D] breakOn :: MonadIO m => Word8 -> Array Word8 -> m (Array Word8, Maybe (Array Word8))+ [A] breakEndBy_ :: Unbox a => (a -> Bool) -> Array a -> (Array a, Array a)+ [A] breakEndByWord8_ :: MonadIO m => Word8 -> Array Word8 -> m (Array Word8, Maybe (Array Word8))+ [A] breakEndBy :: Unbox a => (a -> Bool) -> Array a -> (Array a, Array a)+ [A] breakAt :: Unbox a => Int -> Array a -> (Array a, Array a)+ [A] asCWString :: Array a -> (CWString -> IO b) -> IO b+[C] Streamly.Internal.Control.Exception+ [A] Priority+ [A] Priority2 :: Priority+ [A] Priority1 :: Priority+ [A] GHC.Show.Show+ [A] instance GHC.Show.Show Streamly.Internal.Control.Exception.Priority+ [A] AcquireIO+ [A] AcquireIO :: (forall b c. Priority -> IO b -> (b -> IO c) -> IO (b, IO ())) -> AcquireIO+ [A] withAcquireIO :: (MonadIO m, MonadMask m) => (AcquireIO -> m a) -> m a+ [A] releaser :: MonadIO m => IORef (a, IntMap (IO b), IntMap (IO b)) -> m ()+ [A] registerWith :: Priority -> AcquireIO -> IO () -> IO ()+ [A] register :: AcquireIO -> IO () -> IO ()+ [A] hook :: AcquireIO -> IO () -> IO (IO ())+ [A] allocator :: MonadIO m => IORef (Int, IntMap (IO ()), IntMap (IO ())) -> Priority -> IO a -> (a -> IO b) -> m (a, m ())+ [A] acquire_ :: AcquireIO -> IO b -> (b -> IO c) -> IO b+ [A] acquireWith :: Priority -> AcquireIO -> IO b -> (b -> IO c) -> IO (b, IO ())+ [A] acquire :: AcquireIO -> IO b -> (b -> IO c) -> IO (b, IO ())
docs/Changelog.md view
@@ -1,8 +1,140 @@ # Changelog +## 0.3.1 (May 2026)++* Fixed `Path.fromString` truncation when unicode chars are present.+* Fixed `DirIO.followSymlinks` option not working correctly on macOS.++## 0.3.0 (Sep 2025)++See [0.2.2-0.3.0 API Changelog](/core/docs/ApiChangelogs/0.2.2-0.3.0.txt) for a+full list of deprecations, additions, and changes to the function signatures.++### Enhancements++* Added APIs for prompt cleanup of resources, allowing guaranteed+ cleanup as an alternative to GC-based cleanup.+* Added operations for fair nesting of inner and outer streams for+ exploring them equally, generally useful but especially useful for logic+ programming use cases.+* Introduced `Streamly.Data.Scanl` with a new `Scanl` type. Scans can+ split a stream into multiple streams, process them independently, and+ merge the results. The `Fold` type is now split into `Fold` and `Scanl`.+* Added `RingArray` module for high-performance, unboxed circular buffers.+* Added `Streamly.FileSystem.Path` module with a `Path` type for flexibly typed+ file system paths.+* Added `Streamly.FileSystem.DirIO` and `Streamly.FileSystem.FileIO` to replace+ the deprecated `Streamly.FileSystem.Dir` and `Streamly.FileSystem.File`. The+ new modules use Streamly’s native `Path` type instead of `FilePath`. `DirIO`+ APIs take a `ReadOptions` argument, and its directory read APIs do not follow+ symlinks by default.+* Removed `Storable` constraint from:+ - `Streamly.Data.Stream.isInfixOf`+ - `Streamly.Data.Array.writeLastN`++### Deprecations++Following APIs/modules are deprecated and renamed or replaced with new+APIs.++* `Streamly.FileSystem.Dir`, `Streamly.FileSystem.File` have been replaced by+ new modules.+* Renamed `writeN`-like APIs to `createOf`-like in Array modules.+* Renamed `new`-like APIs to `emptyOf`-like in Array modules.+* In the Fold module renamed `indexGeneric`, `lengthGeneric`, and `foldlM1'` to+ `genericIndex`, `genericLength`, and `foldl1M'` respectively.++### Internal API Changes++* In `Streamly.Internal.Data.Parser`, constructors `Partial`, `Continue`, and+ `Done` are deprecated and replaced with `SPartial`, `SContinue`, and `SDone`.+ Migration steps:+ * In parser step functions:+ - `Partial n` -> `SPartial (1-n)`+ - `Continue n` -> `SContinue (1-n)`+ - `Done n` -> `SDone (1-n)`+ - `Error` -> `SError`+ * Extract function now returns `Parser.Final` (instead of `Parser.Step`):+ - `Continue n` -> `FContinue (-n)`+ - `Done n` -> `FDone (-n)`+ - `Partial n` -> `FContinue (-n)`+ - `Error` -> `FError`+ * If `n` is used for decision-making, the logic must be updated accordingly.+ See docs for details.+* Internal (mut)array functions now use explicit IO callbacks instead of lifted+ callbacks.+* Removed `Storable` constraint from several ring buffer functions.+* Added `Streamly.Internal.Data.IORef` module exposing `IORef` and related+ functions.++## 0.2.2 (Jan 2024)++* Add fixities `infixr 5` for `cons` and `consM` functions.+* Fix a bug in Array `Eq` instance when the type is a sum type with+ differently sized constructors.+* lpackArraysChunksOf, compact, writeChunksWith, putChunksWith now take the+ buffer size in number of array elements instead of bytes.++## 0.2.1 (Dec 2023)++* Make the serialization of the unit constructor deterministic.+* Expose `pinnedSerialize` & `deserialize` via `Streamly.Data.Array`.++## 0.2.0 (Nov 2023)++See [0.1.0-0.2.0 API Changelog](https://github.com/composewell/streamly/blob/streamly-0.10.0/core/docs/ApiChangelogs/0.1.0-0.2.0.txt)+for a full list of API changes in this release. Only a few significant+changes are mentioned here.++### Breaking Changes++* `ParserK` in `Streamly.Data.ParserK` is not implicitly specialized+ to arrays anymore. To adapt to the new code, change `ParserK a m+ b` to `ParserK (Array a) m b` where the `Array` type comes from+ `Streamly.Data.Array`. This change also affected the signatures of+ `parseChunks` and `parseBreakChunks`.+* Changed the signature of 'Streamly.Data.Stream.handle' to make the+ exception handler monadic.+* Behavior change: Exceptions are now rethrown promptly in `bracketIO`.++### Enhancements++* __Serialization__: Added a `Streamly.Data.MutByteArray` module with a+ `Serialize` type class for fast binary serialization. The Data.Array+ module supplies the `serialize` and `deserialize` operations for arrays.+* __Unpinned Arrays__: Unboxed arrays are now created unpinned by default,+ they were created pinned earlier. During IO operations, unpinned arrays+ are automatically copied to pinned memory. When arrays are directly+ passed to IO operations programmers can choose to create them pinned to+ avoid a copy. To create pinned arrays, use the internal APIs with the+ `pinned*` prefix.+* StreamK now supports native exception handling routines (handle, bracketIO).+ Earlier we had to convert it to the `Stream` type for exception handling.++### Deprecations++See [0.1.0-0.2.0 API Changelog](https://github.com/composewell/streamly/blob/streamly-0.10.0/core/docs/ApiChangelogs/0.1.0-0.2.0.txt)+for a full list of deprecations.++### Internal API Changes++* Fold constructor has changed, added a `final` field to support+ finalization and cleanup of a chain of folds. The `extract` field is+ now used only for mapping the fold internal state to fold result for+ scanning purposes. If your fold does not require cleanup you can just use+ your existing `extract` function as `final` as well to adapt to this change.+* Many low level internal modules have been removed, they are entirely+ exported from higher level internal modules. If you were importing any+ of the missing low level modules then import the higher level modules instead.+* Internal module changes:+ * Streamly.Internal.Serialize.FromBytes -> Streamly.Internal.Data.Binary.Parser+ * Streamly.Internal.Serialize.ToBytes -> Streamly.Internal.Data.Binary.Stream+ * Streamly.Internal.Data.Unbox is now exported via Streamly.Internal.Data.Serialize+ * Streamly.Internal.Data.IORef.Unboxed is now exported via Streamly.Internal.Data.Serialize+ ## 0.1.0 (March 2023) -Also see [streamly-core-0.1.0 API Changelog](/core/docs/ApiChangelogs/0.1.0.txt) or+Also see [streamly-core-0.1.0 API Changelog](https://github.com/composewell/streamly/blob/streamly-0.10.0/core/docs/ApiChangelogs/0.1.0.txt) or https://hackage.haskell.org/package/streamly-core-0.1.0/docs/docs/ApiChangelogs/0.1.0.txt `streamly` package is split into two packages, (1) `streamly-core` that
docs/Readme.md view
@@ -1,1 +1,2 @@-Please refer to the "streamly" package for tutorials and other documentation.+Please refer to the [streamly](/docs/User/Tutorials/using-streamly.md)+package for tutorials and other documentation.
− src/DocTestDataArray.hs
@@ -1,14 +0,0 @@-{- $setup->>> :m->>> :set -XFlexibleContexts->>> import Data.Function ((&))->>> import Data.Functor.Identity (Identity(..))->>> import System.IO.Unsafe (unsafePerformIO)-->>> import Streamly.Data.Array (Array)->>> import Streamly.Data.Stream (Stream)-->>> import qualified Streamly.Data.Array as Array->>> import qualified Streamly.Data.Fold as Fold->>> import qualified Streamly.Data.Stream as Stream--}
− src/DocTestDataFold.hs
@@ -1,28 +0,0 @@-{- $setup->>> :m->>> :set -XFlexibleContexts->>> import Control.Monad (void)->>> import qualified Data.Foldable as Foldable->>> import Data.Function ((&))->>> import Data.Functor.Identity (Identity, runIdentity)->>> import Data.IORef (newIORef, readIORef, writeIORef)->>> import Data.Maybe (fromJust, isJust)->>> import Data.Monoid (Endo(..), Last(..), Sum(..))-->>> import Streamly.Data.Array (Array)->>> import Streamly.Data.Fold (Fold, Tee(..))->>> import Streamly.Data.Stream (Stream)-->>> import qualified Streamly.Data.Array as Array->>> import qualified Streamly.Data.Fold as Fold->>> import qualified Streamly.Data.MutArray as MutArray->>> import qualified Streamly.Data.Parser as Parser->>> import qualified Streamly.Data.Stream as Stream->>> import qualified Streamly.Data.StreamK as StreamK->>> import qualified Streamly.Data.Unfold as Unfold--For APIs that have not been released yet.-->>> import qualified Streamly.Internal.Data.Fold as Fold->>> import qualified Streamly.Internal.Data.Fold.Window as FoldW--}
− src/DocTestDataMutArray.hs
@@ -1,10 +0,0 @@-{- $setup->>> :m->>> import qualified Streamly.Data.Fold as Fold->>> import qualified Streamly.Data.MutArray as MutArray->>> import qualified Streamly.Data.Stream as Stream--For APIs that have not been released yet.-->>> import Streamly.Internal.Data.Array.Mut as MutArray--}
− src/DocTestDataMutArrayGeneric.hs
@@ -1,10 +0,0 @@-{- $setup->>> :m->>> import qualified Streamly.Data.Fold as Fold->>> import qualified Streamly.Data.MutArray.Generic as MutArray->>> import qualified Streamly.Data.Stream as Stream--For APIs that have not been released yet.-->>> import Streamly.Internal.Data.Array.Generic.Mut.Type as MutArray--}
− src/DocTestDataParser.hs
@@ -1,20 +0,0 @@-{- $setup->>> :m->>> import Control.Applicative ((<|>))->>> import Data.Bifunctor (second)->>> import Data.Char (isSpace)->>> import qualified Data.Foldable as Foldable->>> import qualified Data.Maybe as Maybe-->>> import Streamly.Data.Fold (Fold)->>> import Streamly.Data.Parser (Parser)-->>> import qualified Streamly.Data.Fold as Fold->>> import qualified Streamly.Data.Parser as Parser->>> import qualified Streamly.Data.Stream as Stream--For APIs that have not been released yet.-->>> import qualified Streamly.Internal.Data.Fold as Fold->>> import qualified Streamly.Internal.Data.Parser as Parser--}
− src/DocTestDataStream.hs
@@ -1,37 +0,0 @@-{- $setup-->>> :m->>> import Control.Concurrent (threadDelay)->>> import Control.Monad (void)->>> import Control.Monad.IO.Class (MonadIO (liftIO))->>> import Control.Monad.Trans.Class (lift)->>> import Control.Monad.Trans.Identity (runIdentityT)->>> import Data.Either (fromLeft, fromRight, isLeft, isRight, either)->>> import Data.Maybe (fromJust, isJust)->>> import Data.Function (fix, (&))->>> import Data.Functor.Identity (runIdentity)->>> import Data.IORef->>> import Data.Semigroup (cycle1)->>> import GHC.Exts (Ptr (Ptr))->>> import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))-->>> hSetBuffering stdout LineBuffering->>> effect n = print n >> return n-->>> import Streamly.Data.Stream (Stream)->>> import qualified Streamly.Data.Array as Array->>> import qualified Streamly.Data.Fold as Fold->>> import qualified Streamly.Data.Stream as Stream->>> import qualified Streamly.Data.Unfold as Unfold->>> import qualified Streamly.Data.Parser as Parser->>> import qualified Streamly.FileSystem.Dir as Dir--For APIs that have not been released yet.-->>> import qualified Streamly.Internal.Data.Fold as Fold->>> import qualified Streamly.Internal.Data.Fold.Window as Window->>> import qualified Streamly.Internal.Data.Parser as Parser->>> import qualified Streamly.Internal.Data.Stream as Stream->>> import qualified Streamly.Internal.Data.Unfold as Unfold->>> import qualified Streamly.Internal.FileSystem.Dir as Dir--}
− src/DocTestDataStreamK.hs
@@ -1,20 +0,0 @@-{- $setup-->>> :m->>> import Data.Function (fix, (&))->>> import Data.Semigroup (cycle1)-->>> effect n = print n >> return n-->>> import Streamly.Data.StreamK (StreamK)->>> import qualified Streamly.Data.Fold as Fold->>> import qualified Streamly.Data.Parser as Parser->>> import qualified Streamly.Data.Stream as Stream->>> import qualified Streamly.Data.StreamK as StreamK->>> import qualified Streamly.FileSystem.Dir as Dir--For APIs that have not been released yet.-->>> import qualified Streamly.Internal.Data.Stream.StreamK as StreamK->>> import qualified Streamly.Internal.FileSystem.Dir as Dir--}
− src/DocTestDataUnfold.hs
@@ -1,12 +0,0 @@-{- $setup-->>> :m->>> import Streamly.Data.Unfold (Unfold)->>> import qualified Streamly.Data.Fold as Fold->>> import qualified Streamly.Data.Stream as Stream->>> import qualified Streamly.Data.Unfold as Unfold--For APIs that have not been released yet.-->>> import qualified Streamly.Internal.Data.Unfold as Unfold--}
src/Streamly/Console/Stdio.hs view
@@ -7,14 +7,37 @@ -- Stability : released -- Portability : GHC ----- Combinators to work with standard input, output and error streams.+-- Combinators to work with standard input, output, and error streams. This+-- module supports reading and writing binary data or UTF-8 encoded text only.+-- However, it is possible to use specific encoders and decoders to implement+-- other encodings. --+-- These streaming APIs use the stdin and stdout handles for reading from and+-- writing to the console. The reads and writes are buffered, meaning each+-- stream has its own buffer. Be cautious when switching between these APIs and+-- handle-based APIs (e.g., readChars, getLine), between different stream APIs+-- (e.g., readChars, readChunks), or even between different calls to the same+-- API (e.g., readChars, readChars). If you switch from one stream to another,+-- you should drain the first stream completely if you care about preserving+-- any buffered data.+-- -- See also: "Streamly.Internal.Console.Stdio" +-- XXX put examples of repeatM getLine from stream module+-- XXX put examples of using parseBreak or foldBreak.+ module Streamly.Console.Stdio (+ -- * Streams (stdin)+ read+ , readChars+ , readChunks++ -- * Streams (srdout)+ , putChunks+ -- * Unfolds (stdin)- reader+ , reader , chunkReader -- * Write (stdout)@@ -24,33 +47,8 @@ -- * Write (stderr) , writeErr , writeErrChunks-- -- * Deprecated- , read- , readChunks ) where -import Control.Monad.IO.Class (MonadIO(..))-import Data.Word (Word8)-import Streamly.Internal.Data.Array.Type (Array)-import Streamly.Internal.Data.Unfold (Unfold)--import Streamly.Internal.Console.Stdio hiding (read, readChunks) import Prelude hiding (read)---- Same as 'reader'------ @since 0.8.0-{-# DEPRECATED read "Please use 'reader' instead" #-}-{-# INLINE read #-}-read :: MonadIO m => Unfold m () Word8-read = reader---- Same as 'chunkReader'------ @since 0.8.0-{-# DEPRECATED readChunks "Please use 'chunkReader' instead" #-}-{-# INLINE readChunks #-}-readChunks :: MonadIO m => Unfold m () (Array Word8)-readChunks = chunkReader+import Streamly.Internal.Console.Stdio
+ src/Streamly/Control/Exception.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE CPP #-}+-- |+-- Module : Streamly.Control.Exception+-- Copyright : (c) 2025 Composewell Technologies+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : released+-- Portability : GHC+--+-- Exception handling and resource managment operations complementing+-- the "Control.Exception" module in base package.++module Streamly.Control.Exception+ (+ -- * Setup+ -- | To execute the code examples provided in this module in ghci, please+ -- run the following commands first.+ --+ -- $setup+ --+ -- * Resource Management+ -- | Exception safe, thread safe resource managment operations, similar to+ -- but more powerful than the @bracket@ and @finally@ operations available+ -- in the base package.+ --+ -- These operations support allocation and free only in the IO monad,+ -- hence the IO suffix.+ --+ AcquireIO+ , withAcquireIO+ , acquire+ , register+ , hook+ )+where++import Streamly.Internal.Control.Exception++#include "DocTestControlException.hs"
src/Streamly/Data/Array.hs view
@@ -29,59 +29,88 @@ -- $overview -- * The Array Type- A.Array+ Array + -- * Pinning & Unpinning+ -- | Arrays are created unpinned by default unless pinned versions of+ -- creation APIs are used. Look for APIs with @pinned@ prefix in+ -- "Streamly.Internal.Data.Array" for some unreleased pinned creation APIs.+ -- If an array is to be sent to the OS without any further modification+ -- then it should be created pinned in the first place instead of pinning+ -- it later. Pinning an unpinned array has a copy overhead. OS interfacing+ -- APIs create a pinned array directly or convert an unpinned array to+ -- pinned array before sending it to the OS.+ , pin+ , unpin+ , isPinned+ -- * Construction -- | When performance matters, the fastest way to generate an array is- -- 'writeN'. 'IsList' and 'IsString' instances can be+ -- 'createOf'. 'IsList' and 'IsString' instances can be -- used to conveniently construct arrays from literal values. -- 'OverloadedLists' extension or 'fromList' can be used to construct an -- array from a list literal. Similarly, 'OverloadedStrings' extension or -- 'fromList' can be used to construct an array from a string literal. - -- Pure List APIs- , A.fromListN- , A.fromList+ -- ** From Stream+ , createOf+ , create+ , createOfLast -- drop old (ring buffer) - -- Monadic APIs- , A.writeN -- drop new- , A.write -- full buffer- , writeLastN -- drop old (ring buffer)+ -- ** From List+ , fromListN+ , fromList - -- * Conversion+ -- * To List -- 'GHC.Exts.toList' from "GHC.Exts" can be used to convert an array to a -- list.- , A.toList+ , toList + -- * To Stream+ , read+ , readRev+ -- * Unfolds- , A.reader- , A.readerRev+ , reader+ , readerRev + -- * Stream of Arrays+ , chunksOf+ , toParserK+ , parse+ , parseBreak+ , parsePos+ , parseBreakPos+ -- * Casting , cast , asBytes -- * Random Access- , A.length+ , length -- , (!!)- , A.getIndex+ , getIndex - -- * Unbox Type Class+ -- * Serialization+ , serialize'+ , deserialize++ -- * Re-exports , Unbox (..)+ , Serialize(..) -- * Deprecated- , read- , readRev+ , pinnedSerialize+ , writeN -- drop new+ , write -- full buffer+ , writeLastN ) where -#include "inline.hs"--import Streamly.Internal.Data.Unfold (Unfold)-import Streamly.Internal.Data.Array as A hiding (read, readRev)+import Streamly.Internal.Data.Array+import Streamly.Internal.Data.MutByteArray (Unbox(..), Serialize(..)) -import Streamly.Internal.Data.Unboxed (Unbox (..))-import Prelude hiding (read)+import Prelude hiding (read, length) #include "DocTestDataArray.hs" @@ -97,7 +126,7 @@ -- -- Convert array to stream, and fold the stream: ----- >>> fold f arr = Stream.unfold Array.reader arr & Stream.fold f+-- >>> fold f arr = Array.read arr & Stream.fold f -- >>> fold Fold.sum (Array.fromList [1,2,3::Int]) -- 6 --@@ -105,18 +134,17 @@ -- -- Convert array to stream, transform, and fold back to array: ----- >>> amap f arr = Stream.unfold Array.reader arr & fmap f & Stream.fold Array.write+-- >>> amap f arr = Array.read arr & fmap f & Stream.fold (Array.createOf (Array.length arr)) -- >>> amap (+1) (Array.fromList [1,2,3::Int]) -- fromList [2,3,4] -- -- == Pinned and Unpinned Arrays ----- The array type can use both pinned and unpinned memory under the hood.--- Currently the array creation APIs create arrays in pinned memory but it will--- change to unpinned in future releases. The change should not affect users--- functionally unless they are directly accessing the internal memory of the--- array via internal APIs. As of now unpinned arrays can be created using--- unreleased APIs.+-- The array type can use both pinned and unpinned memory under the hood. The+-- default array creation operations create unpinned arrays. IO operations+-- automatically copy an array to pinned memory if the array passed to it is+-- unpinned. Programmers can use appropriate pinned array generation APIs to+-- reduce the copying if it happens. -- -- Unpinned arrays have the advantage of allowing automatic defragmentation of -- the memory by GC. Whereas pinned arrays have the advantage of not requiring@@ -127,42 +155,26 @@ -- -- == Creating Arrays from Non-IO Streams ----- Array creation folds require 'MonadIO' because they need to sequence effects--- in IO streams. To operate on streams in pure Monads like 'Identity' you can--- morph it to IO monad as follows:+-- Array creation folds require 'MonadIO' otherwise the compiler may+-- incorrectly share the array memory thinking it is pure. ----- The 'MonadIO' based folds can be morphed to 'Identity' stream folds:+-- See the 'fromPureStream' unreleased API to generate an array from an+-- Identity stream safely without using MonadIO constraint. ----- >>> purely = Fold.morphInner (Identity . unsafePerformIO)--- >>> Stream.fold (purely Array.write) $ Stream.fromList [1,2,3::Int]--- Identity fromList [1,2,3]+-- >>> fromPureStream = Stream.fold Array.create . Stream.generalizeInner ----- Since it is a pure stream we can use 'unsafePerformIO' to extract the result--- of fold from IO.+-- >>> stream = Stream.fromList [1,2,3] :: Stream Identity Int+-- >>> fromPureStream stream+-- fromList [1,2,3] ----- Alternatively, 'Identity' streams can be generalized to IO streams:+-- == Performance Considerations ----- >>> pure = Stream.fromList [1,2,3] :: Stream Identity Int--- >>> generally = Stream.morphInner (return . runIdentity)--- >>> Stream.fold Array.write (generally pure :: Stream IO Int)--- fromList [1,2,3]+-- If you are consuming an array piecemeal (uncons, unsnoc) or by slicing,+-- immutable Array type may be a tiny bit better than MutArray because it uses+-- a smaller constructor size. -- -- == Programming Tips -- -- This module is designed to be imported qualified: -- -- >>> import qualified Streamly.Data.Array as Array---- | Same as 'reader'----{-# DEPRECATED read "Please use 'reader' instead" #-}-{-# INLINE_NORMAL read #-}-read :: (Monad m, Unbox a) => Unfold m (Array a) a-read = reader---- | Same as 'readerRev'----{-# DEPRECATED readRev "Please use 'readerRev' instead" #-}-{-# INLINE_NORMAL readRev #-}-readRev :: (Monad m, Unbox a) => Unfold m (Array a) a-readRev = readerRev
src/Streamly/Data/Array/Generic.hs view
@@ -15,29 +15,45 @@ ( Array -- * Construction- , A.fromListN- , A.fromList+ , fromListN+ , fromList -- MonadicAPIs- , A.writeN- , A.write+ , createOf+ , create + -- * Conversion+ , toList+ -- * Streams- , A.read- , A.readRev+ , read+ , readRev -- * Unfolds- , A.reader+ , reader+ -- , A.readerRev + -- * Stream of Arrays+ , chunksOf+ , toParserK+ , parse+ , parseBreak+ , parsePos+ , parseBreakPos+ -- * Random Access- , A.length+ , length+ , getIndex -- -- * Folding Arrays -- , A.streamFold -- , A.fold++ -- * Deprecated+ , writeN+ , write ) where -import Streamly.Internal.Data.Array.Generic (Array)--import qualified Streamly.Internal.Data.Array.Generic as A+import Streamly.Internal.Data.Array.Generic+import Prelude hiding (length, read)
src/Streamly/Data/Fold.hs view
@@ -7,9 +7,163 @@ -- Stability : released -- Portability : GHC ----- Fast, composable stream consumers with ability to terminate, supporting--- stream fusion.+-- The 'Fold' type represents a consumer of a sequence of values, the+-- corresponding dual type is 'Streamly.Data.Stream.Stream' which represents a+-- producer. Both types can perform equivalent transformations on a stream. But+-- only 'Fold' can be used to compose multiple consumers and only 'Stream' can+-- be used to compose multiple producers. --+-- The 'Fold' type represents stream consumers as state machines, that fuse+-- together when composed statically, eliminating function calls or+-- intermediate constructor allocations. Stream fusion helps generate tight,+-- efficient loops similar to the code generated by low-level languages like C.+-- Folds are suitable for high-performance looping operations.+--+-- Operations in this module are designed to be composed statically rather than+-- dynamically. They are inlined to enable static fusion. More importantly,+-- they are not designed to be used recursively. Recursive use will break+-- fusion and will lead to quadratic performance slowdown. For dynamic or+-- recursive composition use the continuation passing style (CPS) operations+-- from the "Streamly.Data.ParserK" module. 'Fold' and+-- 'Streamly.Data.ParserK.ParserK' types are interconvertible via the+-- 'Streamly.Data.Parser.Parser' type.+--+-- == Using Folds+--+-- This module provides elementary folds and fold combinators that can be used+-- to consume a stream of data and reduce it to a final value, or transform it+-- in a stateful manner using scans. A data stream can be reduced into a stream+-- of folded data elements by folding segments of the stream. Fold combinators+-- can be used to compose multiple folds in parallel or to create a pipeline of+-- folds such that the next fold consumes the result of the previous fold. To+-- run these folds on a stream see 'Streamly.Data.Stream.fold',+-- 'Streamly.Data.Stream.scan', 'Streamly.Data.Stream.postscan',+-- 'Streamly.Data.Stream.scanMaybe', 'Streamly.Data.Stream.foldMany' and other+-- operations accepting 'Fold' type as argument "Streamly.Data.Stream".+--+-- == Reducing a Stream+--+-- A 'Fold' is a consumer of a stream of values. A fold driver (such as+-- 'Streamly.Data.Stream.fold') initializes the fold @accumulator@, runs the+-- fold @step@ function in a loop, processing the input stream one element at a+-- time and accumulating the result. The loop continues until the fold+-- terminates, at which point the accumulated result is returned.+--+-- For example, a 'sum' Fold represents a stream consumer that adds the values+-- in the input stream:+--+-- >>> Stream.fold Fold.sum $ Stream.fromList [1..100]+-- 5050+--+-- Conceptually, a 'Fold' is a data type that mimics a strict left fold+-- ('Data.List.foldl'). The above example is similar to a left fold using+-- @(+)@ as the step and @0@ as the initial value of the accumulator:+--+-- >>> Data.List.foldl' (+) 0 [1..100]+-- 5050+--+-- 'Fold's have an early termination capability e.g. the 'one' fold terminates+-- after consuming one element:+--+-- >>> Stream.fold Fold.one $ Stream.fromList [1..]+-- Just 1+--+-- The above example is similar to the following right fold:+--+-- >>> Prelude.foldr (\x _ -> Just x) Nothing [1..]+-- Just 1+--+-- 'Fold's can be combined together using combinators. For example, to create a+-- fold that sums first two elements in a stream:+--+-- >>> sumTwo = Fold.take 2 Fold.sum+-- >>> Stream.fold sumTwo $ Stream.fromList [1..100]+-- 3+--+-- == Parallel Composition+--+-- Folds can be combined to run in parallel on the same input. For example, to+-- compute the average of numbers in a stream without going through the stream+-- twice:+--+-- >>> avg = Fold.teeWith (/) Fold.sum (fmap fromIntegral Fold.length)+-- >>> Stream.fold avg $ Stream.fromList [1.0..100.0]+-- 50.5+--+-- Folds can be combined so as to partition the input stream over multiple+-- folds. For example, to count even and odd numbers in a stream:+--+-- >>> split n = if even n then Left n else Right n+-- >>> stream = fmap split $ Stream.fromList [1..100]+-- >>> countEven = fmap (("Even " ++) . show) Fold.length+-- >>> countOdd = fmap (("Odd " ++) . show) Fold.length+-- >>> f = Fold.partition countEven countOdd+-- >>> Stream.fold f stream+-- ("Even 50","Odd 50")+--+-- == Sequential Composition+--+-- Terminating folds can be combined to parse the stream serially such that the+-- first fold consumes the input until it terminates and the second fold+-- consumes the rest of the input until it terminates:+--+-- >>> f = Fold.splitWith (,) (Fold.take 8 Fold.toList) (Fold.takeEndBy (== '\n') Fold.toList)+-- >>> Stream.fold f $ Stream.fromList "header: hello\n"+-- ("header: ","hello\n")+--+-- == Splitting a Stream+--+-- A 'Fold' can be applied repeatedly on a stream to transform it to a stream+-- of fold results. To split a stream on newlines:+--+-- >>> f = Fold.takeEndBy (== '\n') Fold.toList+-- >>> Stream.fold Fold.toList $ Stream.foldMany f $ Stream.fromList "Hello there!\nHow are you\n"+-- ["Hello there!\n","How are you\n"]+--+-- Similarly, we can split the input of a fold too:+--+-- >>> Stream.fold (Fold.many f Fold.toList) $ Stream.fromList "Hello there!\nHow are you\n"+-- ["Hello there!\n","How are you\n"]+--+-- == Folds vs. Streams+--+-- We can often use streams or folds to achieve the same goal. However, streams+-- are required for composition of producers (e.g.+-- 'Data.Stream.append' or 'Data.Stream.mergeBy') whereas folds are+-- required for composition of consumers (e.g. 'splitWith', 'partition'+-- or 'teeWith').+--+-- Streams are producers, transformations on streams happen on the output side:+--+-- >>> :{+-- f stream =+-- Stream.filter odd stream+-- & fmap (+1)+-- & Stream.fold Fold.sum+-- :}+--+-- >>> f $ Stream.fromList [1..100 :: Int]+-- 2550+--+-- Folds are stream consumers with an input stream and an output value, stream+-- transformations on folds happen on the input side:+--+-- >>> :{+-- f =+-- Fold.filter odd+-- $ Fold.lmap (+1)+-- $ Fold.sum+-- :}+--+-- >>> Stream.fold f $ Stream.fromList [1..100 :: Int]+-- 2550+--+-- Notice the similiarity in the definition of @f@ in both cases, the only+-- difference is the composition by @&@ vs @$@ and the use @lmap@ vs @map@, the+-- difference is due to output vs input side transformations.+--+-- == Experimental APIs+-- -- Please refer to "Streamly.Internal.Data.Fold" for more functions that have -- not yet been released. @@ -21,25 +175,38 @@ -- -- $setup - -- * Overview- -- $overview-- -- * Running A Fold- drive- -- XXX Should we have a stream returning function in fold module?- -- , breakStream- -- * Fold Type - , Fold -- (..)+ Fold -- (..) , Tee (..) + -- * Running A Fold+ -- | 'Streamly.Data.Strem.fold' and 'drive' are the basic fold runners.+ -- Folds can also be used a incremental builders. The 'addOne' and+ -- 'addStream' combinators can be used to incrementally build any type of+ -- structure using a fold, including arrays or a stream of arrays.++ , drive+ -- XXX should rename to "extract". can use "Fold.drive Stream.nil" instead,+ -- for now.+ -- , extractM+ -- , reduce+ , addOne+ -- , snocl+ -- XXX Can we use something like concatEffect to implement snocM?+ -- , snocM+ -- , snoclM+ , addStream+ , duplicate+ -- , isClosed+ -- * Constructors , foldl' , foldlM' , foldl1'- , foldlM1'+ , foldl1M' , foldr'+ , foldtM' -- * Folds -- ** Accumulators@@ -72,10 +239,6 @@ , toListRev , toSet , toIntSet- , toMap- , toMapIO- , demuxToMap- , demuxToMapIO , topBy -- ** Non-Empty Accumulators@@ -97,10 +260,6 @@ , uniqBy , nub , nubInt- , classify- , classifyIO- , demux- , demuxIO -- ** Terminating Folds , one@@ -122,35 +281,12 @@ , and , or - -- * Incremental builders- -- | Mutable arrays ("Streamly.Data.MutArray") are basic builders. You can- -- use the 'Streamly.Data.MutArray.snoc' or- -- 'Streamly.Data.MutArray.writeAppend' operations to incrementally build- -- mutable arrays. The 'addOne' and 'addStream' combinators can be used to- -- incrementally build any type of structure using a fold, including arrays- -- or a stream of arrays.- --- -- Use pinned arrays if you are going to use the data for IO.-- -- XXX should rename to "extract". can use "Fold.drive Stream.nil" instead,- -- for now.- -- , extractM- -- , reduce- , addOne- -- , snocl- -- XXX Can we use something like concatEffect to implement snocM?- -- , snocM- -- , snoclM- , addStream- , duplicate- -- , isClosed-- -- * Combinators- -- | Combinators are modifiers of folds. In the type @Fold m a b@, @a@ is- -- the input type and @b@ is the output type. Transformations can be+ -- * Transformations+ -- | Transformations are modifiers of folds. In the type @Fold m a b@, @a@+ -- is the input type and @b@ is the output type. Transformations can be -- applied either on the input side (contravariant) or on the output side- -- (covariant). Therefore, combinators are of one of the following general- -- shapes:+ -- (covariant). Therefore, transformations have one of the following+ -- general shapes: -- -- * @... -> Fold m a b -> Fold m c b@ (input transformation) -- * @... -> Fold m a b -> Fold m a c@ (output transformation)@@ -173,10 +309,7 @@ , lmap , lmapM - -- ** Scanning and Filtering- , scan- , postscan- , scanMaybe+ -- ** Filtering , filter , filterM @@ -189,12 +322,36 @@ -- ** Trimming , take- -- , takeInterval , takeEndBy , takeEndBy_+ , takeEndBySeq+ , takeEndBySeq_ - -- ** Serial Append+ -- ** Key-value Collectors+ , toMap+ , toMapIO++ {-+ -- ** Key-value Scanners+ , classifyScan+ , classifyScanIO+ -}++ -- ** Transforming the Monad+ , morphInner++ -- * Combinators+ -- | Transformations that combine two or more folds.++ -- ** Scanning+ , scanl+ , postscanl+ -- , postscanlMaybe++ -- ** Splitting , splitWith+ , many+ , groupsOf -- ** Parallel Distribution -- | For applicative composition using distribution see@@ -219,18 +376,25 @@ -- ** Unzipping , unzip - -- ** Splitting- , many- , groupsOf- -- , intervalsOf+ -- * Dynamic Combinators+ -- | The fold to be used is generated dynamically based on the input or+ -- based on the output of the previous fold. + -- ** Key-value Collectors+ , demuxerToMap+ , demuxerToMapIO++ {-+ -- ** Key-value Scanners+ , demuxScan+ , demuxScanIO+ -}+ -- ** Nesting , concatMap - -- * Transforming the Monad- , morphInner- -- * Deprecated+ , foldlM1' , chunksOf , foldr , drainBy@@ -241,136 +405,29 @@ , variance , stdDev , serialWith+ , classify+ , classifyIO+ , demux+ , demuxIO+ , demuxToMap+ , demuxToMapIO+ , scan+ , postscan+ , scanMaybe ) where import Prelude- hiding (filter, drop, dropWhile, take, takeWhile, zipWith, foldr,- foldl, map, 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, mconcat, foldMap, unzip,+ hiding (Foldable(..), filter, drop, dropWhile, take, takeWhile, zipWith,+ map, mapM_, sequence, all, any,+ notElem, head, last, tail,+ reverse, iterate, init, and, or, lookup, (!!),+ scanl, scanl1, replicate, concatMap, mconcat, unzip, span, splitAt, break, mapM, maybe) import Streamly.Internal.Data.Fold-import Streamly.Internal.Data.Fold.Container #include "DocTestDataFold.hs"---- $overview------ A 'Fold' is a consumer of a stream of values. A fold driver (such as--- 'Streamly.Data.Stream.fold') initializes the fold @accumulator@, runs the--- fold @step@ function in a loop, processing the input stream one element at a--- time and accumulating the result. The loop continues until the fold--- terminates, at which point the accumulated result is returned.------ For example, a 'sum' Fold represents a stream consumer that adds the values--- in the input stream:------ >>> Stream.fold Fold.sum $ Stream.fromList [1..100]--- 5050------ Conceptually, a 'Fold' is a data type that mimics a strict left fold--- ('Data.List.foldl'). The above example is similar to a left fold using--- @(+)@ as the step and @0@ as the initial value of the accumulator:------ >>> Data.List.foldl' (+) 0 [1..100]--- 5050------ 'Fold's have an early termination capability e.g. the 'one' fold terminates--- after consuming one element:------ >>> Stream.fold Fold.one $ Stream.fromList [1..]--- Just 1------ The above example is similar to the following right fold:------ >>> Prelude.foldr (\x _ -> Just x) Nothing [1..]--- Just 1------ 'Fold's can be combined together using combinators. For example, to create a--- fold that sums first two elements in a stream:------ >>> sumTwo = Fold.take 2 Fold.sum--- >>> Stream.fold sumTwo $ Stream.fromList [1..100]--- 3------ Folds can be combined to run in parallel on the same input. For example, to--- compute the average of numbers in a stream without going through the stream--- twice:------ >>> avg = Fold.teeWith (/) Fold.sum (fmap fromIntegral Fold.length)--- >>> Stream.fold avg $ Stream.fromList [1.0..100.0]--- 50.5------ Folds can be combined so as to partition the input stream over multiple--- folds. For example, to count even and odd numbers in a stream:------ >>> split n = if even n then Left n else Right n--- >>> stream = fmap split $ Stream.fromList [1..100]--- >>> countEven = fmap (("Even " ++) . show) Fold.length--- >>> countOdd = fmap (("Odd " ++) . show) Fold.length--- >>> f = Fold.partition countEven countOdd--- >>> Stream.fold f stream--- ("Even 50","Odd 50")------ Terminating folds can be combined to parse the stream serially such that the--- first fold consumes the input until it terminates and the second fold--- consumes the rest of the input until it terminates:------ >>> f = Fold.splitWith (,) (Fold.take 8 Fold.toList) (Fold.takeEndBy (== '\n') Fold.toList)--- >>> Stream.fold f $ Stream.fromList "header: hello\n"--- ("header: ","hello\n")------ A 'Fold' can be applied repeatedly on a stream to transform it to a stream--- of fold results. To split a stream on newlines:------ >>> f = Fold.takeEndBy (== '\n') Fold.toList--- >>> Stream.fold Fold.toList $ Stream.foldMany f $ Stream.fromList "Hello there!\nHow are you\n"--- ["Hello there!\n","How are you\n"]------ Similarly, we can split the input of a fold too:------ >>> Stream.fold (Fold.many f Fold.toList) $ Stream.fromList "Hello there!\nHow are you\n"--- ["Hello there!\n","How are you\n"]------ = Folds vs. Streams------ We can often use streams or folds to achieve the same goal. However, streams--- are more efficient in composition of producers (e.g.--- 'Data.Stream.append' or 'Data.Stream.mergeBy') whereas folds are--- more efficient in composition of consumers (e.g. 'splitWith', 'partition'--- or 'teeWith').------ Streams are producers, transformations on streams happen on the output side:------ >>> :{--- f stream =--- Stream.filter odd stream--- & fmap (+1)--- & Stream.fold Fold.sum--- :}------ >>> f $ Stream.fromList [1..100 :: Int]--- 2550------ Folds are stream consumers with an input stream and an output value, stream--- transformations on folds happen on the input side:------ >>> :{--- f =--- Fold.filter odd--- $ Fold.lmap (+1)--- $ Fold.sum--- :}------ >>> Stream.fold f $ Stream.fromList [1..100 :: Int]--- 2550------ Notice the similiarity in the definition of @f@ in both cases, the only--- difference is the composition by @&@ vs @$@ and the use @lmap@ vs @map@, the--- difference is due to output vs input side transformations. -------------------------------------------------------------------------------- -- Deprecated
src/Streamly/Data/MutArray.hs view
@@ -12,7 +12,7 @@ -- contents of a mutable array can be modified in-place. For general -- documentation, please refer to the original module. ----- Please refer to "Streamly.Internal.Data.Array.Mut" for functions that have+-- Please refer to "Streamly.Internal.Data.MutArray" for functions that have -- not yet been released. -- -- For mutable arrays that work on boxed types, not requiring the 'Unbox'@@ -32,36 +32,55 @@ -- * Construction -- Uninitialized Arrays- , new- , newPinned+ , emptyOf+ , emptyOf' -- From containers , fromListN , fromList- , writeN -- drop new- , write -- full buffer- -- writeLastN+ , createOf+ , create+ -- createOfLast + -- * Pinning & Unpinning+ , pin+ , unpin+ , isPinned+ -- * Appending elements , snoc -- * Appending streams- , writeAppendN- , writeAppend+ , appendN+ , append2 -- * Inplace mutation , putIndex+ , unsafePutIndex+ , modifyIndex+ , unsafeModifyIndex+ , modify -- * Random access , getIndex+ , unsafeGetIndex -- * Conversion , toList + -- * Streams+ , read+ , readRev+ -- * Unfolds , reader , readerRev + -- * Stream of Arrays+ -- | Also see the "Streamly.Data.Stream.Prelude" module in the "streamly"+ -- package for chunking of a stream with timeout.+ , chunksOf+ -- * Casting , cast , asBytes@@ -69,13 +88,33 @@ -- * Size , length - -- * Unbox Type Class+ -- * Re-exports , Unbox (..)++ -- * Deprecated+ , pinnedEmptyOf+ , newPinned+ , new+ , pinnedNew+ , writeN+ , write+ , writeAppendN+ , writeAppend+ , putIndexUnsafe+ , modifyIndexUnsafe+ , getIndexUnsafe+ , append ) where import Prelude hiding (length, read)-import Streamly.Internal.Data.Array.Mut-import Streamly.Internal.Data.Unboxed (Unbox (..))+import Streamly.Internal.Data.MutArray+import Streamly.Internal.Data.Unbox (Unbox (..))+import Control.Monad.IO.Class (MonadIO) #include "DocTestDataMutArray.hs"++{-# DEPRECATED newPinned "Please use emptyOf' instead." #-}+{-# INLINE newPinned #-}+newPinned :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a)+newPinned = emptyOf'
src/Streamly/Data/MutArray/Generic.hs view
@@ -23,27 +23,54 @@ MutArray -- * Construction- , writeN+ , emptyOf+ , fromListN+ , fromList+ , createOf+ , create -- * Appending elements- , new , snoc + -- * Inplace mutation+ , putIndex+ , unsafePutIndex+ , modifyIndex+ , unsafeModifyIndex+ -- , modify++ -- * Random reads+ , getIndex+ , unsafeGetIndex+ -- * Conversion , toList + -- * Streams+ , read+ , readRev+ -- * Unfolds , reader+ -- , readerRev - -- * Random reads- , getIndex+ -- * Stream of Arrays+ , chunksOf - -- * Inplace mutation- , putIndex- , modifyIndex+ -- * Size+ , length++ -- * Deprecated+ , new+ , writeN+ , write+ , modifyIndexUnsafe+ , putIndexUnsafe+ , getIndexUnsafe ) where -import Streamly.Internal.Data.Array.Generic.Mut.Type+import Streamly.Internal.Data.MutArray.Generic+import Prelude hiding (read, length) #include "DocTestDataMutArrayGeneric.hs"
+ src/Streamly/Data/MutByteArray.hs view
@@ -0,0 +1,131 @@+-- |+-- Module : Streamly.Data.MutByteArray+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3-3-Clause+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--+-- This module implements a low level byte Array type 'MutByteArray', along+-- with type classes 'Unbox' and 'Serialize' for fast binary serialization and+-- deserialization of Haskell values. Serialization, deserialization+-- performance is similar to, and in some cases many times better than the+-- store package. Conceptually, the 'Serialize' type class works in the same+-- way as store.+--+-- == Fast serialization with schema+--+-- Serialize instances are configurable to use constructor names (see+-- 'Streamly.Internal.Data.MutByteArray.encodeConstrNames'), record field names (see+-- 'Streamly.Internal.Data.MutByteArray.encodeRecordFields') instead of binary+-- encoded values. This is an experimental feature which allows JSON like+-- properties with faster speed. For example, you can change the order of+-- constructors or record fields without affecting serialized value.+--+-- == Serialization with Array and MutArray+--+-- Higher level unboxed array modules "Streamly.Data.Array" and+-- "Streamly.Data.MutArray" are built on top of this module. Unboxed arrays are+-- essentially serialized Haskell values. Array modules provide higher level+-- serialization routines like 'Streamly.Internal.Data.Array.pinnedSerialize'+-- and 'Streamly.Internal.Data.Array.deserialize' in the+-- "Streamly.Internal.Data.Array" module.+--+-- == Mutable Byte Array+--+-- 'MutByteArray' is a primitive mutable array in the IO monad. 'Unbox' and+-- 'Serialize' type classes use this primitive array to serialize data to and+-- deserialize it from. This array is used to build higher level unboxed+-- array types 'Streamly.Data.MutArray.MutArray' and 'Streamly.Data.Array.Array'.+--+-- == Using with FFI+--+-- For using an array with "safe" FFI functions or OS interfaces the array must+-- be pinned using 'pin' and then the array pointer can be accessed using+-- 'unsafeAsPtr'.+--+-- For using with "unsafe" FFI functions, the array can remain unpinned. The+-- safe way to do that is to directly pass the underlying 'MutableByteArray#+-- RealWorld' (using 'getMutByteArray#') to the FFI function wherever a pointer+-- to the array is required, it translates to the memory address of the payload+-- of the array.+--+-- For more details, see the FFI section in the GHC user guide. Here is a+-- relevant excerpt from the GHC manual:+--+-- GHC, since version 8.4, guarantees that garbage collection will never occur+-- during an unsafe call, even in the bytecode interpreter, and further+-- guarantees that unsafe calls will be performed in the calling thread. Making+-- it safe to pass heap-allocated objects to unsafe functions.++-- == Serialization using Unbox+--+-- The 'Unbox' type class is simple and used to serialize non-recursive fixed+-- size data types. This type class is primarily used to implement unboxed+-- arrays. Unboxed arrays are just a sequence of serialized fixed length+-- Haskell data types. Instances of this type class can be derived using+-- 'Generic' or template haskell based deriving functions provided in this+-- module.+--+-- Writing a data type to an array using the array creation routines in+-- "Streamly.Data.Array" or "Streamly.Data.MutArray" (e.g. @writeN@ or+-- @fromListN@), serializes the type to the array. Similarly, reading the data+-- type from the array deserializes it. You can also serialize and deserialize+-- directly to and from a 'MutByteArray', using the type class methods.+--+-- == Serialization using Serialize+--+-- The 'Serialize' type class is a superset of the 'Unbox' type class, it can+-- serialize variable length data types as well e.g. Haskell lists. Use+-- 'deriveSerialize' to derive the instances of the type class automatically+-- and then use the type class methods to serialize and deserialize to and from+-- a 'MutByteArray'.+--+-- See 'Streamly.Internal.Data.Array.pinnedSerialize' and+-- 'Streamly.Internal.Data.Array.deserialize' for 'Array' type based+-- serialization.+--+-- == Comparing serialized values+--+-- When using the `Unbox` type class the same value may result in differing+-- serialized bytes because of unused uninitialized data in case of sum types.+-- Therefore, byte comparison of serialized values is not reliable.+--+-- However, the 'Serialize' type class guarantees that the serialized values+-- are always exactly the same and byte comparison of serialized is reliable.+--+module Streamly.Data.MutByteArray+ (++ -- * Mutable Byte Array+ -- | The standard way to read from or write to a 'MutByteArray' is by using+ -- the 'Unbox' or 'Serialize' type class methods.+ MutByteArray+ , isPinned+ , pin+ , unpin+ , new+ , pinnedNew++ -- * Unbox+ , Unbox(..)+ , deriveUnbox++ -- * Serialize+ , Serialize(..)++ -- ** Instance Config+ , SerializeConfig+ , inlineAddSizeTo+ , inlineSerializeAt+ , inlineDeserializeAt++ -- ** Instance Deriving+ , deriveSerialize+ , deriveSerializeWith+ ) where++--------------------------------------------------------------------------------+-- Imports+--------------------------------------------------------------------------------++import Streamly.Internal.Data.MutByteArray
src/Streamly/Data/Parser.hs view
@@ -7,13 +7,174 @@ -- Stability : pre-release -- Portability : GHC ----- Fast, composable stream consumers with ability to terminate, backtrack and--- fail, supporting stream fusion. Parsers are a natural extension of--- "Streamly.Data.Fold". Parsers and folds can be interconverted.+-- Parsers are more powerful but less general than 'Streamly.Data.Fold.Fold's: ----- Please refer to "Streamly.Internal.Data.Parser" for functions that have--- not yet been released.+-- * folds cannot fail but parsers can fail and backtrack.+-- * folds can be composed as a Tee but parsers cannot.+-- * folds can be converted to parsers. --+-- Streamly parsers support all operations offered by popular Haskell parser+-- libraries. Unlike other parser libraries, (1) streamly parsers can operate+-- on any Haskell type as input - not just bytes, (2) natively support+-- streaming, (3) and are faster.+--+-- == High Performance by Static Parser Fusion+--+-- Like folds, parsers are designed to utilize stream fusion, compiling to+-- efficient low-level code comparable to the speed of C. Parsers are suitable+-- for high-performance parsing of streams.+--+-- Operations in this module are designed to be composed statically rather than+-- dynamically. They are inlined to enable static fusion. More importantly,+-- they are not designed to be used recursively. Recursive use will break+-- fusion and lead to quadratic performance slowdown. For dynamic and+-- recursive compositions use the continuation passing style (CPS) operations+-- from the "Streamly.Data.ParserK" module. 'Parser' and+-- 'Streamly.Data.ParserK.ParserK' types are interconvertible.+--+-- == How to parse a stream?+--+-- Parser combinators can be used to create a pipeline of parsers such+-- that the next parser consumes the result of the previous parser.+-- Such a composed pipeline of parsers can then be driven by one of many parser+-- drivers available in the Stream and Array modules.+--+-- Use Streamly.Data.Stream.'Streamly.Data.Stream.parse' or+-- Streamly.Data.Stream.'Streamly.Data.Stream.parseBreak' to run a parser on an+-- input stream and return the parsed result.+--+-- Use Streamly.Data.Stream.'Streamly.Data.Stream.parseMany' or+-- Streamly.Data.Stream.'Streamly.Data.Stream.parseIterate' to transform an+-- input data stream to an output stream of parsed data elements using a+-- parser.+--+-- == Parser vs ParserK+--+-- There are two functionally equivalent parsing modules,+-- "Streamly.Data.Parser" (this module) and "Streamly.Data.ParserK". The latter+-- is a CPS based wrapper over the former, and can be used for parsing in+-- general. "Streamly.Data.Parser" enables stream fusion and where possible it should be+-- preferred over "Streamly.Data.ParserK" for high performance stream parsing+-- use cases. However, there are a few cases where this module is not+-- suitable and ParserK should be used instead. As a thumb rule, when recursion+-- or heavy nesting is needed use ParserK.+--+-- === Parser: suitable for non-recursive static fusion+--+-- The 'Parser' type is suitable only for non-recursive static fusion. It could+-- be problematic for recursive definitions. To enable static fusion, parser+-- combinators use strict pattern matching on arguments of type Parser. This+-- leads to infinte loop when a parser is defined recursively, due to strict+-- evaluation of the recursive call. For example, the following implementation+-- loops infinitely because of the recursive use of parser @p@ in the @*>@+-- combinator:+--+-- >>> import Streamly.Data.Parser (Parser)+-- >>> import qualified Streamly.Data.Fold as Fold+-- >>> import qualified Streamly.Data.Parser as Parser+-- >>> import qualified Streamly.Data.Stream as Stream+-- >>> import Control.Applicative ((<|>))+--+-- >>> :{+-- >>> p, p1, p2 :: Monad m => Parser Char m String+-- >>> p1 = Parser.satisfy (== '(') *> p+-- >>> p2 = Parser.fromFold Fold.toList+-- >>> p = p1 <|> p2+-- >>> :}+--+-- Another limitation of Parser type quadratic performance slowdown when too+-- many nested compositions are used. Especially Applicative, Monad,+-- Alternative instances, and sequenced parsing operations (e.g. nested 'one',+-- and 'splitWith') exhibit quadratic slowdown (O(n^2) complexity) when+-- combined @n@ times, roughly 8 or less sequenced parsers usually work fine.+-- READ THE DOCS OF APPLICATIVE, MONAD AND ALTERNATIVE INSTANCES.+--+-- === ParserK: suitable for recursive definitions+--+-- ParserK is suitable for recursive definitions:+--+-- >>> import Streamly.Data.ParserK (ParserK)+-- >>> import Streamly.Data.StreamK (toParserK)+-- >>> import qualified Streamly.Data.StreamK as StreamK+--+-- >>> :{+-- >>> p, p1, p2 :: Monad m => ParserK Char m String+-- >>> p1 = toParserK (Parser.satisfy (== '(')) *> p+-- >>> p2 = toParserK (Parser.fromFold Fold.toList)+-- >>> p = p1 <|> p2+-- >>> :}+--+-- >>> StreamK.parse p $ StreamK.fromStream $ Stream.fromList "hello"+-- Right "hello"+--+-- For this reason Applicative, Alternative or Monad compositions with+-- recursion cannot be used with the 'Parser' type. Alternative type class based+-- operations like 'asum' and Alternative based generic parser combinators use+-- recursion. Similarly, Applicative type class based operations like+-- 'Prelude.sequence' use recursion. Custom implementations of many such+-- operations are provided in this module (e.g. 'some', 'many'), and those+-- should be used instead.+--+-- == Parsers Galore!+--+-- Streamly provides all the parsing functionality provided by popular parsing+-- libraries, and much more with higher performance.+-- This module provides most of the elementary parsers and parser combinators.+-- Additionally,+--+-- * all the folds from the "Streamly.Data.Fold" module can be converted to+-- parsers using 'fromFold'.+-- * "Streamly.Unicode.Parser" module provides Char stream parsers.+-- * all the combinators from the+-- <https://hackage.haskell.org/package/parser-combinators parser-combinators>+-- package can be used with streamly ParserK.+-- * See "Streamly.Internal.Data.Parser" for many more unreleased but useful APIs.+--+-- == Generic Parser Combinators+--+-- With 'Streamly.Data.ParserK.ParserK' you can use the 'Applicative' and+-- 'Control.Applicative.Alternative' type class based generic parser+-- combinators from the+-- <https://hackage.haskell.org/package/parser-combinators parser-combinators>+-- library or similar. However, if available, we recommend that you use the+-- equivalent functionality from this module where performance and streaming+-- behavior matters.+-- Firstly, the combinators in this module are faster due to stream fusion.+-- Secondly, these are streaming in nature as the results can be passed+-- directly to other stream consumers (folds or parsers). The Alternative type+-- class based parsers would end up buffering all the results in lists before+-- they can be consumed.+--+-- == Error Reporting+--+-- There are two types of parser drivers available, @parse@ and @parseBreak@+-- drivers do not track stream position, whereas @parsePos@ and @parseBreakPos@+-- drivers track and report stream position information with slightly more+-- performance overhead.+--+-- When an error occurs the stream position is reported, in case of byte streams+-- or unboxed array streams this is the byte position, in case of generic+-- element parsers or generic array parsers this is the element position in the+-- stream.+--+-- These parsers do not report a case specific error context (e.g. line number+-- or column). If you need line number or column information you can read the+-- stream again (if it is immutable) and this count the lines to translate the+-- reported byte position to line number and column. More elaborate support for+-- building arbitrary and custom error context information is planned to be+-- added in future.+--+-- == Monad Transformer Stack+--+-- 'MonadTrans' instance is not provided. If the 'Parser' type is the top most+-- layer (which should be the case almost always) you can just use 'fromEffect'+-- to execute the lower layer monad effects.+--+-- == Experimental APIs+--+-- Please refer to "Streamly.Internal.Data.Parser" for functions that have not+-- yet been released.+-- module Streamly.Data.Parser ( -- * Setup@@ -22,16 +183,15 @@ -- -- $setup - -- * Overview- -- $overview- -- * Parser Type Parser+ , ParseError(..)+ , ParseErrorPos(..) -- -- * Downgrade to Fold -- , toFold - -- * Parsers+ -- * Elementary Parsers -- ** From Folds , fromFold @@ -44,7 +204,7 @@ , peek , eof - -- ** Element parsers+ -- ** Single Elements -- All of these can be expressed in terms of either , one@@ -61,15 +221,15 @@ , listEqBy , listEq - -- * Combinators+ -- * Transformations -- Mapping on output -- , rmapM - -- ** Mapping on input+ -- ** Map on input , lmap , lmapM - -- * Map on output+ -- ** Map on output , rmapM -- ** Filtering@@ -78,6 +238,7 @@ -- ** Look Ahead , lookAhead + -- * Tokenizing Combinators -- ** Tokenize by length -- , takeBetween , takeEQ@@ -96,8 +257,8 @@ -- ** Grouping , groupBy- -- , groupByRolling- -- , groupByRollingEither+ , groupByRolling+ , groupByRollingEither -- ** Framing -- , wordFramedBy@@ -108,12 +269,12 @@ -- -- * Alternative -- , alt - -- ** Splitting+ -- * Splitting , many , some , manyTill - -- ** De-interleaving+ -- * De-interleaving , deintercalate ) @@ -123,55 +284,3 @@ import Prelude hiding (dropWhile, takeWhile, filter) #include "DocTestDataParser.hs"---- $overview------ Several combinators in this module can be many times faster than CPS based--- parsers because of stream fusion. For example,--- 'Streamly.Internal.Data.Parser.many' combinator in this module is much--- faster than the 'Control.Applicative.many' combinator of--- 'Control.Applicative.Alternative' type class used by CPS based parsers.------ The use of 'Alternative' type class, in parsers has another drawback.--- Alternative based parsers use plain Haskell lists to collect the results. In--- a strict Monad like IO, the results are necessarily buffered before they can--- be consumed. This may not perform optimally in streaming applications--- processing large amounts of data. Equivalent combinators in this module can--- consume the results of parsing using a 'Fold' or another parser, thus--- providing a scalable and composable consumer.------ Note that these parsers do not report the error context (e.g. line number or--- column). This may be supported in future.------ mtl instances are not provided. If the 'Parser' type is the top most layer--- (which should be the case almost always) you can just use 'fromEffect' to--- execute the lower layer monad effects.------ == Performance Notes------ The 'Parser' type represents a stream consumer by composing state as data--- which enables stream fusion. Stream fusion generates a tight loop without--- any constructor allocations between the stages, providing C like performance--- for the loop. Stream fusion works when multiple functions are combined in a--- pipeline statically. Therefore, the operations in this module must be--- inlined and must not be used recursively to allow for stream fusion. Note--- that operations like 'sequence', and 'asum' that compose pasrers using--- recursion should be avoided with these parsers. You can use these with the--- 'ParserK' module instead.------ Using the 'Parser' type, parsing operations like 'one', 'splitWith' etc.--- degrade quadratically (O(n^2)) when combined many times. If you need to--- combine these operations, say more than 8 times in a single loop, then you--- should consider using the continuation style parser type 'ParserK' instead.--- Also, if you need to use these operations in a recursive loop you should use--- 'ParserK' instead.------ The 'ParserK' type represents a stream consumer by composing function calls,--- therefore, a function call overhead is incurred at each composition. It is--- quite fast in general but may be a few times slower than a fused parser.--- However, it allows for scalable dynamic composition especially parsers can--- be used in recursive calls. Using the 'ParserK' type operations like--- 'splitWith' provide linear (O(n)) performance with respect to the number of--- compositions..------ 'Parser' and 'ParserK' types can be interconverted.
src/Streamly/Data/ParserK.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} -- | -- Module : Streamly.Data.ParserK -- Copyright : (c) 2023 Composewell Technologies@@ -6,39 +7,150 @@ -- Stability : pre-release -- Portability : GHC ----- Parsers using Continuation Passing Style (CPS). See notes in--- "Streamly.Data.Parser" module to know when to use this module.+-- See the general notes about parsing in the "Streamly.Data.Parser" module.+-- This (ParserK) module implements a Continuation Passing Style (CPS) wrapper+-- over the fused "Streamly.Data.Parser" module. It is a faster CPS parser than+-- attoparsec. ----- To run a 'ParserK' use 'Streamly.Data.StreamK.parseChunks'.+-- The 'ParserK' type represents a stream-consumer as a composition of function+-- calls, therefore, a function call overhead is incurred at each composition.+-- It is reasonably fast in general but may be a few times slower than the+-- fused 'Streamly.Data.Parser.Parser' type. However, unlike fused parsers, it+-- allows for scalable dynamic composition, especially, 'ParserK' can be used+-- in recursive calls. Operations like 'splitWith' on 'ParserK' type have+-- linear (O(n)) performance with respect to the number of compositions. --+-- 'ParserK' is preferred over the fused 'Streamly.Data.Parser.Parser' when+-- extensive applicative, alternative and monadic composition is required, or+-- when recursive or dynamic composition of parsers is required. 'ParserK' also+-- allows efficient parsing of a stream of byte arrays, it can also break the+-- input stream into a parse result and the remaining stream so that the stream+-- can be parsed independently in segments.+--+-- == How to parse a stream?+--+-- All the fused parsers from the "Streamly.Data.Parser" module can be+-- converted to the CPS ParserK, for use with different types of parser+-- drivers, using+-- the @toParserK@ combinators -+-- Streamly.Data.Array.'Streamly.Data.Array.toParserK',+-- Streamly.Data.StreamK.'Streamly.Data.StreamK.toParserK', and+-- Streamly.Data.Array.Generic.'Streamly.Data.Array.Generic.toParserK'+--+-- To parse a stream of unboxed arrays, use+-- Streamly.Data.Array.'Streamly.Data.Array.parse' for running the parser, this+-- is the preferred and most efficient way to parse chunked input. The+-- Streamly.Data.Array.'Streamly.Data.Array.parseBreak' function returns the+-- remaining stream as well along with the parse result.+--+-- To parse a stream of boxed arrays, use+-- Streamly.Data.Array.Generic.'Streamly.Data.Array.Generic.parse' or+-- Streamly.Data.Array.Generic.'Streamly.Data.Array.Generic.parseBreak' to run+-- the parser.+--+-- To parse a stream of individual elements, use+-- Streamly.Data.StreamK.'Streamly.Data.StreamK.parse' and+-- Streamly.Data.StreamK.'Streamly.Data.StreamK.parseBreak' to run the parser.+--+-- == Applicative Composition+--+-- Applicative parsers are simpler but we cannot use lookbehind as we can in+-- the monadic parsers.+--+-- If we have to parse "9a" or "a9" but not "99" or "aa" we can use the+-- following Applicative, backtracking parser:+--+-- >>> -- parse p1 : p2 : []+-- >>> token p1 p2 = ((:) <$> p1 <*> ((:) <$> p2 <*> pure []))+-- >>> :{+-- backtracking :: Monad m => ParserK Char m String+-- backtracking = StreamK.toParserK $+-- token (Parser.satisfy isDigit) (Parser.satisfy isAlpha) -- e.g. "9a"+-- <|>+-- token (Parser.satisfy isAlpha) (Parser.satisfy isDigit) -- e.g. "a9"+-- :}+--+-- == Monadic Composition+--+-- Monad composition can be used to implement lookbehind parsers, we can dynamically+-- compose new parsers based on the results of the previously parsed values.+--+-- In the previous example, we know that if the first parse resulted in a digit+-- at the first place then the second parse is going to fail. However, we+-- waste that information and parse the first character again in the second+-- parse only to know that it is not an alphabetic char. By using lookbehind+-- in a 'Monad' composition we can make dynamic decisions based on previously+-- parsed information and avoid redundant work:+--+-- >>> data DigitOrAlpha = Digit Char | Alpha Char+--+-- >>> :{+-- lookbehind :: Monad m => ParserK Char m String+-- lookbehind = do+-- x1 <- StreamK.toParserK $+-- Digit <$> Parser.satisfy isDigit+-- <|> Alpha <$> Parser.satisfy isAlpha+-- -- Note: the parse depends on what we parsed already+-- x2 <- StreamK.toParserK $+-- case x1 of+-- Digit _ -> Parser.satisfy isAlpha+-- Alpha _ -> Parser.satisfy isDigit+-- return $ case x1 of+-- Digit x -> [x,x2]+-- Alpha x -> [x,x2]+-- :}+--+-- == Experimental APIs+--+-- Please refer to "Streamly.Internal.Data.ParserK" for functions that have+-- not yet been released.+-- module Streamly.Data.ParserK (+ -- * Setup+ -- | To execute the code examples provided in this module in ghci, please+ -- run the following commands first.+ --+ -- $setup+ -- * Parser Type ParserK -- * Parsers- -- ** Conversions- , fromFold- , fromParser- -- , toParser - -- ** Without Input+ -- -- ** Without Input , fromPure , fromEffect , die++ -- * Deprecated+ , fromFold+ , fromParser+ , adapt+ , adaptC+ , adaptCG ) where import Control.Monad.IO.Class (MonadIO) import Streamly.Internal.Data.Fold (Fold)-import Streamly.Internal.Data.Unboxed (Unbox)-import qualified Streamly.Internal.Data.Parser.ParserD as ParserD+import Streamly.Internal.Data.Unbox (Unbox)+import Streamly.Internal.Data.Array (Array)+import qualified Streamly.Internal.Data.Parser as ParserD+import qualified Streamly.Internal.Data.Array as Array -import Streamly.Internal.Data.Parser.ParserK.Type+import Streamly.Internal.Data.ParserK --- | Convert a 'Fold' to a 'ParserK'.---+#include "DocTestDataParserK.hs"++{-# DEPRECATED fromFold "Please use \"Array.toParserK . Parser.fromFold\" instead." #-} {-# INLINE fromFold #-}-fromFold :: (MonadIO m, Unbox a) => Fold m a b -> ParserK a m b-fromFold = fromParser . ParserD.fromFold+fromFold :: (MonadIO m, Unbox a) => Fold m a b -> ParserK (Array a) m b+fromFold = Array.toParserK . ParserD.fromFold++{-# DEPRECATED fromParser "Please use \"Array.toParserK\" instead." #-}+{-# INLINE fromParser #-}+fromParser ::+ (MonadIO m, Unbox a) => ParserD.Parser a m b -> ParserK (Array a) m b+fromParser = Array.toParserK
+ src/Streamly/Data/RingArray.hs view
@@ -0,0 +1,92 @@+{-# LANGUAGE CPP #-}+-- |+-- Module : Streamly.Data.RingArray+-- Copyright : (c) 2025 Composewell Technologies+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : released+-- Portability : GHC+--+-- This module provides APIs to create and use unboxed, mutable ring arrays of+-- fixed size. Ring arrays are useful to keep a circular buffer or a sliding+-- window of elements.+--+-- RingArrays are of fixed size but there is a way to expand the size of the+-- ring, you can copy the ring to a MutArray, expand the MutArray and the cast+-- it back to RingArray.+--+-- This module is designed to be imported qualified:+--+-- >>> import qualified Streamly.Data.RingArray as Ring+--+-- Please refer to "Streamly.Internal.Data.RingArray" for functions that have+-- not yet been released.+--++module Streamly.Data.RingArray+ ( RingArray++ -- * Construction+ , createOfLast+ , castMutArray -- XXX this is unsafeFreeze in Array module+ , castMutArrayWith+ -- , unsafeCastMutArray+ -- , unsafeCastMutArrayWith++ -- * Moving the Head+ , moveForward+ , moveReverse+ -- , moveBy++ -- * In-place Mutation+ , insert+ , replace+ , replace_+ , putIndex+ , modifyIndex++ -- * Random Access+ , getIndex+ , unsafeGetIndex+ , unsafeGetHead++ -- * Conversion+ , toList+ , toMutArray++ -- * Streams+ , read+ , readRev++ -- * Unfolds+ , reader+ , readerRev++ -- * Size+ , length+ , byteLength++ -- * Casting+ , cast+ -- , unsafeCast+ , asBytes+ , asMutArray+ -- , asMutArray_++ -- * Folds+ -- , foldlM'+ , fold++ -- * Stream of Rings+ , ringsOf+ , scanRingsOf++ -- * Fast Byte Comparisons+ , eqArray+ , eqArrayN++ ) where++import Streamly.Internal.Data.RingArray+import Prelude hiding (read, length)
+ src/Streamly/Data/Scanl.hs view
@@ -0,0 +1,189 @@+{-# LANGUAGE CPP #-}+-- |+-- Module : Streamly.Data.Scanl+-- Copyright : (c) 2019 Composewell Technologies+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : released+-- Portability : GHC+--++module Streamly.Data.Scanl+ (+ -- * Setup+ -- | To execute the code examples provided in this module in ghci, please+ -- run the following commands first.+ --+ -- $setup++ -- * Scanl Type++ Scanl -- (..)++ -- * Constructors+ , mkScanl+ , mkScanlM+ , mkScanl1+ , mkScanl1M+ , mkScanr++ -- * Scans+ -- ** Accumulators+ -- | Scans that never terminate, these scans are much like strict left+ -- folds. 'mconcat' is the fundamental accumulator. All other accumulators+ -- can be expressed in terms of 'mconcat' using a suitable Monoid. Instead+ -- of writing scans we could write Monoids and turn them into scans.++ -- Monoids+ , sconcat+ , mconcat+ , foldMap+ , foldMapM++ -- Reducers+ , drain+ -- , drainMapM+ , length+ , countDistinct+ , countDistinctInt+ -- , frequency+ , sum+ , product+ , mean+ , rollingHash+ , rollingHashWithSalt++ -- Collectors+ , toList+ , toListRev+ , toSet+ , toIntSet+ , topBy++ -- ** Non-Empty Accumulators+ -- | Accumulators that do not have a default value, therefore, return+ -- 'Nothing' on an empty stream.+ , latest+ , maximumBy+ , maximum+ , minimumBy+ , minimum++ -- ** Filtering Scanners+ -- | Accumulators that are usually run as a scan using the 'potscanlMaybe'+ -- combinator.+ , findIndices+ , elemIndices+ , deleteBy+ -- , uniq+ , uniqBy+ , nub+ , nubInt++ -- ** Terminating Scans+ -- , satisfy+ -- , maybe++ , the++ -- * Transformations+ -- | Transformations are modifiers of scans. In the type @Scan m a b@, @a@+ -- is the input type and @b@ is the output type. Transformations can be+ -- applied either on the input side (contravariant) or on the output side+ -- (covariant). Therefore, transformations have one of the following+ -- general shapes:+ --+ -- * @... -> Scanl m a b -> Scanl m c b@ (input transformation)+ -- * @... -> Scanl m a b -> Scanl m a c@ (output transformation)+ --+ -- The input side transformations are more interesting for scans. Most of+ -- the following sections describe the input transformation operations on a+ -- scan. When an operation makes sense on both input and output side we use+ -- the prefix @l@ (for left) for input side operations and the prefix @r@+ -- (for right) for output side operations.++ -- ** Mapping on output+ -- | The 'Functor' instance of a scan maps on the output of the scan:+ --+ -- >>> Stream.toList $ Stream.scanl (fmap show Scanl.sum) (Stream.enumerateFromTo 1 10)+ -- ["0","1","3","6","10","15","21","28","36","45","55"]+ --+ , rmapM++ -- ** Mapping on Input+ , lmap+ , lmapM++ -- ** Filtering+ , filter+ , filterM++ -- -- ** Mapping Filters+ , mapMaybe+ , catMaybes+ , catLefts+ , catRights+ , catEithers++ -- ** Trimming+ , take+ , takeEndBy+ , takeEndBy_++ -- ** Key-value Scanners+ , classify+ , classifyIO++ -- ** Transforming the Monad+ , morphInner++ -- * Combinators+ -- | Transformations that combine two or more scans.++ -- ** Scanning+ , scanl+ , postscanl+ , postscanlMaybe++ -- ** Parallel Distribution+ -- | The 'Applicative' instance distributes the input to both scans.++ , teeWith+ --, teeWithFst+ --, teeWithMin+ , tee+ , distribute++ -- ** Partitioning+ -- | Direct items in the input stream to different scans using a binary+ -- scan selector.++ , partition+ --, partitionByM+ --, partitionByFstM+ --, partitionByMinM+ --, partitionBy++ -- ** Unzipping+ , unzip++ -- * Dynamic Combinators+ -- | The scan to be used is generated dynamically based on the input.++ -- ** Key-value Scanners+ , demux+ , demuxIO+ )+where++import Prelude+ hiding (Foldable(..), filter, drop, dropWhile, take, takeWhile, zipWith,+ map, mapM_, sequence, all, any,+ notElem, head, last, tail,+ reverse, iterate, init, and, or, lookup, (!!),+ scanl, scanl1, replicate, concatMap, mconcat, unzip,+ span, splitAt, break, mapM, maybe)++import Streamly.Internal.Data.Scanl++#include "DocTestDataScanl.hs"
src/Streamly/Data/Stream.hs view
@@ -8,18 +8,130 @@ -- Stability : released -- Portability : GHC ----- Fast, composable stream producers with ability to terminate, supporting--- stream fusion.+-- The 'Stream' type represents a producer of a sequence of values. Its dual,+-- 'Streamly.Data.Fold.Fold', represents a consumer. While both types support+-- similar transformations, the key difference is that only 'Stream' can+-- compose multiple producers, and only 'Fold' can compose multiple consumers. ----- Please refer to "Streamly.Internal.Data.Stream" for more functions that have--- not yet been released.+-- == Console Echo Example ----- For continuation passing style (CPS) stream type, please refer to--- the "Streamly.Data.StreamK" module.+-- To get you started, here is an example of a program which reads lines from+-- console and writes them back to the console. ----- Checkout the <https://github.com/composewell/streamly-examples>--- repository for many more real world examples of stream programming.+-- >>> import Data.Function ((&))+-- >>> :{+-- echo =+-- Stream.repeatM getLine -- Stream IO String+-- & Stream.mapM putStrLn -- Stream IO ()+-- & Stream.fold Fold.drain -- IO ()+-- :}+--+-- This is a simple example of a declarative representation of an imperative+-- loop using streaming combinators.+-- In this example, 'repeatM' generates an infinite stream of 'String's by+-- repeatedly performing the 'getLine' IO action. 'mapM' then applies+-- 'putStrLn' on each element in the stream converting it to stream of '()'.+-- Finally, 'Streamly.Data.Fold.drain' 'fold's the stream to IO discarding the+-- () values, thus producing only effects.+--+-- This gives you an idea about how we can program declaratively by+-- representing loops using streams. Compare this declarative loopless approach+-- with an imperative approach using a @while@ loop for writing the same+-- program. In this module, you can find all "Data.List"-like functions and+-- many more powerful combinators to perform common programming tasks.+--+-- == Static Stream Fusion+--+-- The 'Stream' type represents streams as state machines. When composed+-- statically, these state machines fuse together at compile time, eliminating+-- intermediate data structures and function calls. This results in the+-- generation of tight, efficient loops comparable to those written in+-- low-level languages like C. For instance, in the earlier example, operations+-- like 'repeatM' and 'mapM' are written as separate fragments but fuse into a+-- single, optimized loop.+--+-- The primary goal of the 'Stream' type is to build highly efficient streams+-- via compile-time fusion of modular loop fragments. However, this technique+-- comes with trade-offs and should be used with care. Stream /construction/+-- operations such as 'cons', 'append', 'interleave', 'mergeBy', and 'zipWith'+-- work extremely well at a small scale. But at a large scale, their+-- performance degrades due to O(n^2) complexity, where @n@ is the number of+-- compositions.+--+-- Therefore, it's best to generate a fused stream in one go, if possible.+-- While using a small number of composition operations is absolutely fine,+-- avoid using large number of composition operations. For example, do not try+-- to construct a fused 'Stream' by using `cons` rescursively. However, you can+-- use 'Streamly.Data.StreamK.cons' and any other construction operations on+-- the CPS 'StreamK' type without any problem. The CPS construction operations+-- have linear (O(n)) performance characteristics and scale much better, though+-- they are not as efficient as fused streams due to function call overhead at+-- each step.+--+-- When used correctly, the fused 'Stream' type can be 10x to 100x faster+-- than CPS-based streams, depending on the use case.+--+-- __Rule of Thumb:__ Use the fused 'Stream' type when the number of+-- compositions is small and they are static or compile-time. Use the CPS-based+-- 'StreamK' type when the number of compositions is large or potentially+-- infinite, and they are dynamic or composed at runtime. Both types are fully+-- interconvertible, allowing you to choose the best tool for each part of your+-- pipeline.+--+-- == Better and Effectful Lists+--+-- This module offers operations analogous to standard Haskell lists from the+-- @base@ package. Streams can be viewed as a generalization of lists —+-- providing all the functionality of standard lists, plus additional+-- capabilities such as effectful operations and improved performance through+-- stream fusion. They can easily replace lists in most contexts, and go+-- beyond where lists fall short.+--+-- For instance, a common limitation of lists is the inability to perform IO+-- actions (e.g., printing) at arbitrary points during processing. Streams+-- naturally support such effectful operations.+--+-- As discussed in the fusion section above, while the 'Stream' type is not+-- consable and appendable at scale, the 'StreamK' type is consable and+-- appendable at scale.+--+-- == Non-determinism and List Transformers+--+-- Streamly does not provide a 'ListT' like Monad instance but it provides all+-- the equivalent functionality and more. We do not provide a Monad instance+-- for streams, as there are many possible ways to define the bind operation.+-- Instead, we offer bind-style operations such as 'concatFor', 'concatForM',+-- and their variants (e.g. fair interleaving and breadth-first nesting). These+-- can be used for convenient ListT-style stream composition. Additionally, we+-- provide applicative-style cross product operations like 'cross' and its+-- variants which are many times faster than the monad style operations.+--+-- == Logic Programming+--+-- Streamly does not provide a 'LogicT'-style Monad instance, but it offers all+-- the equivalent functionality—and more. Operations like 'fairCross' and+-- 'fairConcatFor' nest outer and inner streams fairly, ensuring that no stream+-- is starved when exploring cross products.+--+-- This enables balanced exploration across all dimensions in backtracking+-- problems, while also supporting infinite streams. It effectively replaces the+-- core functionality of 'LogicT' from the @logict@ package, with significantly+-- better performance. In particular, it avoids the quadratic slowdown seen with+-- @observeMany@, and the applicative 'fairCross' runs many times faster,+-- achieving loop nesting performance comparable to C. +-- == Additional Resources+--+-- The combinators in this module support /serial/ composition of streams.+-- For /concurrent/ composition of streams, refer to+-- "Streamly.Data.Stream.Prelude" in the @streamly@ package.+--+-- For more, yet unreleased functions, try: "Streamly.Internal.Data.Stream".+--+-- For real-world examples, visit:+-- <https://github.com/composewell/streamly-examples>.+--+ module Streamly.Data.Stream ( -- * Setup@@ -37,15 +149,19 @@ -- * Construction -- | Functions ending in the general shape @b -> Stream m a@. --- -- See also: "Streamly.Internal.Data.Stream.Generate" for- -- @Pre-release@ functions.+ -- Useful Idioms:+ --+ -- >>> fromIndices f = fmap f $ Stream.enumerateFrom 0+ -- >>> fromIndicesM f = Stream.mapM f $ Stream.enumerateFrom 0+ -- >>> fromListM = Stream.sequence . Stream.fromList+ -- >>> fromFoldable = StreamK.toStream . StreamK.fromFoldable+ -- >>> fromFoldableM = Stream.sequence . fromFoldable -- ** Primitives- -- | Primitives to construct a stream from pure values or monadic actions.- -- All other stream construction and generation combinators described later- -- can be expressed in terms of these primitives. However, the special- -- versions provided in this module can be much more efficient in most- -- cases. Users can create custom combinators using these primitives.+ -- | These primitives are meant to statically fuse a small number of stream+ -- elements. The 'Stream' type is never constructed at large scale using+ -- these primitives. Use 'StreamK' if you need to construct a stream from+ -- primitives. , nil , nilM , cons@@ -57,38 +173,38 @@ , unfoldr , unfoldrM - -- ** From Values- -- | Generate a monadic stream from a seed value or values.+ -- ** Singleton , fromPure , fromEffect++ -- ** Iteration+ -- | Generate a monadic stream from a seed value or values.+ --+ , iterate+ , iterateM , repeat , repeatM , replicate , replicateM - -- Note: Using enumeration functions e.g. 'Prelude.enumFromThen' turns out- -- to be slightly faster than the idioms like @[from, then..]@.- -- -- ** Enumeration- -- | We can use the 'Enum' type class to enumerate a type producing a list- -- and then convert it to a stream:+ -- | 'Enumerable' type class is to streams as 'Enum' is to lists. Enum+ -- provides functions to generate a list, Enumerable provides similar+ -- functions to generate a stream instead. --- -- @- -- 'fromList' $ 'Prelude.enumFromThen' from then- -- @+ -- It is much more efficient to use 'Enumerable' directly than enumerating+ -- to a list and converting it to stream. The following works but is not+ -- particularly efficient: --- -- However, this is not particularly efficient.- -- The 'Enumerable' type class provides corresponding functions that- -- generate a stream instead of a list, efficiently.+ -- >>> f from next = Stream.fromList $ Prelude.enumFromThen from next+ --+ -- Note: For lists, using enumeration functions e.g. 'Prelude.enumFromThen'+ -- turns out to be slightly faster than the idioms like @[from, then..]@. , Enumerable (..) , enumerate , enumerateTo - -- ** Iteration- , iterate- , iterateM- -- ** From Containers -- | Convert an input structure, container or source into a stream. All of -- these can be expressed in terms of primitives.@@ -103,8 +219,6 @@ -- | Functions ending in the general shape @Stream m a -> m b@ or @Stream m -- a -> m (b, Stream m a)@ --- -- See also: "Streamly.Internal.Data.Stream.Eliminate" for @Pre-release@- -- functions. -- EXPLANATION: In imperative terms a fold can be considered as a loop over the stream -- that reduces the stream to a single value.@@ -192,7 +306,9 @@ -- ** Parsing , parse- -- , parseBreak+ , parseBreak+ , parsePos+ , parseBreakPos -- ** Lazy Right Folds -- | Consuming a stream to build a right associated expression, suitable@@ -203,12 +319,57 @@ -- operations like mapping a function over the stream. , foldrM , foldr+ -- foldr1 -- ** Specific Folds- -- | Usually you can use the folds in "Streamly.Data.Fold". However, some- -- folds that may be commonly used or may have an edge in performance in- -- some cases are provided here.- -- , drain+ -- | Streams are folded using folds in "Streamly.Data.Fold". Here are some+ -- idioms and equivalents of Data.List APIs using folds:+ --+ -- >>> foldlM' f a = Stream.fold (Fold.foldlM' f a)+ -- >>> foldl1' f = Stream.fold (Fold.foldl1' f)+ -- >>> foldl' f a = Stream.fold (Fold.foldl' f a)+ -- >>> drain = Stream.fold Fold.drain+ -- >>> mapM_ f = Stream.fold (Fold.drainMapM f)+ -- >>> length = Stream.fold Fold.length+ -- >>> genericLength = Stream.fold Fold.genericLength+ -- >>> head = Stream.fold Fold.one+ -- >>> last = Stream.fold Fold.latest+ -- >>> null = Stream.fold Fold.null+ -- >>> and = Stream.fold Fold.and+ -- >>> or = Stream.fold Fold.or+ -- >>> any p = Stream.fold (Fold.any p)+ -- >>> all p = Stream.fold (Fold.all p)+ -- >>> sum = Stream.fold Fold.sum+ -- >>> product = Stream.fold Fold.product+ -- >>> maximum = Stream.fold Fold.maximum+ -- >>> maximumBy cmp = Stream.fold (Fold.maximumBy cmp)+ -- >>> minimum = Stream.fold Fold.minimum+ -- >>> minimumBy cmp = Stream.fold (Fold.minimumBy cmp)+ -- >>> elem x = Stream.fold (Fold.elem x)+ -- >>> notElem x = Stream.fold (Fold.notElem x)+ -- >>> lookup x = Stream.fold (Fold.lookup x)+ -- >>> find p = Stream.fold (Fold.find p)+ -- >>> (!?) i = Stream.fold (Fold.index i)+ -- >>> genericIndex i = Stream.fold (Fold.genericIndex i)+ -- >>> elemIndex x = Stream.fold (Fold.elemIndex x)+ -- >>> findIndex p = Stream.fold (Fold.findIndex p)+ --+ -- Some equivalents of Data.List APIs from the Stream module:+ --+ -- >>> head = fmap (fmap fst) . Stream.uncons+ -- >>> tail = fmap (fmap snd) . Stream.uncons+ -- >>> tail = Stream.tail -- unreleased API+ -- >>> init = Stream.init -- unreleased API+ --+ -- A Stream based toList fold implementation is provided below because it+ -- has a better performance compared to the fold.++ -- Functions in Data.List, missing here:+ -- unsnoc = Stream.parseBreak (Parser.init Fold.toList)+ -- genericTake+ -- genericDrop+ -- genericSplitAt+ -- genericReplicate , toList -- * Mapping@@ -236,8 +397,6 @@ -- * Scanning -- | Stateful one-to-one transformations. --- -- See also: "Streamly.Internal.Data.Stream.Transform" for- -- @Pre-release@ functions. {- -- ** Left scans@@ -252,10 +411,27 @@ , scanl1M' -} - -- ** Scanning By 'Fold'- , scan- , postscan+ -- ** Scanning By 'Scanl'+ -- | Useful idioms:+ --+ -- >>> scanl' f z = Stream.scanl (Scanl.mkScanl f z)+ -- >>> scanlM' f z = Stream.scanl (Scanl.mkScanlM f z)+ -- >>> postscanl' f z = Stream.postscanl (Scanl.mkScanl f z)+ -- >>> postscanlM' f z = Stream.postscanl (Scanl.mkScanlM f z)+ -- >>> scanl1' f = Stream.catMaybes . Stream.scanl (Scanl.mkScanl1 f)+ -- >>> scanl1M' f = Stream.catMaybes . Stream.scanl (Scanl.mkScanl1M f)+ , scanl+ , postscanl -- XXX postscan1 can be implemented using Monoids or Refolds.+ -- The following scans from Data.List are not provided.+ -- XXX scanl+ -- XXX scanl1+ -- XXX scanr+ -- XXX scanr1+ -- XXX mapAccumL+ -- XXX mapAccumR+ -- XXX inits+ -- XXX tails -- ** Specific scans -- Indexing can be considered as a special type of zipping where we zip a@@ -264,6 +440,8 @@ -- * Insertion -- | Add elements to the stream.+ --+ -- >>> insert = Stream.insertBy compare -- Inserting elements is a special case of interleaving/merging streams. , insertBy@@ -294,17 +472,33 @@ , catEithers -- ** Stateful Filters- -- | 'scanMaybe' is the most general stateful filtering operation. The++ -- XXX Should use scanr instead of scanlMaybe for filtering.++ -- 'scanMaybe' is the most general stateful filtering operation. The -- filtering folds (folds returning a 'Maybe' type) in -- "Streamly.Internal.Data.Fold" can be used along with 'scanMaybe' to -- perform stateful filtering operations in general.- , scanMaybe+ --+ -- Idioms and equivalents of Data.List APIs:+ --+ -- >>> deleteBy cmp x = Stream.scanMaybe (Fold.deleteBy cmp x)+ -- >>> deleteBy = Stream.deleteBy -- unreleased API+ -- >>> delete = deleteBy (==)+ -- >>> findIndices p = Stream.scanMaybe (Fold.findIndices p)+ -- >>> elemIndices a = findIndices (== a)+ -- >>> uniq = Stream.scanMaybe (Fold.uniqBy (==))+ -- >>> partition p = Stream.fold (Fold.partition Fold.toList Fold.toList) . fmap (if p then Left else Right)+ -- >>> takeLast n s = Stream.fromEffect $ fmap Array.read $ Array.createOfLast n s+ -- , scanlMaybe , take , takeWhile , takeWhileM , drop , dropWhile , dropWhileM+ -- XXX write to an array in reverse and then read in reverse+ -- > dropWhileEnd = reverse . dropWhile p . reverse -- XXX These are available as scans in folds. We need to check the -- performance though. If these are common and we need convenient stream@@ -326,34 +520,42 @@ -- , elemIndices -- * Combining Two Streams+ -- | Note that these operations are suitable for statically fusing a few+ -- streams, they have a quadratic O(n^2) time complexity wrt to the number+ -- of streams. If you want to compose many streams dynamically using binary+ -- combining operations see the corresponding operations in+ -- "Streamly.Data.StreamK".+ --+ -- When fusing more than two streams it is more efficient if the binary+ -- operations are composed as a balanced tree rather than a right+ -- associative or left associative one e.g.:+ --+ -- >>> s1 = Stream.fromList [1,2] `Stream.append` Stream.fromList [3,4]+ -- >>> s2 = Stream.fromList [4,5] `Stream.append` Stream.fromList [6,7]+ -- >>> s = s1 `Stream.append` s2+ -- ** Appending+ -- | Equivalent of Data.List append:+ --+ -- >>> (++) = Stream.append , append -- ** Interleaving- -- | When interleaving more than two streams you may want to interleave- -- them pairwise creating a balanced binary merge tree. , interleave -- ** Merging- -- | When merging more than two streams you may want to merging them- -- pairwise creating a balanced binary merge tree.- --- -- Merging of @n@ streams can be performed by combining the streams pair- -- wise using 'mergeMapWith' to give O(n * log n) time complexity. If used- -- with 'concatMapWith' it will have O(n^2) performance.- , mergeBy , mergeByM -- ** Zipping- -- | When zipping more than two streams you may want to zip them- -- pairwise creating a balanced binary tree.+ -- | Idioms and equivalents of Data.List APIs: --- -- Zipping of @n@ streams can be performed by combining the streams pair- -- wise using 'mergeMapWith' with O(n * log n) time complexity. If used- -- with 'concatMapWith' it will have O(n^2) performance.+ -- >>> zip = Stream.zipWith (,)+ -- >>> unzip = Stream.fold (Fold.unzip Fold.toList Fold.toList) , zipWith , zipWithM+ -- XXX zipWith3,4,5,6,7+ -- XXX unzip3,4,5,6,7 -- , ZipStream (..) -- ** Cross Product@@ -364,86 +566,189 @@ -- transformed stream at the end we can have a flipped version called -- "crossMap" or "nestWith". , crossWith- -- , cross+ , cross+ -- , fairCrossWith+ , fairCross -- , joinInner -- , CrossStream (..) -- * Unfold Each- , unfoldMany- , intercalate- , intercalateSuffix+ -- Idioms and equivalents of Data.List APIs:+ --+ -- >>> cycle = Stream.unfoldEach Unfold.fromList . Stream.repeat+ -- >>> unlines = Stream.unfoldEachEndBy '\n'+ -- >>> unwords = Stream.unfoldEachSepBy ' '+ -- >>> unlines = Stream.unfoldEachEndBySeq "\n" Unfold.fromList+ -- >>> unwords = Stream.unfoldEachSepBySeq " " Unfold.fromList+ --+ , unfoldEach+ , bfsUnfoldEach+ , fairUnfoldEach+ , unfoldEachSepBySeq+ , unfoldEachEndBySeq -- * Stream of streams- -- | Stream operations like map and filter represent loop processing in+ -- | Stream operations like map and filter represent loops in -- imperative programming terms. Similarly, the imperative concept of -- nested loops are represented by streams of streams. The 'concatMap' -- operation represents nested looping.- -- A 'concatMap' operation loops over the input stream and then for each- -- element of the input stream generates another stream and then loops over- -- that inner stream as well producing effects and generating a single- -- output stream. --- -- One dimension loops are just a special case of nested loops. For- -- example, 'concatMap' can degenerate to a simple map operation:+ -- A 'concatMap' operation loops over the input stream (outer loop),+ -- generating a stream from each element of the stream. Then it loops over+ -- each element of the generated streams (inner loop), collecting them in a+ -- single output stream. --- -- > map f m = S.concatMap (\x -> S.fromPure (f x)) m+ -- One dimension loops are just a special case of nested loops. For+ -- example map and filter can be expressed using concatMap: --- -- Similarly, 'concatMap' can perform filtering by mapping an element to a- -- 'nil' stream:+ -- >>> map f = Stream.concatMap (Stream.fromPure . f)+ -- >>> filter p = Stream.concatMap (\x -> if p x then Stream.fromPure x else Stream.nil) --- -- > filter p m = S.concatMap (\x -> if p x then S.fromPure x else S.nil) m+ -- Idioms and equivalents of Data.List APIs: --+ -- >>> concat = Stream.concatMap id+ -- >>> cycle = Stream.concatMap Stream.fromList . Stream.repeat , concatEffect , concatMap , concatMapM+ -- , bfsConcatMap+ , fairConcatMap + , concatFor+ -- , bfsConcatFor+ , fairConcatFor++ , concatForM+ -- , bfsConcatForM+ , fairConcatForM+ -- * Repeated Fold- , foldMany -- XXX Rename to foldRepeat+ -- | Idioms and equivalents of Data.List APIs:+ --+ -- >>> groupsOf n = Stream.foldMany (Fold.take n Fold.toList)+ -- >>> groupBy eq = Stream.groupsWhile eq Fold.toList+ -- >>> groupBy eq = Stream.parseMany (Parser.groupBy eq Fold.toList)+ -- >>> groupsByRolling eq = Stream.parseMany (Parser.groupByRolling eq Fold.toList)+ -- >>> groups = groupBy (==)+ , foldMany+ , groupsOf , parseMany- , Array.chunksOf + -- * Splitting+ -- | Idioms and equivalents of Data.List APIs:+ --+ -- >>> splitEndBy p f = Stream.foldMany (Fold.takeEndBy p f)+ -- >>> splitEndBy_ p f = Stream.foldMany (Fold.takeEndBy_ p f)+ -- >>> lines = splitEndBy_ (== '\n')+ -- >>> words = Stream.wordsBy isSpace+ -- >>> splitAt n = Stream.fold (Fold.splitAt n Fold.toList Fold.toList)+ -- >>> span p = Parser.splitWith (,) (Parser.takeWhile p Fold.toList) (Parser.fromFold Fold.toList)+ -- >>> break p = span (not . p)+ , splitSepBy_+ , splitSepBySeq_+ , splitEndBySeq+ , splitEndBySeq_+ , wordsBy++ -- XXX Should use scanr instead+ -- >>> nub = Stream.fold Fold.toList . Stream.scanMaybe Fold.nub+ -- * Buffered Operations -- | Operations that require buffering of the stream. -- Reverse is essentially a left fold followed by an unfold.+ --+ -- Idioms and equivalents of Data.List APIs:+ --+ -- >>> nub = Stream.ordNub -- unreleased API+ -- >>> sortBy = StreamK.sortBy+ -- >>> sortOn f = StreamK.sortOn -- unreleased API+ -- >>> deleteFirstsBy = Stream.deleteFirstsBy -- unreleased+ -- >>> (\\) = Stream.deleteFirstsBy (==) -- unreleased+ -- >>> intersectBy = Stream.intersectBy -- unreleased+ -- >>> intersect = Stream.intersectBy (==) -- unreleased+ -- >>> unionBy = Stream.unionBy -- unreleased+ -- >>> union = Stream.unionBy (==) -- unreleased+ -- , reverse+ , unionBy+ -- XXX transpose: write the streams to arrays and then stream transposed.+ -- XXX subsequences+ -- XXX permutations+ -- , nub+ -- , ordNub+ -- , nubBy -- * Multi-Stream folds -- | Operations that consume multiple streams at the same time. , eqBy , cmpBy , isPrefixOf+ , isInfixOf+ -- , isSuffixOf+ -- , isSuffixOfUnbox , isSubsequenceOf -- trimming sequences , stripPrefix+ -- , stripSuffix+ -- , stripSuffixUnbox -- Exceptions and resource management depend on the "exceptions" package -- XXX We can have IO Stream operations not depending on "exceptions" -- in Exception.Base -- * Exceptions- -- | Most of these combinators inhibit stream fusion, therefore, when+ -- | __Scope__: Note that the stream exception handling routines+ -- (catch and handle) observe exceptions only in the stream segment (i.e.+ -- functions with the 'Stream' type) of the pipeline and not in the+ -- consumer segments (i.e. functions with 'Fold' or 'Parser' types). For+ -- example, if we are folding or parsing a stream - any exceptions in the+ -- fold or parser code won't be observed by the stream exception handlers.+ --+ -- Exceptions in the fold code can be handled using similar exception+ -- handling routines found in the "Streamly.Data.Fold" module. To observe+ -- exceptions in the entire pipeline, you can wrap the stream elimination+ -- effect itself in a monad level exception handler (e.g. @Stream.fold+ -- Fold.drain `catch` ...@).+ --+ -- Most of these combinators inhibit stream fusion, therefore, when -- possible, they should be called in an outer loop to mitigate the cost. -- For example, instead of calling them on a stream of chars call them on a -- stream of arrays before flattening it to a stream of chars. --- -- See also: "Streamly.Internal.Data.Stream.Exception" for- -- @Pre-release@ functions.- , onException , handle -- * Resource Management -- | 'bracket' is the most general resource management operation, all other- -- operations can be expressed using it. These functions have IO suffix- -- because the allocation and cleanup functions are IO actions. For- -- generalized allocation and cleanup functions see the functions without- -- the IO suffix in the "streamly" package.+ -- resource management operations can be expressed using it. These+ -- functions have IO suffix because the allocation and cleanup functions+ -- are IO actions. For generalized allocation and cleanup functions, see+ -- the functions without the IO suffix in the @streamly@ package.+ --+ -- __Scope__: Note that these operations bracket only the stream-segment in+ -- a pipeline, they do not cover the stream-consumer (e.g. folds). This+ -- means that if an exception occurs in the consumer of the stream (e.g. in a+ -- fold or parser driven by the stream) then the exception won't be+ -- observed by the stream resource handlers, in such cases the resource+ -- stream cleanup handler runs when the stream is garbage collected.+ --+ -- To observe exceptions in the entire pipline, put a monad level resource+ -- bracket around the stream elimination effect (e.g. around @(Stream.fold+ -- Fold.sum)@).+ --+ -- See also the "Streamly.Control.Exception" module for general+ -- resource management operations in non-stream as well as stream code. , before , afterIO , finallyIO+ , finallyIO'+ , finallyIO'' , bracketIO+ -- XXX Expose the Control.Exception module as well.+ , bracketIO'+ , bracketIO'' , bracketIO3 -- * Transforming Inner Monad@@ -453,129 +758,33 @@ , runReaderT , runStateT - -- -- * Stream Types- -- $serial- -- , Interleave- -- , Zip+ -- * Deprecated+ , scan+ , scanMaybe+ , postscan+ , splitOn+ , unfoldMany+ , intercalate+ , intercalateSuffix+ , chunksOf ) where -import qualified Streamly.Internal.Data.Array.Type as Array-import Streamly.Internal.Data.Stream.StreamD+import Streamly.Internal.Data.Stream 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, repeat, replicate, concatMap, span)+ mapM, scanl, sequence, reverse, iterate, foldr1, repeat, replicate,+ concatMap) +import Streamly.Internal.Data.Unbox (Unbox(..))+import Control.Monad.IO.Class (MonadIO(..))++import qualified Streamly.Internal.Data.Array.Type as Array+ #include "DocTestDataStream.hs" --- $overview------ Streamly is a framework for modular data flow based programming and--- declarative concurrency. Powerful stream fusion framework in streamly--- allows high performance combinatorial programming even when using byte level--- streams. Streamly API is similar to Haskell lists.------ == Console Echo Example------ In the following example, 'repeatM' generates an infinite stream of 'String'--- by repeatedly performing the 'getLine' IO action. 'mapM' then applies--- 'putStrLn' on each element in the stream converting it to stream of '()'.--- Finally, 'drain' folds the stream to IO discarding the () values, thus--- producing only effects.------ >>> import Data.Function ((&))------ >>> :{--- echo =--- Stream.repeatM getLine -- Stream IO String--- & Stream.mapM putStrLn -- Stream IO ()--- & Stream.fold Fold.drain -- IO ()--- :}------ This is a console echo program. It is an example of a declarative loop--- written using streaming combinators. Compare it with an imperative @while@--- loop.------ Hopefully, this gives you an idea how we can program declaratively by--- representing loops using streams. In this module, you can find all--- "Data.List" like functions and many more powerful combinators to perform--- common programming tasks.------ == Stream Fusion------ The fused 'Stream' type employs stream fusion for C-like performance when--- looping over data. It represents a stream source or transformation by--- defining a state machine with explicit state, and a step function working on--- the state. A typical stream operation consumes elements from the previous--- state machine in the pipeline, transforms them and yields new values for the--- next stage to consume. The stream operations are modular and represent a--- single task, they have no knowledge of previous or next operation on the--- elements.------ A typical stream pipeline consists of a stream producer, several stream--- transformation operations and a stream consumer. All these operations taken--- together form a closed loop processing the stream elements. Elements are--- transferred between stages using a boxed data constructor. However, all the--- stages of the pipeline are fused together by GHC, eliminating the--- intermediate constructors, and thus forming a tight C like loop without any--- boxed data being used in the loop.------ Stream fusion works effectively when:------ * the stream pipeline is composed statically (known at compile time)--- * all the operations forming the loop are inlined--- * the loop is not recursively defined, recursion breaks inlining------ If these conditions cannot be met, the CPS style stream type 'StreamK' may--- turn out to be a better choice than the fused stream type 'Stream'.------ == Stream vs StreamK------ The fused stream model avoids constructor allocations or function call--- overheads. However, the stream is represented as a state machine and to--- generate elements it has to navigate the decision tree of the state machine.--- Moreover, the state machine is cranked for each element in the stream. This--- performs extremely well when the number of states are limited. The state--- machine starts getting expensive as the number of states increase. For--- example, generating a million element stream from a list requires a single--- state and is very efficient. However, using fused 'cons' to generate a--- million element stream would be a disaster.------ A typical worst case scenario for fused stream model is a large number of--- `cons` or `append` operations. A few static `cons` or `append` operations--- are very fast and much faster than a CPS style stream. However, if we--- construct a large stream using `cons` it introduces as many states in the--- state machine as the number of elements. If we compose the `cons` as a--- binary tree it will take @n * log n@ time to navigate the tree, and @n * n@--- if it is a right associative composition.------ For quadratic cases of fused stream, after a certain threshold the CPS--- stream would perform much better and exhibit linear performance behavior.--- Operations like 'cons' or 'append'; are typically recursively called to--- construct a lazy infinite stream. For such use cases the CPS style 'StreamK'--- type is provided. CPS streams do not have a state machine that needs to be--- cranked for each element, past state has no effect on the future element--- processing. However, it incurs a function call overhead for each operation--- for each element, which could be very large overhead compared to fused state--- machines even if it has many states and cranks it for each element. But in--- some cases scales tip in favor of the CPS stream. In those cases even though--- CPS has a large constant overhead, it has a linear performance rather than--- quadratic.------ As a general guideline, if you have to use 'cons' or 'append' or operations--- of similar nature, at a large scale, then 'StreamK' should be used. When you--- need to compose the stream dynamically or recursively, then 'StreamK' should--- be used. Typically you would use a dynamically generated 'StreamK' with--- chunks of data which can then be processed by statically fused stream--- pipeline operations.------ 'Stream' and 'StreamK' types can be interconverted. See--- "Streamly.Data.StreamK" module for conversion operations.------ == Useful Idioms------ >>> fromListM = Stream.sequence . Stream.fromList--- >>> fromIndices f = fmap f $ Stream.enumerateFrom 0+{-# DEPRECATED chunksOf "Please use chunksOf from the Array module instead." #-}+{-# INLINE chunksOf #-}+chunksOf :: forall m a. (MonadIO m, Unbox a)+ => Int -> Stream m a -> Stream m (Array.Array a)+chunksOf = Array.chunksOf
− src/Streamly/Data/Stream/Zip.hs
@@ -1,16 +0,0 @@--- |--- Module : Streamly.Data.Stream.Zip--- Copyright : (c) 2017 Composewell Technologies------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : released--- Portability : GHC----module Streamly.Data.Stream.Zip- (- ZipStream (..)- )-where--import Streamly.Internal.Data.Stream.Zip
src/Streamly/Data/StreamK.hs view
@@ -8,12 +8,57 @@ -- Stability : released -- Portability : GHC ----- Streams using Continuation Passing Style (CPS). See the @Stream vs StreamK@--- section in the "Streamly.Data.Stream" module to know when to use this--- module.+-- Streams represented as chains of function calls using Continuation Passing+-- Style (CPS), suitable for dynamically and recursively composing potentially+-- large number of streams. The 'K' in 'StreamK' stands for Kontinuation. ----- Please refer to "Streamly.Internal.Data.Stream.StreamK" for more functions--- that have not yet been released.+-- In addition to the combinators in this module, you can use operations from+-- "Streamly.Data.Stream" for StreamK as well by converting StreamK to Stream+-- ('toStream'), and vice-versa ('fromStream'). Please refer to+-- "Streamly.Internal.Data.StreamK" for more functions that have not yet been+-- released.+--+-- For documentation see the corresponding combinators in+-- "Streamly.Data.Stream". Documentation has been omitted in this module unless+-- there is a difference worth mentioning or if the combinator does not exist+-- in "Streamly.Data.Stream".+--+-- == Fused vs CPS Streams+--+-- Unlike the statically fused operations in "Streamly.Data.Stream", StreamK+-- operations are less efficient, involving a function call overhead for each+-- element, but they exhibit linear O(n) time complexity wrt to the number of+-- stream compositions. Therefore, they are suitable for dynamically composing+-- streams e.g. appending potentially infinite streams in recursive loops.+-- While fused streams can be used efficiently to process elements as small as+-- a single byte, CPS streams are typically used on bigger chunks of data to+-- avoid the larger overhead per element.+--+-- = Overview+--+-- StreamK can be constructed like lists, except that they use 'nil' instead of+-- '[]' and 'cons' instead of ':'.+--+-- >>> import Streamly.Data.StreamK (StreamK, cons, consM, nil)+--+-- `cons` constructs a stream from pure values:+--+-- >>> stream = 1 `cons` 2 `cons` nil :: StreamK IO Int+--+-- Operations from "Streamly.Data.Stream" can be used for StreamK as well by+-- converting StreamK to Stream ('toStream'), and vice-versa ('fromStream').+--+-- >>> Stream.fold Fold.toList $ StreamK.toStream stream -- IO [Int]+-- [1,2]+--+-- Stream can also be constructed from effects not just pure values:+--+-- >>> effect n = print n >> return n+-- >>> stream = effect 1 `consM` effect 2 `consM` nil+-- >>> Stream.fold Fold.toList $ StreamK.toStream stream+-- 1+-- 2+-- [1,2] -- Notes: --@@ -36,14 +81,21 @@ -- -- $setup - -- * Overview- -- $overview- -- * Type StreamK + -- -- * Nested+ -- -- | List transformers and logic programming monads.+ -- , Nested(..) -- need to decide on mtl instances+ -- , FairNested(..) -- bind is not associative+ -- * Construction -- ** Primitives+ -- | Primitives to construct a stream from pure values or monadic actions.+ -- All other stream construction and generation combinators described later+ -- can be expressed in terms of these primitives. However, the special+ -- versions provided in this module can be much more efficient in some+ -- cases. Users can create custom combinators using these primitives. , nil , nilM , cons@@ -54,12 +106,17 @@ , fromEffect -- ** From Stream+ -- | Please note that 'Stream' type does not observe any exceptions from+ -- the consumer of the stream whereas 'StreamK' does. , fromStream , toStream -- ** From Containers , fromFoldable + -- ** To Containers+ , toList+ -- * Elimination -- ** Primitives@@ -70,16 +127,24 @@ -- , foldBreak -- ** Parsing- -- , parseBreak- , parseBreakChunks- , parseChunks+ , toParserK+ , parse+ , parseBreak+ , parsePos+ , parseBreakPos -- * Transformation , mapM , dropWhile , take+ , filter -- * Combining Two Streams+ -- | Unlike the operations in "Streamly.Data.Stream", these operations can+ -- be used to dynamically compose large number of streams e.g. using the+ -- 'concatMapWith' and 'mergeMapWith' operations. They have a linear O(n)+ -- time complexity wrt to the number of streams being composed.+ -- ** Appending , append @@ -103,51 +168,50 @@ -- , CrossStreamK (..) -- * Stream of streams+ -- | Some useful idioms:+ --+ -- >>> concatFoldableWith f = Prelude.foldr f StreamK.nil+ -- >>> concatMapFoldableWith f g = Prelude.foldr (f . g) StreamK.nil+ -- >>> concatForFoldableWith f xs g = Prelude.foldr (f . g) StreamK.nil xs+ -- , concatEffect- -- , concatMap+ , concatMap+ , bfsConcatMap+ , fairConcatMap , concatMapWith++ , concatFor+ , bfsConcatFor+ , fairConcatFor++ , concatForM+ , bfsConcatForM+ , fairConcatForM+ , mergeMapWith -- * Buffered Operations , reverse , sortBy++ -- * Exceptions+ -- | Please note that 'Stream' type does not observe any exceptions from+ -- the consumer of the stream whereas 'StreamK' does.+ , handle++ -- * Resource Management+ -- | Please note that 'Stream' type does not observe any exceptions from+ -- the consumer of the stream whereas 'StreamK' does.+ , bracketIO++ -- * Deprecated+ , parseBreakChunks+ , parseChunks ) where -import Streamly.Internal.Data.Stream.StreamK-import Prelude hiding (reverse, zipWith, mapM, dropWhile, take)+import Streamly.Internal.Data.StreamK+import Prelude hiding+ (reverse, zipWith, mapM, dropWhile, take, filter, concatMap) #include "DocTestDataStreamK.hs"---- $overview------ Continuation passing style (CPS) stream implementation. The 'K' in 'StreamK'--- stands for Kontinuation.------ StreamK can be constructed like lists, except that they use 'nil' instead of--- '[]' and 'cons' instead of ':'.------ `cons` adds a pure value at the head of the stream:------ >>> import Streamly.Data.StreamK (StreamK, cons, consM, nil)--- >>> stream = 1 `cons` 2 `cons` nil :: StreamK IO Int------ You can use operations from "Streamly.Data.Stream" for StreamK as well by--- converting StreamK to Stream ('toStream'), and vice-versa ('fromStream').------ >>> Stream.fold Fold.toList $ StreamK.toStream stream -- IO [Int]--- [1,2]------ `consM` adds an effect at the head of the stream:------ >>> stream = effect 1 `consM` effect 2 `consM` nil--- >>> Stream.fold Fold.toList $ StreamK.toStream stream--- 1--- 2--- [1,2]------ == Exception Handling------ There are no native exception handling operations in the StreamK module,--- please convert to 'Stream' type and use exception handling operations from--- "Streamly.Data.Stream".
src/Streamly/Data/Unfold.hs view
@@ -11,7 +11,7 @@ -- Fast, composable stream producers with ability to terminate, supporting -- nested stream fusion. Nested stream operations like -- 'Streamly.Data.Stream.concatMap' in the "Streamly.Data.Stream" module do not--- fuse, however, the 'Streamly.Data.Stream.unfoldMany' operation, using the+-- fuse, however, the 'Streamly.Data.Stream.unfoldEach' operation, using the -- 'Unfold' type, is a fully fusible alternative to -- 'Streamly.Data.Stream.concatMap'. --@@ -53,6 +53,9 @@ , replicateM , iterateM + -- ** Enumeration+ , Enumerable (..)+ -- ** From Containers , fromList , fromListM@@ -62,6 +65,9 @@ -- ** Mapping on Input , lmap , lmapM+ , first+ , second+ , carry -- ** Mapping on Output , mapM@@ -83,6 +89,9 @@ , crossWith -- ** Nesting+ , unfoldEach++ -- * Deprecated , many )
src/Streamly/FileSystem/Dir.hs view
@@ -1,3 +1,4 @@+{-# OPTIONS_GHC -Wno-deprecations #-} -- | -- Module : Streamly.FileSystem.Dir -- Copyright : (c) 2018 Composewell Technologies@@ -12,6 +13,7 @@ -- something else. module Streamly.FileSystem.Dir+{-# DEPRECATED "Please use \"Streamly.FileSystem.DirIO\" instead." #-} ( -- * Streams read
+ src/Streamly/FileSystem/DirIO.hs view
@@ -0,0 +1,41 @@+-- |+-- Module : Streamly.FileSystem.DirIO+-- Copyright : (c) 2018 Composewell Technologies+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : pre-release+-- Portability : GHC+--+-- High performance and streaming APIs for reading directories.+--+-- File system paths are specified using the 'Streamly.FileSystem.Path.Path'+-- type. If you want to convert between 'String' or 'FilePath' and 'Path' use+-- 'Streamly.FileSystem.Path.fromString_', 'Streamly.FileSystem.Path.toString'+-- from the "Streamly.FileSystem.Path" module..+--+-- >>> import qualified Streamly.FileSystem.DirIO as Dir+--++module Streamly.FileSystem.DirIO+ (+ -- * Configuration+#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+ -- | Only the default ReadOptions are supported for Windows. Please use "id"+ -- as the configuration modifier.+ ReadOptions+#else+ ReadOptions+ , followSymlinks+ , ignoreMissing+ , ignoreSymlinkLoops+ , ignoreInaccessible+#endif+ -- * Streams+ , read+ , readEither+ )+where++import Streamly.Internal.FileSystem.DirIO+import Prelude hiding (read)
src/Streamly/FileSystem/File.hs view
@@ -1,3 +1,5 @@+{-# OPTIONS_GHC -Wno-deprecations #-}+ -- | -- Module : Streamly.FileSystem.File -- Copyright : (c) 2019 Composewell Technologies@@ -19,9 +21,10 @@ -- the handle based APIs as there is no possibility of a file descriptor -- leakage. ----- >>> import qualified Streamly.FileSystem.File as File+-- >> import qualified Streamly.FileSystem.File as File -- module Streamly.FileSystem.File+{-# DEPRECATED "Please use \"Streamly.FileSystem.FileIO\" instead." #-} ( -- * Streaming IO -- | Stream data to or from a file or device sequentially. When reading,
+ src/Streamly/FileSystem/FileIO.hs view
@@ -0,0 +1,67 @@+-- |+-- Module : Streamly.FileSystem.FileIO+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : pre-release+-- Portability : GHC+--+-- Read and write streams and arrays to and from files specified by their paths+-- in the file system. These APIs open the file handle, perform the requested+-- operation and close the handle. These are higher level and safer compared to+-- the handle based APIs as there is no possibility of a file descriptor+-- leakage.+--+-- Files are always opened in:+--+-- * __Binary mode__ — encoding, decoding, and newline translation should be+-- handled explicitly by the streaming APIs.+-- * __Unbuffered mode__ — buffering can be managed explicitly via streaming+-- APIs.+--+-- File system paths are specified using the 'Streamly.FileSystem.Path.Path'+-- type. If you want to convert between 'String' or 'FilePath' and 'Path' use+-- 'Streamly.FileSystem.Path.fromString_', 'Streamly.FileSystem.Path.toString'+-- from the "Streamly.FileSystem.Path" module..+--+-- >> import qualified Streamly.FileSystem.FileIO as File+--+module Streamly.FileSystem.FileIO+ (+ -- * Streaming IO+ -- | Stream data to or from a file or device sequentially. When reading,+ -- the stream is lazy and generated on-demand as the consumer consumes it.+ -- Read IO requests to the IO device are performed in chunks limited to a+ -- maximum size of 32KiB, this is referred to as @defaultChunkSize@ in the+ -- documentation. One IO request may or may not read the full+ -- chunk. If the whole stream is not consumed, it is possible that we may+ -- read slightly more from the IO device than what the consumer needed.+ -- When writing, unless specified otherwise in the API, writes are+ -- collected into chunks of @defaultChunkSize@ before they are written to+ -- the IO device.++ -- Streaming APIs work for all kind of devices, seekable or non-seekable;+ -- including disks, files, memory devices, terminals, pipes, sockets and+ -- fifos. While random access APIs work only for files or devices that have+ -- random access or seek capability for example disks, memory devices.+ -- Devices like terminals, pipes, sockets and fifos do not have random+ -- access capability.++ -- ** File IO Using Handle+ withFile++ -- ** Streams+ , read+ , readChunksWith+ , readChunks++ -- ** Folds+ , write+ , writeWith+ , writeChunks+ )+where++import Streamly.Internal.FileSystem.FileIO+import Prelude hiding (read)
src/Streamly/FileSystem/Handle.hs view
@@ -1,4 +1,4 @@-#include "inline.hs"+{-# LANGUAGE CPP #-} -- | -- Module : Streamly.FileSystem.Handle@@ -14,7 +14,11 @@ -- Read and write byte streams and array streams to and from file handles -- ('Handle'). ----- The 'TextEncoding', 'NewLineMode', and 'Buffering' options of the underlying+-- Please set NoBuffering mode on the handle as buffering is explicitly+-- controlled by the streaming API and double buffering can sometimes cause+-- unexpected results.+--+-- Also note that the 'TextEncoding', 'NewLineMode' options of the underlying -- GHC 'Handle' are ignored by these APIs. Please use "Streamly.Unicode.Stream" -- module for encoding and decoding a byte stream, use stream splitting -- operations in "Streamly.Data.Stream" to create a stream of lines or to split@@ -40,6 +44,12 @@ -- module Streamly.FileSystem.Handle (+ -- * Setup+ -- | To execute the code examples provided in this module in ghci, please+ -- run the following commands first.+ --+ -- $setup+ -- * Singleton IO -- | Read or write a single buffer. getChunk@@ -75,13 +85,13 @@ -- position of the file handle. The stream ends as soon as EOF is -- encountered. - -- -- *** Streams- -- , read- -- , readWith- -- , readChunks- -- , readChunksWith+ -- *** Streams+ , read+ , readWith+ , readChunks+ , readChunksWith - -- -- *** Unfolds+ -- *** Unfolds , reader , readerWith , chunkReader@@ -98,34 +108,14 @@ , writeChunks -- * Deprecated- , read , readWithBufferOf- , readChunks , readChunksWithBufferOf , writeChunksWithBufferOf , writeWithBufferOf ) where -import Control.Monad.IO.Class (MonadIO(..))-import Data.Word (Word8)-import Streamly.Internal.Data.Array.Type (Array)-import Streamly.Internal.Data.Unfold.Type (Unfold)-import System.IO (Handle)--import Streamly.Internal.FileSystem.Handle hiding (read, readChunks)+import Streamly.Internal.FileSystem.Handle import Prelude hiding (read) --- | Same as 'reader'----{-# DEPRECATED read "Please use 'reader' instead" #-}-{-# INLINE read #-}-read :: MonadIO m => Unfold m Handle Word8-read = reader---- | Same as 'chunkReader'----{-# DEPRECATED readChunks "Please use 'chunkReader' instead" #-}-{-# INLINE readChunks #-}-readChunks :: MonadIO m => Unfold m Handle (Array Word8)-readChunks = chunkReader+#include "DocTestFileSystemHandle.hs"
+ src/Streamly/FileSystem/Path.hs view
@@ -0,0 +1,194 @@+{-# LANGUAGE CPP #-}+-- |+-- Module : Streamly.FileSystem.Path+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--+-- File system paths that are extensible, high-performance and preserve the OS+-- and filesystem encoding.+--+-- The 'Path' type is built on top of Streamly's 'Array' type, leveraging all+-- its operations — including support for both pinned and unpinned+-- representations. The API integrates with streams, prioritizes safety,+-- flexibility, and performance. It supports configurable equality for+-- cross-platform compatibility and user-defined path matching. It is designed+-- for extensibility and fine-grained type safety as well. For type-safe+-- adaptations, see the "Streamly.Internal.FileSystem.Path.*" modules.+--+-- 'Path' is interconvertible with the 'OsPath' type from the @filepath@+-- package at zero runtime cost. While the API is mostly compatible with that+-- of the @filepath@ package, some differences exist due to a slightly+-- different design philosophy focused on better safety.+--+-- = Rooted vs Unrooted Paths+--+-- To ensure the safety of the path append operation, we distinguish between+-- rooted paths and free path segments or unrooted paths. A path that starts+-- from an explicit or implicit file system root is called a rooted path or an+-- anchored path. For example, @\/usr\/bin@ is a rooted path with @/@ as an+-- explicit root directory. Similarly, @.\/bin@ is a rooted path with the+-- current directoy \".\" as an implicit root. A path that is not rooted is+-- called an unrooted path or unanchored path; for example, @local\/bin@ is an+-- unrooted path.+--+-- This distinction ensures the safety of the path append operation. You can+-- append only an unrooted path to another path, it does not make sense to+-- append a rooted path to another path. The default append operation in the+-- Path module checks for this and fails if the operation is invalid.+--+-- Rooted vs unrooted distinction is a stricter form of relative vs absolute+-- path distinction. In this model, for better safety, paths relative to the+-- current directory are also treated in the same way as absolute paths, from+-- the perspective of a path append operation. This is because the meaning of+-- current directory is context dependent and dynamic, therefore, appending it+-- to another path is not allowed. Only unrooted path segments (e.g.+-- @local/bin@) can be appended to any other path using safe operations.+--+-- = File vs. Directory Paths+--+-- By default, a path with a trailing separator (e.g. @local/@) is implicitly+-- considered a directory path. However, the absence of a trailing separator+-- does not indicate whether the path is a file or a directory — it could be+-- either. Therefore, when using the @Path@ type, the append operation allows+-- appending to paths even if they lack a trailing separator.+--+-- = Compatibility with the filepath package+--+-- Any path type can be converted to the 'FilePath' type from the @filepath@+-- package by using the 'toString' operation. Operations to convert to and from+-- the 'OsPath' type at zero cost are provided in the @streamly-filepath@+-- package. Zero-cost interconversion is possible because the 'Path' type uses+-- an underlying representation which is compatible with the 'OsPath' type.+--+-- = Path Creation Quasiquoter+--+-- The 'path' quasiquoter is useful in creating valid paths that are checked+-- during the compile time.++module Streamly.FileSystem.Path+ (+ -- * Setup+ -- | To execute the code examples provided in this module in ghci, please+ -- run the following commands first.+ --+ -- $setup++ -- * Type+ Path+ , OsWord++ -- * Construction+ , validatePath+ , fromArray+ , fromString+ , fromString_++ -- * Statically Verified String Literals+ -- | Quasiquoters.+ , path++ -- * Statically Verified Strings+ -- | Template Haskell expression splices.+ , pathE++ -- * Elimination+ , toArray+ -- , toChars -- need fromChars as well+ , toString+ -- , asOsCString++ -- * Path Info+ , isRooted+ , isUnrooted++ -- * Joining+ , unsafeJoin+ , join+ , joinStr++ -- * Splitting root+ , splitRoot++ -- * Splitting path components+ , splitPath+ -- , splitPath_++ -- * Splitting file extension+ , splitExtension+ , takeExtension+ , dropExtension+ -- , addExtension+ -- , replaceExtension++ -- * Splitting file and dir+ , splitFile+ , takeFileName+ , takeDirectory+ , takeFileBase++ -- * Equality+ , EqCfg+ , ignoreCase+ , ignoreTrailingSeparators+ , allowRelativeEquality++ , eqPath+ )+where++{- Documentation on typed paths. We can add this back into the module level+ documentation when we introduce the typed paths.++-- = Rooted Paths vs Branches+--+-- /Flexible typing/: you can choose the level of type safety you want. 'Path'+-- is the basic path type which can represent a file, directory, absolute or+-- relative path with no restrictions. Depending on how much type safety you+-- want, you can choose appropriate type wrappers or a combination of those to+-- wrap the 'Path' type in stricter types.++-- The "Streamly.FileSystem.Path.Seg" module provides explicit types for path+-- segments, distinguishing rooted paths from branches. Rooted paths use the+-- @Rooted Path@ type, and branches use the @Branch Path@ type. If you use the+-- generic 'Path' type, append may fail at run time if you attempt to append+-- a rooted path to another rooted path. In contrast, using the @Rooted Path@+-- and @Branch Path@ types guarantees compile-time safety, preventing such errors.++-- = File vs. Directory Paths+--+-- Independent of the rooted or branch distinction, you can also make a+-- type-level distinction between file and directory nodes using the+-- "Streamly.FileSystem.Path.Node" module. The type @File Path@ represents a+-- file, whereas @Dir Path@ represents a directory. This distinction provides+-- safety against appending to file type paths — append operations are not+-- allowed on paths of type 'File'.++-- = Flexible Typing+--+-- You can use the 'Rooted', 'Branch', 'Dir', and 'File' types independently by+-- importing only the required modules. If you want both types of distinctions,+-- you can use them together via the "Streamly.FileSystem.Path.SegNode" module.+-- For example, @Rooted (Dir Path)@ represents a rooted path that is a+-- directory. You can append other paths only to paths that have a 'Dir' type,+-- and only a path of type 'Branch' can be appended.+--+-- You may choose to use the basic 'Path' type or any combination of the safer+-- types. You can upgrade or downgrade the safety level by converting between+-- types using the @adapt@ operation. When converting from a less restrictive+-- type to a more restrictive one, run-time checks are performed, and the+-- conversion may fail. However, converting from a more restrictive type to a+-- less restrictive one is always allowed.+--+-- = Extensibility+--+-- You can define your own newtype wrappers similar to 'File' or 'Dir' to+-- provide custom restrictions if you want.+--++-}++import Streamly.Internal.FileSystem.Path++#include "DocTestFileSystemPath.hs"
+ src/Streamly/FileSystem/Path/Node.hs view
@@ -0,0 +1,37 @@+-- |+-- Module : Streamly.FileSystem.Path.Node+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--+-- Represent 'File' or 'Dir' type path nodes explicitly as separate types for+-- the safety of path append operation. A 'Dir' path is a branching or+-- intermediate node whereas a 'File' type is a terminal or leaf node. We+-- cannot append a path to a 'File' type path.+--+-- See the overview in the "Streamly.FileSystem.Path" module for more details.+--+module Streamly.FileSystem.Path.Node+ (+ -- * Types+ File+ , Dir+ , IsNode++ -- * Statically Verified Path Literals+ -- | Quasiquoters.+ , dir+ , file++ -- * Statically Verified Path Strings+ -- | Template Haskell expression splices.+ , dirE+ , fileE++ -- * Operations+ , join+ )+where++import Streamly.Internal.FileSystem.Path.Node
+ src/Streamly/FileSystem/Path/Seg.hs view
@@ -0,0 +1,37 @@+-- |+-- Module : Streamly.FileSystem.Path.Seg+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--+-- Represent 'Rooted' or 'Unrooted' type path segments explicitly as separate+-- types for the safety of path append operation. A Rooted path is an absolute+-- path or a path that is relative to the current directory with a leading dot.+-- Rooted paths cannot be appended to other paths.+--+-- See the overview in the "Streamly.FileSystem.Path" module for more details.+--+module Streamly.FileSystem.Path.Seg+ (+ -- * Types+ Rooted+ , Unrooted+ , IsSeg++ -- * Statically Verified Path Literals+ -- | Quasiquoters.+ , rt+ , ur++ -- * Statically Verified Path Strings+ -- | Template Haskell expression splices.+ , rtE+ , urE++ -- * Operations+ , join+ )+where++import Streamly.Internal.FileSystem.Path.Seg
+ src/Streamly/FileSystem/Path/SegNode.hs view
@@ -0,0 +1,37 @@+-- |+-- Module : Streamly.FileSystem.Path.SegNode+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--+-- Use 'Rooted' or 'Unrooted' path segment type annotations as well as 'File' and+-- 'Dir' node type annotations on the same path for the safety of path append+-- operation. A Rooted path cannot be appended to other paths, and you canno+-- append a path to a 'File' type path.+--+-- See the overview in the "Streamly.FileSystem.Path" module for more details.+--++module Streamly.FileSystem.Path.SegNode+ (+ -- * Statically Verified Path Literals+ -- | Quasiquoters.+ rtdir+ , urdir+ , rtfile+ , urfile++ -- * Statically Verified Path Strings+ -- | Template Haskell expression splices.+ , rtdirE+ , urdirE+ , rtfileE+ , urfileE++ -- * Operations+ , join+ )+where++import Streamly.Internal.FileSystem.Path.SegNode
src/Streamly/Internal/Console/Stdio.hs view
@@ -9,16 +9,25 @@ module Streamly.Internal.Console.Stdio (- -- * Streams+ -- * Singleton APIs+ -- getChunk+ -- , putChunk++ -- * Stream reads read- , readChars+ -- , readWith -- buffer , readChunks- -- , getChunksLn- -- , getStringsWith -- get strings using the supplied decoding- -- , getStrings -- get strings of complete chars,- -- leave any partial chars for next string- -- , getStringsLn -- get lines decoded as char strings+ -- , readChunksWith -- buffer+ -- , readChunksLn -- chunks with line buffering -- repeatM Text.getLine + -- -- ** Encoding specific+ -- , readCharsWith+ -- , readStringsLnWith++ -- ** UTF-8 decoded+ , readChars+ -- , readStringsLn -- strings with line buffering -- repeatM getLine+ -- * Unfolds , reader , chunkReader@@ -31,11 +40,18 @@ -- * Stream writes , putBytes -- Buffered (32K)- , putChars , putChunks -- Unbuffered++ -- ** Encoding specific+ -- , putCharsWith , putStringsWith+ -- , putStringsLnWith++ -- ** UTF-8 encoded+ , putChars , putStrings , putStringsLn+ -- , putChunksLn ) where @@ -47,13 +63,12 @@ import Prelude hiding (read) import Streamly.Internal.Data.Array.Type (Array(..))-import Streamly.Internal.Data.Stream.StreamD (Stream)+import Streamly.Internal.Data.Stream (Stream) import Streamly.Internal.Data.Unfold (Unfold) import Streamly.Internal.Data.Fold (Fold) import qualified Streamly.Internal.Data.Array as Array-import qualified Streamly.Internal.Data.Stream.StreamD as Stream- (intersperseMSuffix)+import qualified Streamly.Internal.Data.Stream as Stream import qualified Streamly.Internal.Data.Unfold as Unfold import qualified Streamly.Internal.FileSystem.Handle as Handle import qualified Streamly.Internal.Unicode.Stream as Unicode@@ -194,7 +209,7 @@ -- folds as well as unfolds/streams. Non-backtracking (one-to-one, one-to-many, -- filters, reducers) transformations may be easy so we can possibly start with -- those.---+ -- | Write a stream of strings to standard output using the supplied encoding. -- Output is flushed to the device for each string. --@@ -224,5 +239,5 @@ putStringsLn :: MonadIO m => Stream m String -> m () putStringsLn = putChunks- . Stream.intersperseMSuffix (return $ Array.fromList [10])+ . Stream.intersperseEndByM (return $ Array.fromList [10]) . Unicode.encodeStrings Unicode.encodeUtf8
src/Streamly/Internal/Control/Exception.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} -- | -- Module : Streamly.Internal.Control.Exception -- Copyright : (c) 2019 Composewell Technologies@@ -10,11 +11,50 @@ -- Additional "Control.Exception" utilities. module Streamly.Internal.Control.Exception- ( verify+ (+ -- * Verify+ verify , verifyM++ -- * Resource Management+ -- | Exception safe, thread safe resource managment operations, similar to+ -- but more powerful than the @bracket@ and @finally@ operations available+ -- in the base package.+ --+ -- These operations support allocation and free only in the IO monad,+ -- hence the IO suffix.+ --+ , AcquireIO(..)+ , Priority(..)+ , allocator+ , releaser+ , withAcquireIO+ , acquireWith+ , acquire+ , acquire_+ , registerWith+ , register+ , hook ) where +-- import Control.Concurrent (myThreadId)+import Control.Monad (void)+import Control.Monad.IO.Class (MonadIO(..))+import Control.Exception (mask_)+import Control.Monad.Catch (MonadMask)+import Data.IntMap.Strict (IntMap)+import Data.IORef (IORef, newIORef, atomicModifyIORef')++import qualified Control.Monad.Catch as MC+import qualified Data.IntMap.Strict as Map++#include "DocTestControlException.hs"++-------------------------------------------------------------------------------+-- Asserts+-------------------------------------------------------------------------------+ -- | Like 'assert' but is not removed by the compiler, it is always present in -- production code. --@@ -36,3 +76,252 @@ {-# INLINE verifyM #-} verifyM :: Applicative f => Bool -> f () verifyM predicate = verify predicate (pure ())++-------------------------------------------------------------------------------+-- Resource management+-------------------------------------------------------------------------------++-- XXX In a manual release mechanism of resources we always have the risk of+-- using the resource by some persisting thread even after it has been freed.+-- Ideally, we should use the GC to clean up resources because that way we do+-- not need to worry about references, we can pass around resources to other+-- threads and we get an automatic reference counting. Is it possible to use+-- compact regions to confine resource to smaller areas so that we can perform+-- a limited GC to free them? We can then just put gc sync barriers at points+-- where we want to ensure that resources are freed.++-- | Resources with 'Priority1' are freed before 'Priority2'. Priority is+-- especially introduced to take care of the case where we need to free+-- concurrency channels, so that all the workers of the channel are cleaned up+-- before we free the resources allocated by the workers of the channel.+-- Otherwise we might free the resources and workers may be trying to use them+-- and start misbehaving.+--+data Priority = Priority1 | Priority2 deriving Show++-- To keep the type signatures simple and to avoid inference problems we should+-- use this newtype. We cannot pass around a foralled type without wrapping+-- it in a newtype.++-- | @AcquireIO@ is used to acquire a resource safely such that it is+-- automatically released if not released manually.+--+-- See 'withAcquireIO'.+--+newtype AcquireIO = AcquireIO+ (forall b c. Priority -> IO b -> (b -> IO c) -> IO (b, IO ()))++-- | /Internal/.+allocator :: MonadIO m =>+ IORef (Int, IntMap (IO ()), IntMap (IO ()))+ -> Priority+ -> IO a+ -> (a -> IO b)+ -> m (a, m ())+allocator ref pri alloc free = do+ let insertResource r (i, mp1, mp2) =+ case pri of+ Priority1 ->+ ((i + 1, Map.insert i (void $ free r) mp1, mp2), i)+ Priority2 ->+ ((i + 1, mp1, Map.insert i (void $ free r) mp2), i)++ (r, index) <-+ liftIO $ mask_ $ do+ -- tid <- myThreadId+ r <- alloc+ idx <- atomicModifyIORef' ref (insertResource r)+ -- liftIO $ putStrLn $ "insert: " ++ show pri+ -- ++ " " ++ show idx ++ " " ++ show tid+ return (r, idx)++ let deleteResource (i, mp1, mp2) =+ case pri of+ Priority1 ->+ let res = Map.lookup index mp1+ in ((i, Map.delete index mp1, mp2), res)+ Priority2 ->+ let res = Map.lookup index mp2+ in ((i, mp1, Map.delete index mp2), res)++ release =+ -- IMPORTANT: do not use interruptible operations in this+ -- critical section. Even putStrLn can make tests fail.+ liftIO $ mask_ $ do+ -- tid <- myThreadId+ -- liftIO $ putStrLn $ "releasing index: " ++ show index+ -- ++ " " ++ show tid+ f <- atomicModifyIORef' ref deleteResource+ -- restoring exceptions makes it non-atomic, tests fail.+ -- Can use allowInterrupt in "free" if desired.+ sequence_ f+ return (r, release)++-- XXX can we ensure via GC that the resources that we are freeing are all+-- dead, there are no other references to them?++-- | We ensure that all async workers for concurrent streams are stopped+-- before we release the resources so that nobody could be using the+-- resource after they are freed.+--+-- The only other possibility, could be user issued forkIO not being+-- tracked by us, however, that would be a programming error and any such+-- threads could misbehave if we freed the resources from under them.+--+-- We use GC based hooks in 'Stream.bracketIO\'' so there could be async threads+-- spawned by GC, releasing resources concurrently with us. For that reason we+-- need to make sure that the "release" in the bracket end action is executed+-- only once in that case.+--+-- /Internal/.+releaser :: MonadIO m => IORef (a, IntMap (IO b), IntMap (IO b)) -> m ()+releaser ref =+ liftIO $ mask_ $ do+ -- Delete the map from the ref first so that anyone else (GC)+ -- releasing concurrently cannot find the map.+ -- liftIO $ putStrLn "cleaning up priority 1"+ mp1 <- atomicModifyIORef' ref+ (\(i, mp1,mp2) -> ((i, Map.empty, mp2), mp1))+ -- Note that the channel cleanup function is interruptible because+ -- it has blocking points.+ sequence_ mp1+ -- Now nobody would be changing mp2, we can read it safely+ -- liftIO $ putStrLn "cleaning up priority 2"+ mp2 <- atomicModifyIORef' ref+ (\(i, mp,mp2) -> ((i, mp, Map.empty), mp2))+ sequence_ mp2+ -- XXX We can now assert that the IORef has both maps empty.++-- | @withAcquireIO action@ runs the given @action@, providing it with a+-- an 'AcquireIO' reference called @ref@ as argument. @ref@ is used for resource+-- acquisition or hook registeration within the scope of @action@. An @acquire+-- ref alloc free@ call can be used within @action@ any number of times to+-- acquire resources that are automatically freed when the scope of @action@+-- ends or if an exception occurs at any time. @alloc@ is a function supplied+-- by the user to allocate a resource and @free@ is supplied to free the+-- allocated resource. @acquire@ returns @(resource, release)@ -- the acquired+-- @resource@ and a @release@ action to release it.+--+-- @acquire@ allocates a resource in an exception safe manner and sets up its+-- automatic release on exception or when the scope of @action@ ends. The+-- @release@ function returned by @acquire@ can be used to free the resource+-- manually at any time. @release@ is guaranteed to free the resource once and+-- only once even if it is called concurrently or multiple times.+--+-- Here is an example to allocate resources that are guaranteed to be released+-- automatically, and can be released manually as well:+--+-- >>> :{+-- close x h = do+-- putStrLn $ "closing: " ++ x+-- hClose h+-- :}+--+-- >>> :{+-- action ref =+-- Stream.fromList ["file1", "file2"]+-- & Stream.mapM+-- (\x -> do+-- (h, release) <- Exception.acquire ref (openFile x ReadMode) (close x)+-- -- use h here+-- threadDelay 1000000+-- when (x == "file1") $ do+-- putStrLn $ "Manually releasing: " ++ x+-- release+-- return x+-- )+-- & Stream.trace print+-- & Stream.fold Fold.drain+-- :}+--+-- >>> run = Exception.withAcquireIO action+--+-- In the above code, you should see the \"closing:\" message for both the+-- files, and only once for each file. Even if you interrupt the program with+-- CTRL-C you should still see the \"closing:\" message for the files opened+-- before the interrupt. Make sure you create "file1" and "file2" before+-- running this code snippet.+--+-- Cleanup is guaranteed to happen as soon as the scope of 'action'+-- finishes or if an exception occurs.+--+-- Here is an example for just registering hooks to be called eventually:+--+-- >>> :{+-- action ref =+-- Stream.fromList ["file1", "file2"]+-- & Stream.mapM+-- (\x -> do+-- Exception.register ref $ putStrLn $ "saw: " ++ x+-- threadDelay 1000000+-- return x+-- )+-- & Stream.trace print+-- & Stream.fold Fold.drain+-- :}+--+-- >>> run = Exception.withAcquireIO action+--+-- In the above code, even if you interrupt the program with CTRL-C you should+-- still see the "saw:" message for the elements seen before the interrupt.+--+-- The registered hooks are guaranteed to be invoked as soon as the scope of+-- 'action' finishes or if an exception occurs.+--+-- This function provides functionality similar to the @bracket@ function+-- available in the base library. However, it is more powerful as any number of+-- resources can be allocated and released within the scope of 'action'.+--+-- Exception safe, thread safe.+{-# INLINE withAcquireIO #-}+withAcquireIO :: (MonadIO m, MonadMask m) => (AcquireIO -> m a) -> m a+withAcquireIO action = do+ -- Assuming 64-bit int counter will never overflow+ ref <- liftIO $ newIORef (0 :: Int, Map.empty, Map.empty)+ action (AcquireIO (allocator ref)) `MC.finally` releaser ref++-- | Like 'acquire' but allows specifying a priority for releasing the+-- resource. 'Priority1' resources are released before 'Priority2'. This allows+-- us to specify a dependency between resource release.+{-# INLINE acquireWith #-}+acquireWith :: Priority -> AcquireIO -> IO b -> (b -> IO c) -> IO (b, IO ())+acquireWith pri (AcquireIO f) = f pri++-- | @acquire ref alloc free@ is used in bracket-style safe resource allocation+-- functions, where @alloc@ is a function supplied by the user to allocate a+-- resource and @free@ is supplied to free it. @acquire@ returns a tuple+-- @(resource, release)@ where @resource@ is the allocated resource and+-- @release@ is an action that can be called later to release the resource.+-- Both @alloc@ and @free@ are invoked with async signals masked. You can use+-- @allowInterrupt@ from base package for allowing interrupts if required.+--+-- The @release@ action can be called multiple times or even concurrently from+-- multiple threads, but it will release the resource only once. If @release@+-- is never called by the programmer it will be automatically called at the end+-- of the bracket scope.+--+acquire :: AcquireIO -> IO b -> (b -> IO c) -> IO (b, IO ())+acquire = acquireWith Priority2++-- | Like 'acquire' but does not return a release action. The resource is freed+-- automatically only.+acquire_ :: AcquireIO -> IO b -> (b -> IO c) -> IO b+acquire_ a b c = fmap fst $ acquire a b c++-- | Like 'register' but specifies a 'Priority' for calling the hook.+{-# INLINE registerWith #-}+registerWith :: Priority -> AcquireIO -> IO () -> IO ()+registerWith pri (AcquireIO f) g = void $ f pri (return ()) (\() -> g)++-- | Register a hook to be executed at the end of a bracket.+register :: AcquireIO -> IO () -> IO ()+register = registerWith Priority2++-- | Like 'register' but returns a hook release function as well. When the+-- returned hook release function is called, the hook is invoked and removed.+-- If the returned function is never called by the programmer then it is+-- automatically invoked at the end of the bracket. The hook is invoked once+-- and only once.+--+hook :: AcquireIO -> IO () -> IO (IO())+hook (AcquireIO f) g = fmap snd $ f Priority2 (return ()) (\() -> g)
src/Streamly/Internal/Data/Array.hs view
@@ -2,572 +2,1178 @@ -- | -- Module : Streamly.Internal.Data.Array -- Copyright : (c) 2019 Composewell Technologies------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC----module Streamly.Internal.Data.Array- (- -- * Setup- -- $setup-- -- * Design Notes- -- $design-- -- * The Array Type- Array-- -- * Construction-- -- Pure List APIs- , A.fromListN- , A.fromList-- -- Stream Folds- , fromStreamN- , fromStream-- -- Monadic Folds- , A.writeN -- drop new- , A.writeNAligned- , A.write -- full buffer- , writeLastN-- -- * Elimination- -- ** Conversion- , A.toList-- -- ** Streams- , A.read- , A.readRev-- -- ** Unfolds- , reader- , readerUnsafe- , A.readerRev- , producer -- experimental-- -- * Random Access- -- , (!!)- , getIndex- , A.unsafeIndex -- XXX Rename to getIndexUnsafe??- , getIndexRev- , last -- XXX getIndexLast?- , getIndices- , getIndicesFromThenTo- -- , getIndicesFrom -- read from a given position to the end of file- -- , getIndicesUpto -- read from beginning up to the given position- -- , getIndicesFromTo- -- , getIndicesFromRev -- read from a given position to the beginning of file- -- , getIndicesUptoRev -- read from end to the given position in file-- -- * Size- , length- , null-- -- * Search- , binarySearch- , findIndicesOf- -- , findIndexOf- -- , find-- -- * Casting- , cast- , asBytes- , castUnsafe- , asPtrUnsafe- , asCStringUnsafe- , A.unsafeFreeze -- asImmutableUnsafe?- , A.unsafeThaw -- asMutableUnsafe?-- -- * Subarrays- , getSliceUnsafe- -- , getSlice- , genSlicesFromLen- , getSlicesFromLen- , splitOn-- -- * Streaming Operations- , streamTransform-- -- ** Folding- , streamFold- , fold-- -- * Deprecated- , A.toStream- , A.toStreamRev- )-where--#include "inline.hs"-#include "ArrayMacros.h"--import Control.Exception (assert)-import Control.Monad (when)-import Control.Monad.IO.Class (MonadIO(..))-import Data.Functor.Identity (Identity)-import Data.Proxy (Proxy(..))-import Data.Word (Word8)-import Foreign.C.String (CString)-import Foreign.Ptr (castPtr)-import Foreign.Storable (Storable)-import Streamly.Internal.Data.Unboxed- ( Unbox- , peekWith- , sizeOf- )-import Prelude hiding (length, null, last, map, (!!), read, concat)--import Streamly.Internal.Data.Array.Mut.Type (ArrayUnsafe(..))-import Streamly.Internal.Data.Array.Type- (Array(..), length, asPtrUnsafe)-import Streamly.Internal.Data.Fold.Type (Fold(..))-import Streamly.Internal.Data.Producer.Type (Producer(..))-import Streamly.Internal.Data.Stream.StreamD (Stream)-import Streamly.Internal.Data.Tuple.Strict (Tuple3Fused'(..))-import Streamly.Internal.Data.Unfold.Type (Unfold(..))-import Streamly.Internal.System.IO (unsafeInlineIO)--import qualified Streamly.Internal.Data.Array.Mut.Type as MA-import qualified Streamly.Internal.Data.Array.Mut as MA-import qualified Streamly.Internal.Data.Array.Type as A-import qualified Streamly.Internal.Data.Fold as FL-import qualified Streamly.Internal.Data.Producer.Type as Producer-import qualified Streamly.Internal.Data.Producer as Producer-import qualified Streamly.Internal.Data.Ring.Unboxed as RB-import qualified Streamly.Internal.Data.Stream.StreamD as D-import qualified Streamly.Internal.Data.Stream.StreamD as Stream-import qualified Streamly.Internal.Data.Unfold as Unfold--#include "DocTestDataArray.hs"---- $design------ To summarize:------ * Arrays are finite and fixed in size--- * provide /O(1)/ access to elements--- * store only data and not functions--- * provide efficient IO interfacing------ 'Foldable' instance is not provided because the implementation would be much--- less efficient compared to folding via streams. 'Semigroup' and 'Monoid'--- instances should be used with care; concatenating arrays using binary--- operations can be highly inefficient. Instead, use--- 'Streamly.Internal.Data.Stream.Chunked.toArray' to concatenate N--- arrays at once.------ Each array is one pointer visible to the GC. Too many small arrays (e.g.--- single byte) are only as good as holding those elements in a Haskell list.--- However, small arrays can be compacted into large ones to reduce the--- overhead. To hold 32GB memory in 32k sized buffers we need 1 million arrays--- if we use one array for each chunk. This is still significant to add--- pressure to GC.------------------------------------------------------------------------------------ Construction------------------------------------------------------------------------------------ | Create an 'Array' 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.------ /Pre-release/-{-# INLINE fromStreamN #-}-fromStreamN :: (MonadIO m, Unbox a) => Int -> Stream m a -> m (Array a)-fromStreamN n m = do- when (n < 0) $ error "writeN: negative write count specified"- A.fromStreamDN n m---- | Create an 'Array' from a stream. This is useful when we want to create a--- single array from a stream of unknown size. 'writeN' is at least twice--- as efficient when the size is already known.------ Note that if the input stream is too large memory allocation for the array--- may fail. When the stream size is not known, `chunksOf` followed by--- processing of indvidual arrays in the resulting stream should be preferred.------ /Pre-release/-{-# INLINE fromStream #-}-fromStream :: (MonadIO m, Unbox a) => Stream m a -> m (Array a)-fromStream = Stream.fold A.write--- write m = A.fromStreamD $ D.fromStreamK m------------------------------------------------------------------------------------ Elimination----------------------------------------------------------------------------------{-# INLINE_NORMAL producer #-}-producer :: forall m a. (Monad m, Unbox a) => Producer m (Array a) a-producer =- Producer.translate A.unsafeThaw A.unsafeFreeze- $ MA.producerWith (return . unsafeInlineIO)---- | Unfold an array into a stream.----{-# INLINE_NORMAL reader #-}-reader :: forall m a. (Monad m, Unbox a) => Unfold m (Array a) a-reader = Producer.simplify producer---- | 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.------ /Pre-release/----{-# INLINE_NORMAL readerUnsafe #-}-readerUnsafe :: forall m a. (Monad m, Unbox a) => Unfold m (Array a) a-readerUnsafe = Unfold step inject- where-- inject (Array contents start end) =- return (ArrayUnsafe contents end start)-- {-# INLINE_LATE step #-}- step (ArrayUnsafe contents end p) = 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 = unsafeInlineIO $ peekWith contents p- let !p1 = INDEX_NEXT(p,a)- return $ D.Yield x (ArrayUnsafe contents end p1)---- |------ >>> import qualified Streamly.Internal.Data.Array.Type as Array--- >>> null arr = Array.byteLength arr == 0------ /Pre-release/-{-# INLINE null #-}-null :: Array a -> Bool-null arr = A.byteLength arr == 0---- | Like 'getIndex' but indexes the array in reverse from the end.------ /Pre-release/-{-# INLINE getIndexRev #-}-getIndexRev :: forall a. Unbox a => Int -> Array a -> Maybe a-getIndexRev i arr =- unsafeInlineIO- $ do- let elemPtr = RINDEX_OF(arrEnd arr, i, a)- if i >= 0 && elemPtr >= arrStart arr- then Just <$> peekWith (arrContents arr) elemPtr- else return Nothing---- |------ >>> import qualified Streamly.Internal.Data.Array as Array--- >>> last arr = Array.getIndexRev arr 0------ /Pre-release/-{-# INLINE last #-}-last :: Unbox a => Array a -> Maybe a-last = getIndexRev 0------------------------------------------------------------------------------------ Folds with Array as the container------------------------------------------------------------------------------------ | @writeLastN n@ folds a maximum of @n@ elements from the end of the input--- stream to an 'Array'.----{-# INLINE writeLastN #-}-writeLastN ::- (Storable a, Unbox a, MonadIO m) => Int -> Fold m a (Array a)-writeLastN n- | n <= 0 = fmap (const mempty) FL.drain- | otherwise = A.unsafeFreeze <$> Fold step initial done-- where-- step (Tuple3Fused' rb rh i) a = do- rh1 <- liftIO $ RB.unsafeInsert rb rh a- return $ FL.Partial $ Tuple3Fused' rb rh1 (i + 1)-- initial =- let f (a, b) = FL.Partial $ Tuple3Fused' a b (0 :: Int)- in fmap f $ liftIO $ RB.new n-- done (Tuple3Fused' rb rh i) = do- arr <- liftIO $ MA.newPinned n- foldFunc i rh snoc' arr rb-- -- XXX We should write a read unfold for ring.- snoc' b a = liftIO $ MA.snocUnsafe b a-- foldFunc i- | i < n = RB.unsafeFoldRingM- | otherwise = RB.unsafeFoldRingFullM------------------------------------------------------------------------------------ Random Access-------------------------------------------------------------------------------------------------------------------------------------------------------------------- Searching------------------------------------------------------------------------------------ | Given a sorted array, perform a binary search to find the given element.--- Returns the index of the element if found.------ /Unimplemented/-{-# INLINE binarySearch #-}-binarySearch :: a -> Array a -> Maybe Int-binarySearch = undefined---- find/findIndex etc can potentially be implemented more efficiently on arrays--- compared to streams by using SIMD instructions.--- We can also return a bit array instead.---- | Perform a linear search to find all the indices where a given element is--- present in an array.------ /Unimplemented/-findIndicesOf :: (a -> Bool) -> Unfold Identity (Array a) Int-findIndicesOf = undefined--{--findIndexOf :: (a -> Bool) -> Array a -> Maybe Int-findIndexOf p = Unfold.fold Fold.one . Stream.unfold (findIndicesOf p)--find :: (a -> Bool) -> Array a -> Bool-find = Unfold.fold Fold.null . Stream.unfold (findIndicesOf p)--}------------------------------------------------------------------------------------ Folds------------------------------------------------------------------------------------ XXX We can potentially use SIMD instructions on arrays to fold faster.------------------------------------------------------------------------------------ Slice------------------------------------------------------------------------------------ | /O(1)/ Slice an array in constant time.------ Caution: The bounds of the slice are not checked.------ /Unsafe/------ /Pre-release/-{-# INLINE getSliceUnsafe #-}-getSliceUnsafe ::- forall a. Unbox a- => Int -- ^ starting index- -> Int -- ^ length of the slice- -> Array a- -> Array a-getSliceUnsafe index len (Array contents start e) =- let size = SIZE_OF(a)- start1 = start + (index * size)- end1 = start1 + (len * size)- in assert (end1 <= e) (Array contents start1 end1)---- | Split the array into a stream of slices using a predicate. The element--- matching the predicate is dropped.------ /Pre-release/-{-# INLINE splitOn #-}-splitOn :: (Monad m, Unbox a) =>- (a -> Bool) -> Array a -> Stream m (Array a)-splitOn predicate arr =- fmap (\(i, len) -> getSliceUnsafe i len arr)- $ D.sliceOnSuffix predicate (A.toStreamD arr)--{-# INLINE genSlicesFromLen #-}-genSlicesFromLen :: forall m a. (Monad m, Unbox a)- => Int -- ^ from index- -> Int -- ^ length of the slice- -> Unfold m (Array a) (Int, Int)-genSlicesFromLen from len =- Unfold.lmap A.unsafeThaw (MA.genSlicesFromLen from len)---- | Generate a stream of slices of specified length from an array, starting--- from the supplied array index. The last slice may be shorter than the--- requested length.------ /Pre-release//-{-# INLINE getSlicesFromLen #-}-getSlicesFromLen :: forall m a. (Monad m, Unbox a)- => Int -- ^ from index- -> Int -- ^ length of the slice- -> Unfold m (Array a) (Array a)-getSlicesFromLen from len =- fmap A.unsafeFreeze- $ Unfold.lmap A.unsafeThaw (MA.getSlicesFromLen from len)------------------------------------------------------------------------------------ Random reads and writes------------------------------------------------------------------------------------ XXX Change this to a partial function instead of a Maybe type? And use--- MA.getIndex instead.------ | /O(1)/ Lookup the element at the given index. Index starts from 0.----{-# INLINE getIndex #-}-getIndex :: forall a. Unbox a => Int -> Array a -> Maybe a-getIndex i arr =- unsafeInlineIO- $ do- let elemPtr = INDEX_OF(arrStart arr, i, a)- if i >= 0 && INDEX_VALID(elemPtr, arrEnd arr, a)- then Just <$> peekWith (arrContents arr) elemPtr- else return Nothing---- | Given a stream of array indices, read the elements on those indices from--- the supplied Array. An exception is thrown if an index is out of bounds.------ This is the most general operation. We can implement other operations in--- terms of this:------ @--- read =--- let u = lmap (\arr -> (0, length arr - 1)) Unfold.enumerateFromTo--- in Unfold.lmap f (getIndices arr)------ readRev =--- let i = length arr - 1--- in Unfold.lmap f (getIndicesFromThenTo i (i - 1) 0)--- @------ /Pre-release/-{-# INLINE getIndices #-}-getIndices :: (Monad m, Unbox a) => Stream m Int -> Unfold m (Array a) a-getIndices m =- let unf = MA.getIndicesD (return . unsafeInlineIO) m- in Unfold.lmap A.unsafeThaw unf---- | Unfolds @(from, then, to, array)@ generating a finite stream whose first--- element is the array value from the index @from@ and the successive elements--- are from the indices in increments of @then@ up to @to@. Index enumeration--- can occur downwards or upwards depending on whether @then@ comes before or--- after @from@.------ @--- getIndicesFromThenTo =--- let f (from, next, to, arr) =--- (Stream.enumerateFromThenTo from next to, arr)--- in Unfold.lmap f getIndices--- @------ /Unimplemented/-{-# INLINE getIndicesFromThenTo #-}-getIndicesFromThenTo :: Unfold m (Int, Int, Int, Array a) a-getIndicesFromThenTo = undefined------------------------------------------------------------------------------------ Transform via stream operations------------------------------------------------------------------------------------ for non-length changing operations we can use the original length for--- allocation. If we can predict the length then we can use the prediction for--- new allocation. Otherwise we can use a hint and adjust dynamically.--{---- | Transform an array into another array using a pipe transformation--- operation.----{-# INLINE runPipe #-}-runPipe :: (MonadIO m, Unbox a, Unbox b)- => Pipe m a b -> Array a -> m (Array b)-runPipe f arr = P.runPipe (toArrayMinChunk (length arr)) $ f (A.read arr)--}---- XXX For transformations that cannot change the number of elements e.g. "map"--- we can use a predetermined array length.------ | Transform an array into another array using a stream transformation--- operation.------ /Pre-release/-{-# INLINE streamTransform #-}-streamTransform :: forall m a b. (MonadIO m, Unbox a, Unbox b)- => (Stream m a -> Stream m b) -> Array a -> m (Array b)-streamTransform f arr =- Stream.fold (A.writeWith (length arr)) $ f (A.read arr)------------------------------------------------------------------------------------ Casts------------------------------------------------------------------------------------ | Cast an array having elements of type @a@ into an array having elements of--- type @b@. The array size must be a multiple of the size of type @b@--- otherwise accessing the last element of the array may result into a crash or--- a random value.------ /Pre-release/----castUnsafe ::-#ifdef DEVBUILD- Unbox b =>-#endif- Array a -> Array b-castUnsafe (Array contents start end) =- Array contents start end---- | Cast an @Array a@ into an @Array Word8@.-------asBytes :: Array a -> Array Word8-asBytes = castUnsafe---- | Cast an array having elements of type @a@ into an array having elements of--- type @b@. The length of the array should be a multiple of the size of the--- target element otherwise 'Nothing' is returned.-------cast :: forall a b. (Unbox b) => Array a -> Maybe (Array b)-cast arr =- let len = A.byteLength arr- r = len `mod` SIZE_OF(b)- in if r /= 0- then Nothing- else Just $ castUnsafe arr---- | Convert an array of any type into a null terminated CString Ptr.------ /Unsafe/------ /O(n) Time: (creates a copy of the array)/------ /Pre-release/----asCStringUnsafe :: Array a -> (CString -> IO b) -> IO b-asCStringUnsafe arr act = do- -- XXX Ensure a pinned allocation here.- let arr1 = asBytes arr <> A.fromList [0]- asPtrUnsafe arr1 $ \ptr -> act (castPtr ptr)------------------------------------------------------------------------------------ Folds------------------------------------------------------------------------------------ XXX We can directly use toStreamD and D.fold here.---- | Fold an array using a 'Fold'.------ /Pre-release/-{-# INLINE fold #-}-fold :: forall m a b. (Monad m, Unbox a) => Fold m a b -> Array a -> m b-fold f arr = Stream.fold f (A.read arr)---- | Fold an array using a stream fold operation.------ /Pre-release/-{-# INLINE streamFold #-}-streamFold :: (Monad m, Unbox a) => (Stream m a -> m b) -> Array a -> m b-streamFold f arr = f (A.read arr)+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+module Streamly.Internal.Data.Array+ (+ -- * Setup+ -- $setup++ -- * Design Notes+ -- $design++ -- * The Array Type+ module Streamly.Internal.Data.Array.Type++ -- * Construction+ -- Monadic Folds+ , createOfLast++ -- * Random Access+ -- , getIndicesFrom -- read from a given position to the end of file+ -- , getIndicesUpto -- read from beginning up to the given position+ -- , getIndicesFromTo+ -- , getIndicesFromRev -- read from a given position to the beginning of file+ -- , getIndicesUptoRev -- read from end to the given position in file+ , indexReader+ , indexReaderFromThenTo++ -- * Search+ , binarySearch+ , findIndicesOf+ -- getIndicesOf+ , indexFinder -- see splitOn+ -- , findIndexOf+ -- , find++ -- * Casting+ , cast+ , asBytes+ , unsafeCast+ , asCStringUnsafe -- XXX asCString+ , asCWString++ -- * Subarrays+ -- , sliceOffLen+ , indexerFromLen+ , splitterFromLen++ -- * Streaming Operations+ , streamTransform++ -- * Folding+ , streamFold+ , foldM+ , foldRev++ -- * Stream of Arrays+ , concatSepBy+ , concatEndBy+ , concatEndBySeq++ , compactMax+ , compactMax'+ , compactSepByByte_+ , compactEndByByte_+ , compactEndByLn_++ -- * Parsing Stream of Arrays+ , foldBreakChunks -- Uses Stream, bad perf on break+ , foldChunks+ , foldBreak+ -- , parseBreakChunksK -- XXX uses Parser. parseBreak is better?+ , toParserK+ , parseBreak+ , parseBreakPos+ , parse+ , parsePos++ -- * Serialization+ , encodeAs+ , serialize+ , serialize'+ , deserialize++ -- * Deprecated+ , slicerFromLen+ , sliceIndexerFromLen+ , castUnsafe+ , getSliceUnsafe+ , pinnedSerialize+ , genSlicesFromLen+ , getSlicesFromLen+ , getIndices+ , writeLastN+ , interpose+ , interposeSuffix+ , intercalateSuffix+ , compactLE+ , pinnedCompactLE+ , compactOnByte+ , compactOnByteSuffix+ , splitOn+ , fold+ , foldBreakChunksK+ )+where++#include "assert.hs"+#include "deprecation.h"+#include "inline.hs"+#include "ArrayMacros.h"++import Control.Monad.IO.Class (MonadIO(..))+-- import Data.Bifunctor (first)+-- import Data.Either (fromRight)+import Data.Functor.Identity (Identity(..))+import Data.Proxy (Proxy(..))+import Data.Word (Word8)+import Foreign.C.String (CString, CWString)+import GHC.Types (SPEC(..))+import Streamly.Internal.Data.Unbox (Unbox(..))+import Prelude hiding (length, null, last, map, (!!), read, concat)++import Streamly.Internal.Data.MutByteArray.Type (PinnedState(..), MutByteArray)+import Streamly.Internal.Data.Serialize.Type (Serialize)+import Streamly.Internal.Data.Fold.Type (Fold(..))+import Streamly.Internal.Data.Parser (ParseError(..), ParseErrorPos(..))+import Streamly.Internal.Data.ParserK.Type+ (ParserK, ParseResult(..), Input(..), Step(..))+import Streamly.Internal.Data.Stream (Stream(..))+import Streamly.Internal.Data.StreamK.Type (StreamK)+import Streamly.Internal.Data.SVar.Type (adaptState, defState)+import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))+import Streamly.Internal.Data.Unfold.Type (Unfold(..))+import Streamly.Internal.System.IO (unsafeInlineIO)++import qualified Streamly.Internal.Data.Fold.Type as Fold+import qualified Streamly.Internal.Data.Serialize.Type as Serialize+import qualified Streamly.Internal.Data.MutByteArray.Type as MBA+import qualified Streamly.Internal.Data.MutArray as MA+import qualified Streamly.Internal.Data.RingArray as RB+import qualified Streamly.Internal.Data.ParserDrivers as Drivers+import qualified Streamly.Internal.Data.Parser.Type as ParserD+import qualified Streamly.Internal.Data.ParserK.Type as ParserK+import qualified Streamly.Internal.Data.Stream as D+import qualified Streamly.Internal.Data.Stream as Stream+import qualified Streamly.Internal.Data.StreamK.Type as StreamK+import qualified Streamly.Internal.Data.Unfold as Unfold++import Streamly.Internal.Data.Array.Type++#include "DocTestDataArray.hs"++-- $design+--+-- To summarize:+--+-- * Arrays are finite and fixed in size+-- * provide /O(1)/ access to elements+-- * store only data and not functions+-- * provide efficient IO interfacing+--+-- 'Foldable' instance is not provided because the implementation would be much+-- less efficient compared to folding via streams. 'Semigroup' and 'Monoid'+-- instances should be used with care; concatenating arrays using binary+-- operations can be highly inefficient. Instead, use+-- 'Streamly.Internal.Data.Stream.Chunked.toArray' to concatenate N+-- arrays at once.+--+-- Each array is one pointer visible to the GC. Too many small arrays (e.g.+-- single byte) are only as good as holding those elements in a Haskell list.+-- However, small arrays can be compacted into large ones to reduce the+-- overhead. To hold 32GB memory in 32k sized buffers we need 1 million arrays+-- if we use one array for each chunk. This is still significant to add+-- pressure to GC.++-------------------------------------------------------------------------------+-- Folds with Array as the container+-------------------------------------------------------------------------------++-- NOTE: We could possible write this in terms of "MutArray.createOfLast" but+-- this causes regression. This is probably because mapping inside "Fold.ifThen"+-- is more efficient than mapping over "Fold.ifTen".+--+-- | @createOfLast n@ folds a maximum of @n@ elements from the end of the input+-- stream to an 'Array'.+--+{-# INLINE createOfLast #-}+createOfLast ::+ (Unbox a, MonadIO m) => Int -> Fold m a (Array a)+createOfLast n = Fold.ifThen (pure (n <= 0)) (Fold.fromPure empty) lst++ where++ lst =+ let f = fmap unsafeFreeze . RB.toMutArray+ in Fold.rmapM f $ RB.createOfLast n++{-# DEPRECATED writeLastN "Please use createOfLast instead." #-}+{-# INLINE writeLastN #-}+writeLastN :: (Unbox a, MonadIO m) => Int -> Fold m a (Array a)+writeLastN = createOfLast++-------------------------------------------------------------------------------+-- Random Access+-------------------------------------------------------------------------------++-------------------------------------------------------------------------------+-- Searching+-------------------------------------------------------------------------------++-- | Given a sorted array, perform a binary search to find the given element.+-- Returns the index of the element if found.+--+-- /Unimplemented/+{-# INLINE binarySearch #-}+binarySearch :: a -> Array a -> Maybe Int+binarySearch = undefined++-- find/findIndex etc can potentially be implemented more efficiently on arrays+-- compared to streams by using SIMD instructions.+-- We can also return a bit array instead.++-- Can use SIMD.++-- | Perform a linear search to find all the indices where a given element is+-- present in an array.+--+-- /Unimplemented/+indexFinder :: (a -> Bool) -> Unfold Identity (Array a) Int+indexFinder = undefined++-- |+-- /Unimplemented/+findIndicesOf :: (a -> Bool) -> Array a -> Stream Identity Int+findIndicesOf p = Stream.unfold (indexFinder p)++{-+findIndexOf :: (a -> Bool) -> Array a -> Maybe Int+findIndexOf p = Unfold.fold Fold.one . Stream.unfold (indexFinder p)++find :: (a -> Bool) -> Array a -> Bool+find = Unfold.fold Fold.null . Stream.unfold (indexFinder p)+-}++-------------------------------------------------------------------------------+-- Folds+-------------------------------------------------------------------------------++-- XXX We can potentially use SIMD instructions on arrays to fold faster.++-------------------------------------------------------------------------------+-- Slice+-------------------------------------------------------------------------------++getSliceUnsafe ::+ forall a. Unbox a+ => Int -- ^ starting index+ -> Int -- ^ length of the slice+ -> Array a+ -> Array a+RENAME(getSliceUnsafe,unsafeSliceOffLen)++splitOn :: (Monad m, Unbox a) =>+ (a -> Bool) -> Array a -> Stream m (Array a)+RENAME(splitOn,splitEndBy_)++{-# INLINE indexerFromLen #-}+indexerFromLen, sliceIndexerFromLen :: forall m a. (Monad m, Unbox a)+ => Int -- ^ from index+ -> Int -- ^ length of the slice+ -> Unfold m (Array a) (Int, Int)+indexerFromLen from len =+ Unfold.lmap unsafeThaw (MA.indexerFromLen from len)+RENAME(sliceIndexerFromLen,indexerFromLen)++{-# DEPRECATED genSlicesFromLen "Please use indexerFromLen instead." #-}+{-# INLINE genSlicesFromLen #-}+genSlicesFromLen :: forall m a. (Monad m, Unbox a)+ => Int -- ^ from index+ -> Int -- ^ length of the slice+ -> Unfold m (Array a) (Int, Int)+genSlicesFromLen = indexerFromLen++-- | Generate a stream of slices of specified length from an array, starting+-- from the supplied array index. The last slice may be shorter than the+-- requested length.+--+-- /Pre-release//+{-# INLINE splitterFromLen #-}+splitterFromLen, slicerFromLen :: forall m a. (Monad m, Unbox a)+ => Int -- ^ from index+ -> Int -- ^ length of the slice+ -> Unfold m (Array a) (Array a)+splitterFromLen from len =+ fmap unsafeFreeze+ $ Unfold.lmap unsafeThaw (MA.splitterFromLen from len)+RENAME(slicerFromLen,splitterFromLen)++{-# DEPRECATED getSlicesFromLen "Please use splitterFromLen instead." #-}+{-# INLINE getSlicesFromLen #-}+getSlicesFromLen :: forall m a. (Monad m, Unbox a)+ => Int -- ^ from index+ -> Int -- ^ length of the slice+ -> Unfold m (Array a) (Array a)+getSlicesFromLen = splitterFromLen++-------------------------------------------------------------------------------+-- Random reads and writes+-------------------------------------------------------------------------------++-- | Given a stream of array indices, read the elements on those indices from+-- the supplied Array. An exception is thrown if an index is out of bounds.+--+-- This is the most general operation. We can implement other operations in+-- terms of this:+--+-- @+-- read =+-- let u = lmap (\arr -> (0, length arr - 1)) Unfold.enumerateFromTo+-- in Unfold.lmap f (indexReader arr)+--+-- readRev =+-- let i = length arr - 1+-- in Unfold.lmap f (indexReaderFromThenTo i (i - 1) 0)+-- @+--+-- /Pre-release/+{-# INLINE indexReader #-}+indexReader :: (Monad m, Unbox a) => Stream m Int -> Unfold m (Array a) a+indexReader m =+ let unf = MA.indexReaderWith (return . unsafeInlineIO) m+ in Unfold.lmap unsafeThaw unf++-- XXX DO NOT REMOVE, change the signature to use Stream instead of unfold+{-# DEPRECATED getIndices "Please use getIndices instead." #-}+{-# INLINE getIndices #-}+getIndices :: (Monad m, Unbox a) => Stream m Int -> Unfold m (Array a) a+getIndices = indexReader++-- | Unfolds @(from, then, to, array)@ generating a finite stream whose first+-- element is the array value from the index @from@ and the successive elements+-- are from the indices in increments of @then@ up to @to@. Index enumeration+-- can occur downwards or upwards depending on whether @then@ comes before or+-- after @from@.+--+-- @+-- getIndicesFromThenTo =+-- let f (from, next, to, arr) =+-- (Stream.enumerateFromThenTo from next to, arr)+-- in Unfold.lmap f getIndices+-- @+--+-- /Unimplemented/+{-# INLINE indexReaderFromThenTo #-}+indexReaderFromThenTo :: Unfold m (Int, Int, Int, Array a) a+indexReaderFromThenTo = undefined++-------------------------------------------------------------------------------+-- Transform via stream operations+-------------------------------------------------------------------------------++-- for non-length changing operations we can use the original length for+-- allocation. If we can predict the length then we can use the prediction for+-- new allocation. Otherwise we can use a hint and adjust dynamically.++{-+-- | Transform an array into another array using a pipe transformation+-- operation.+--+{-# INLINE runPipe #-}+runPipe :: (MonadIO m, Unbox a, Unbox b)+ => Pipe m a b -> Array a -> m (Array b)+runPipe f arr = P.runPipe (toArrayMinChunk (length arr)) $ f (read arr)+-}++-- XXX For transformations that cannot change the number of elements e.g. "map"+-- we can use a predetermined array length.+--+-- | Transform an array into another array using a stream transformation+-- operation.+--+-- /Pre-release/+{-# INLINE streamTransform #-}+streamTransform :: forall m a b. (MonadIO m, Unbox a, Unbox b)+ => (Stream m a -> Stream m b) -> Array a -> m (Array b)+streamTransform f arr =+ Stream.fold (createWith (length arr)) $ f (read arr)++-------------------------------------------------------------------------------+-- Casts+-------------------------------------------------------------------------------++-- | Cast an array having elements of type @a@ into an array having elements of+-- type @b@. The array size must be a multiple of the size of type @b@+-- otherwise accessing the last element of the array may result into a crash or+-- a random value.+--+-- /Pre-release/+--+unsafeCast, castUnsafe ::+#ifdef DEVBUILD+ Unbox b =>+#endif+ Array a -> Array b+unsafeCast (Array contents start end) =+ Array contents start end+RENAME(castUnsafe,unsafeCast)++-- | Cast an @Array a@ into an @Array Word8@.+--+--+asBytes :: Array a -> Array Word8+asBytes = unsafeCast++-- | Cast an array having elements of type @a@ into an array having elements of+-- type @b@. The length of the array should be a multiple of the size of the+-- target element otherwise 'Nothing' is returned.+--+--+cast :: forall a b. (Unbox b) => Array a -> Maybe (Array b)+cast arr =+ let len = byteLength arr+ r = len `mod` SIZE_OF(b)+ in if r /= 0+ then Nothing+ else Just $ unsafeCast arr++-- | Convert an array of any element type into a null terminated CString Ptr.+-- The array is copied to pinned memory.+--+-- /Unsafe/+--+-- /O(n) Time: (creates a copy of the array)/+--+-- /Pre-release/+--+asCStringUnsafe :: Array a -> (CString -> IO b) -> IO b+asCStringUnsafe arr = MA.asCString (unsafeThaw arr)++-- | Convert an array of any element type into a null terminated CWString Ptr.+-- The array is copied to pinned memory.+--+-- /Unsafe/+--+-- /O(n) Time: (creates a copy of the array)/+--+-- /Pre-release/+--+asCWString :: Array a -> (CWString -> IO b) -> IO b+asCWString arr = MA.asCWString (unsafeThaw arr)++-------------------------------------------------------------------------------+-- Folds+-------------------------------------------------------------------------------++-- XXX Use runIdentity for pure fold+-- XXX Rename fold to foldM, we can then use "fold" for pure folds.+-- XXX We do not need an INLINE on fold?++-- | Fold an array using a 'Fold'.+--+-- /Pre-release/+{-# INLINE foldM #-}+fold, foldM :: (Monad m, Unbox a) => Fold m a b -> Array a -> m b+foldM f arr = Stream.fold f (read arr)+RENAME(fold,foldM)++foldRev :: Unbox a => Fold.Fold Identity a b -> Array a -> b+foldRev f arr = runIdentity $ Stream.fold f (readRev arr)++-- | Fold an array using a stream fold operation.+--+-- /Pre-release/+{-# INLINE streamFold #-}+streamFold :: (Monad m, Unbox a) => (Stream m a -> m b) -> Array a -> m b+streamFold f arr = f (read arr)++--------------------------------------------------------------------------------+-- Serialization+--------------------------------------------------------------------------------++{-# INLINE encodeAs #-}+encodeAs :: forall a. Serialize a => PinnedState -> a -> Array Word8+encodeAs ps a =+ unsafeInlineIO $ do+ let len = Serialize.addSizeTo 0 a+ mbarr <- MBA.newAs ps len+ off <- Serialize.serializeAt 0 mbarr a+ assertM(len == off)+ pure $ Array mbarr 0 off++-- |+-- Properties:+-- 1. Identity: @deserialize . serialize == id@+-- 2. Encoded equivalence: @serialize a == serialize a@+{-# INLINE serialize #-}+serialize :: Serialize a => a -> Array Word8+serialize = encodeAs Unpinned++-- | Serialize a Haskell type to a pinned byte array. The array is allocated+-- using pinned memory so that it can be used directly in OS APIs for writing+-- to file or sending over the network.+--+-- Properties:+--+-- 1. Identity: @deserialize . serialize' == id@+-- 2. Encoded equivalence: @serialize' a == serialize' a@+{-# INLINE serialize' #-}+pinnedSerialize, serialize' :: Serialize a => a -> Array Word8+serialize' = encodeAs Pinned+RENAME_PRIME(pinnedSerialize,serialize)++-- XXX We can deserialize it like MutArray, returning the remaining slice.++-- | Decode a Haskell type from a byte array containing its serialized+-- representation.+{-# INLINE deserialize #-}+deserialize :: Serialize a => Array Word8 -> (a, Array Word8)+deserialize arr =+ let (a, b) = unsafeInlineIO $ MA.deserialize (unsafeThaw arr)+ in (a, unsafeFreeze b)++-------------------------------------------------------------------------------+-- Streams of Arrays+-------------------------------------------------------------------------------++-- TODO: efficiently compare two streams of arrays. Two streams can have chunks+-- of different sizes, we can handle that in the stream comparison abstraction.+-- This could be useful e.g. to fast compare whether two files differ.++-- | Insert the given element between arrays and flatten.+--+-- >>> concatSepBy x = Stream.unfoldEachSepBy x Array.reader+--+{-# INLINE concatSepBy #-}+concatSepBy, interpose :: (Monad m, Unbox a) =>+ a -> Stream m (Array a) -> Stream m a+concatSepBy x = D.unfoldEachSepBy x reader++RENAME(interpose,concatSepBy)++data FlattenState s =+ OuterLoop s+ | InnerLoop s !MutByteArray !Int !Int++-- | Insert the given element after each array and flatten. This is similar to+-- unlines.+--+-- >>> concatEndBy x = Stream.unfoldEachEndBy x Array.reader+--+{-# INLINE_NORMAL concatEndBy #-}+concatEndBy, interposeSuffix :: forall m a. (Monad m, Unbox a)+ => a -> Stream m (Array a) -> Stream m a+-- concatEndBy x = D.unfoldEachEndBy x reader+concatEndBy sep (D.Stream step state) = D.Stream step' (OuterLoop state)++ where++ {-# INLINE_LATE step' #-}+ step' gst (OuterLoop st) = do+ r <- step (adaptState gst) st+ return $ case r of+ D.Yield Array{..} s ->+ D.Skip (InnerLoop s arrContents arrStart arrEnd)+ D.Skip s -> D.Skip (OuterLoop s)+ D.Stop -> D.Stop++ step' _ (InnerLoop st _ p end) | p == end =+ return $ D.Yield sep $ OuterLoop st++ step' _ (InnerLoop st contents p end) = do+ let !x = unsafeInlineIO $ peekAt p contents+ return $ D.Yield x (InnerLoop st contents (INDEX_NEXT(p,a)) end)++RENAME(interposeSuffix,concatEndBy)++-- | Insert the given array after each array and flatten.+--+-- >>> concatEndBySeq x = Stream.unfoldEachEndBySeq x Array.reader+--+{-# INLINE concatEndBySeq #-}+concatEndBySeq, intercalateSuffix :: (Monad m, Unbox a)+ => Array a -> Stream m (Array a) -> Stream m a+concatEndBySeq x = D.unfoldEachEndBySeq x reader++RENAME(intercalateSuffix,concatEndBySeq)++-- | @compactMax n@ coalesces adjacent arrays in the input stream+-- only if the combined size would be less than or equal to n.+--+-- Generates unpinned arrays irrespective of the pinning status of input+-- arrays.+{-# INLINE_NORMAL compactMax #-}+compactMax, compactLE :: (MonadIO m, Unbox a)+ => Int -> Stream m (Array a) -> Stream m (Array a)+compactMax n stream =+ D.map unsafeFreeze $ MA.compactMax n $ D.map unsafeThaw stream++RENAME(compactLE,compactMax)++-- | Like 'compactMax' but generates pinned arrays.+{-# INLINE_NORMAL compactMax' #-}+compactMax', pinnedCompactLE :: (MonadIO m, Unbox a)+ => Int -> Stream m (Array a) -> Stream m (Array a)+compactMax' n stream =+ D.map unsafeFreeze $ MA.compactMax' n $ D.map unsafeThaw stream++{-# DEPRECATED pinnedCompactLE "Please use compactMax' instead." #-}+{-# INLINE pinnedCompactLE #-}+pinnedCompactLE = compactMax'++-- | Split a stream of byte arrays on a given separator byte, dropping the+-- separator and coalescing all the arrays between two separators into a single+-- array.+--+{-# INLINE compactSepByByte_ #-}+compactSepByByte_, compactOnByte+ :: (MonadIO m)+ => Word8+ -> Stream m (Array Word8)+ -> Stream m (Array Word8)+compactSepByByte_ byte =+ fmap unsafeFreeze . MA.compactSepByByte_ byte . fmap unsafeThaw++RENAME(compactOnByte,compactSepByByte_)++-- | Like 'compactSepByByte_', but considers the separator in suffix position+-- instead of infix position.+{-# INLINE compactEndByByte_ #-}+compactEndByByte_, compactOnByteSuffix+ :: (MonadIO m)+ => Word8+ -> Stream m (Array Word8)+ -> Stream m (Array Word8)+compactEndByByte_ byte =+ fmap unsafeFreeze . MA.compactEndByByte_ byte . fmap unsafeThaw+-- compactEndByByte_ byte = chunksEndBy_ (== byte) . concat++RENAME(compactOnByteSuffix,compactEndByByte_)++-- XXX On windows we should compact on "\r\n". We can just compact on '\n' and+-- drop the last byte in each array if it is '\r'.++-- | Compact byte arrays on newline character, dropping the newline char.+{-# INLINE compactEndByLn_ #-}+compactEndByLn_ :: MonadIO m+ => Stream m (Array Word8)+ -> Stream m (Array Word8)+compactEndByLn_ = compactEndByByte_ 10++-------------------------------------------------------------------------------+-- Folding Streams of Arrays+-------------------------------------------------------------------------------++-- XXX This should not be used for breaking a stream as the D.cons used in+-- reconstructing the stream could be very bad for performance. This can only+-- be useful in folding without breaking.+{-# INLINE_NORMAL foldBreakChunks #-}+foldBreakChunks :: forall m a b. (MonadIO m, Unbox a) =>+ Fold m a b -> Stream m (Array a) -> m (b, Stream m (Array a))+foldBreakChunks (Fold fstep initial _ final) stream@(Stream step state) = do+ res <- initial+ case res of+ Fold.Partial fs -> go SPEC state fs+ Fold.Done fb -> return $! (fb, stream)++ where++ {-# INLINE go #-}+ go !_ st !fs = do+ r <- step defState st+ case r of+ Stream.Yield (Array contents start end) s ->+ let fp = Tuple' end contents+ in goArray SPEC s fp start fs+ Stream.Skip s -> go SPEC s fs+ Stream.Stop -> do+ b <- final fs+ return (b, D.nil)++ goArray !_ s (Tuple' end _) !cur !fs+ | cur == end = do+ go SPEC s fs+ goArray !_ st fp@(Tuple' end contents) !cur !fs = do+ x <- liftIO $ peekAt cur contents+ res <- fstep fs x+ let next = INDEX_NEXT(cur,a)+ case res of+ Fold.Done b -> do+ let arr = Array contents next end+ return $! (b, D.cons arr (D.Stream step st))+ Fold.Partial fs1 -> goArray SPEC st fp next fs1++-- This may be more robust wrt fusion compared to unfoldMany?++-- | Fold a stream of arrays using a 'Fold'. This is equivalent to the+-- following:+--+-- >>> foldChunks f = Stream.fold f . Stream.unfoldEach Array.reader+--+foldChunks :: (MonadIO m, Unbox a) => Fold m a b -> Stream m (Array a) -> m b+foldChunks f s = fmap fst (foldBreakChunks f s)+-- foldStream f = Stream.fold f . Stream.unfoldEach reader++-- | Fold a stream of arrays using a 'Fold' and return the remaining stream.+--+-- The following alternative to this function allows composing the fold using+-- the parser Monad:+--+-- @+-- foldBreakStreamK f s =+-- fmap (first (fromRight undefined))+-- $ StreamK.parseBreakChunks (ParserK.adaptC (Parser.fromFold f)) s+-- @+--+-- We can compare perf and remove this one or define it in terms of that.+--+foldBreak, foldBreakChunksK :: forall m a b. (MonadIO m, Unbox a) =>+ Fold m a b -> StreamK m (Array a) -> m (b, StreamK m (Array a))+{-+foldBreakChunksK f s =+ fmap (first (fromRight undefined))+ $ StreamK.parseBreakChunks (ParserK.adaptC (Parser.fromFold f)) s+-}+foldBreak (Fold fstep initial _ final) stream = do+ res <- initial+ case res of+ Fold.Partial fs -> go fs stream+ Fold.Done fb -> return (fb, stream)++ where++ {-# INLINE go #-}+ go !fs st = do+ let stop = (, StreamK.nil) <$> final fs+ single a = yieldk a StreamK.nil+ yieldk (Array contents start end) r =+ let fp = Tuple' end contents+ in goArray fs r fp start+ in StreamK.foldStream defState yieldk single stop st++ goArray !fs st (Tuple' end _) !cur+ | cur == end = do+ go fs st+ goArray !fs st fp@(Tuple' end contents) !cur = do+ x <- liftIO $ peekAt cur contents+ res <- fstep fs x+ let next = INDEX_NEXT(cur,a)+ case res of+ Fold.Done b -> do+ let arr = Array contents next end+ return $! (b, StreamK.cons arr st)+ Fold.Partial fs1 -> goArray fs1 st fp next++RENAME(foldBreakChunksK,foldBreak)++{-+-- This can be generalized to any type provided it can be unfolded to a stream+-- and it can be combined using a semigroup operation.+--+{-# INLINE_NORMAL parseBreakD #-}+parseBreakD ::+ forall m a b. (MonadIO m, MonadThrow m, Unbox a)+ => PRD.Parser a m b+ -> D.Stream m (Array.Array a)+ -> m (b, D.Stream m (Array.Array a))+parseBreakD+ (PRD.Parser pstep initial extract) stream@(D.Stream step state) = do++ res <- initial+ case res of+ PRD.IPartial s -> go SPEC state (List []) s+ PRD.IDone b -> return (b, stream)+ PRD.IError err -> throwM $ ParseError err++ where++ -- "backBuf" contains last few items in the stream that we may have to+ -- backtrack to.+ --+ -- XXX currently we are using a dumb list based approach for backtracking+ -- buffer. This can be replaced by a sliding/ring buffer using Data.Array.+ -- That will allow us more efficient random back and forth movement.+ go !_ st backBuf !pst = do+ r <- step defState st+ case r of+ D.Yield (Array contents start end) s ->+ gobuf SPEC s backBuf+ (Tuple' end contents) start pst+ D.Skip s -> go SPEC s backBuf pst+ D.Stop -> do+ b <- extract pst+ return (b, D.nil)++ -- Use strictness on "cur" to keep it unboxed+ gobuf !_ s backBuf (Tuple' end _) !cur !pst+ | cur == end = do+ go SPEC s backBuf pst+ gobuf !_ s backBuf fp@(Tuple' end contents) !cur !pst = do+ x <- liftIO $ peekByteIndex contents cur+ pRes <- pstep pst x+ let next = INDEX_NEXT(cur,a)+ case pRes of+ PR.Partial 0 pst1 ->+ gobuf SPEC s (List []) fp next pst1+ PR.Partial n pst1 -> do+ assert (n <= Prelude.length (x:getList backBuf)) (return ())+ let src0 = Prelude.take n (x:getList backBuf)+ arr0 = A.fromListN n (Prelude.reverse src0)+ arr1 = Array contents next end+ src = arr0 <> arr1+ let !(Array cont1 start end1) = src+ fp1 = Tuple' end1 cont1+ gobuf SPEC s (List []) fp1 start pst1+ PR.Continue 0 pst1 ->+ gobuf SPEC s (List (x:getList backBuf)) fp next pst1+ PR.Continue n pst1 -> do+ assert (n <= Prelude.length (x:getList backBuf)) (return ())+ let (src0, buf1) = splitAt n (x:getList backBuf)+ arr0 = A.fromListN n (Prelude.reverse src0)+ arr1 = Array contents next end+ src = arr0 <> arr1+ let !(Array cont1 start end1) = src+ fp1 = Tuple' end1 cont1+ gobuf SPEC s (List buf1) fp1 start pst1+ PR.Done 0 b -> do+ let arr = Array contents next end+ return (b, D.cons arr (D.Stream step s))+ PR.Done n b -> do+ assert (n <= Prelude.length (x:getList backBuf)) (return ())+ let src0 = Prelude.take n (x:getList backBuf)+ -- XXX create the array in reverse instead+ arr0 = A.fromListN n (Prelude.reverse src0)+ arr1 = Array contents next end+ -- XXX Use StreamK to avoid adding arbitrary layers of+ -- constructors every time.+ str = D.cons arr0 (D.cons arr1 (D.Stream step s))+ return (b, str)+ PR.SError err -> throwM $ ParseError err++-- | Parse an array stream using the supplied 'Parser'. Returns the parse+-- result and the unconsumed stream. Throws 'ParseError' if the parse fails.+--+-- 'parseBreak' is an alternative to this function which allows composing the+-- parser using the parser Monad.+--+-- We can compare perf and remove this one or define it in terms of that.+--+-- /Internal/+--+{-# INLINE_NORMAL parseBreakChunksK #-}+parseBreakChunksK ::+ forall m a b. (MonadIO m, Unbox a)+ => Parser a m b+ -> StreamK m (Array a)+ -> m (Either ParseError b, StreamK m (Array a))+parseBreakChunksK (Parser pstep initial extract) stream = do+ res <- initial+ case res of+ IPartial s -> go s stream []+ IDone b -> return (Right b, stream)+ IError err -> return (Left (ParseError err), stream)++ where++ -- "backBuf" contains last few items in the stream that we may have to+ -- backtrack to.+ --+ -- XXX currently we are using a dumb list based approach for backtracking+ -- buffer. This can be replaced by a sliding/ring buffer using Data.Array.+ -- That will allow us more efficient random back and forth movement.+ go !pst st backBuf = do+ let stop = goStop pst backBuf -- (, K.nil) <$> extract pst+ single a = yieldk a StreamK.nil+ yieldk arr r = goArray pst backBuf r arr+ in StreamK.foldStream defState yieldk single stop st++ -- Use strictness on "cur" to keep it unboxed+ goArray !pst backBuf st (Array _ cur end) | cur == end = go pst st backBuf+ goArray !pst backBuf st (Array contents cur end) = do+ x <- liftIO $ peekAt cur contents+ pRes <- pstep pst x+ let next = INDEX_NEXT(cur,a)+ case pRes of+ Parser.SPartial 1 s ->+ goArray s [] st (Array contents next end)+ Parser.SPartial m s -> do+ assertM(m <= 1)+ let n = 1 - m+ assert (n <= Prelude.length (x:backBuf)) (return ())+ let src0 = Prelude.take n (x:backBuf)+ arr0 = fromListN n (Prelude.reverse src0)+ arr1 = Array contents next end+ src = arr0 <> arr1+ goArray s [] st src+ Parser.SContinue 1 s ->+ goArray s (x:backBuf) st (Array contents next end)+ Parser.SContinue m s -> do+ assertM(m <= 1)+ let n = 1 - m+ assert (n <= Prelude.length (x:backBuf)) (return ())+ let (src0, buf1) = Prelude.splitAt n (x:backBuf)+ arr0 = fromListN n (Prelude.reverse src0)+ arr1 = Array contents next end+ src = arr0 <> arr1+ goArray s buf1 st src+ Parser.SDone 1 b -> do+ let arr = Array contents next end+ return (Right b, StreamK.cons arr st)+ Parser.SDone m b -> do+ assertM(m <= 1)+ let n = 1 - m+ assert (n <= Prelude.length (x:backBuf)) (return ())+ let src0 = Prelude.take n (x:backBuf)+ -- XXX Use fromListRevN once implemented+ -- arr0 = A.fromListRevN n src0+ arr0 = fromListN n (Prelude.reverse src0)+ arr1 = Array contents next end+ str = StreamK.cons arr0 (StreamK.cons arr1 st)+ return (Right b, str)+ Parser.SError err -> do+ let n = Prelude.length backBuf+ arr0 = fromListN n (Prelude.reverse backBuf)+ arr1 = Array contents cur end+ str = StreamK.cons arr0 (StreamK.cons arr1 st)+ return (Left (ParseError err), str)++ -- This is a simplified goArray+ goExtract !pst backBuf (Array _ cur end)+ | cur == end = goStop pst backBuf+ goExtract !pst backBuf (Array contents cur end) = do+ x <- liftIO $ peekAt cur contents+ pRes <- pstep pst x+ let next = INDEX_NEXT(cur,a)+ case pRes of+ Parser.SPartial 0 s ->+ goExtract s [] (Array contents next end)+ Parser.SPartial m s -> do+ assertM(m <= 0)+ let n = negate m+ assert (n <= Prelude.length (x:backBuf)) (return ())+ let src0 = Prelude.take n (x:backBuf)+ arr0 = fromListN n (Prelude.reverse src0)+ arr1 = Array contents next end+ src = arr0 <> arr1+ goExtract s [] src+ Parser.SContinue 0 s ->+ goExtract s backBuf (Array contents next end)+ Parser.SContinue m s -> do+ assertM(m <= 0)+ let n = negate m+ assert (n <= Prelude.length (x:backBuf)) (return ())+ let (src0, buf1) = Prelude.splitAt n (x:backBuf)+ arr0 = fromListN n (Prelude.reverse src0)+ arr1 = Array contents next end+ src = arr0 <> arr1+ goExtract s buf1 src+ Parser.SDone 0 b -> do+ let arr = Array contents next end+ return (Right b, StreamK.fromPure arr)+ Parser.SDone m b -> do+ assertM(m <= 0)+ let n = negate m+ assert (n <= Prelude.length backBuf) (return ())+ let src0 = Prelude.take n (x:backBuf)+ -- XXX Use fromListRevN once implemented+ -- arr0 = A.fromListRevN n src0+ arr0 = fromListN n (Prelude.reverse src0)+ arr1 = Array contents next end+ str = StreamK.cons arr0 (StreamK.fromPure arr1)+ return (Right b, str)+ Parser.SError err -> do+ let n = Prelude.length backBuf+ arr0 = fromListN n (Prelude.reverse backBuf)+ arr1 = Array contents cur end+ str = StreamK.cons arr0 (StreamK.fromPure arr1)+ return (Left (ParseError err), str)++ -- This is a simplified goExtract+ {-# INLINE goStop #-}+ goStop !pst backBuf = do+ pRes <- extract pst+ case pRes of+ Parser.SPartial _ _ -> error "Bug: parseBreak: Partial in extract"+ Parser.SContinue 0 s ->+ goStop s backBuf+ Parser.SContinue m s -> do+ assertM(m <= 0)+ let n = negate m+ assert (n <= Prelude.length backBuf) (return ())+ let (src0, buf1) = Prelude.splitAt n backBuf+ arr = fromListN n (Prelude.reverse src0)+ goExtract s buf1 arr+ Parser.SDone 0 b ->+ return (Right b, StreamK.nil)+ Parser.SDone m b -> do+ assertM(m <= 0)+ let n = negate m+ assert (n <= Prelude.length backBuf) (return ())+ let src0 = Prelude.take n backBuf+ -- XXX Use fromListRevN once implemented+ -- arr0 = A.fromListRevN n src0+ arr0 = fromListN n (Prelude.reverse src0)+ return (Right b, StreamK.fromPure arr0)+ Parser.SError err -> do+ let n = Prelude.length backBuf+ arr0 = fromListN n (Prelude.reverse backBuf)+ return (Left (ParseError err), StreamK.fromPure arr0)+-}++-- | Run a 'ParserK' over a 'StreamK' of Arrays and return the parse result and+-- the remaining Stream.+{-# INLINE parseBreak #-}+parseBreak+ :: (Monad m, Unbox a)+ => ParserK (Array a) m b+ -> StreamK m (Array a)+ -> m (Either ParseError b, StreamK m (Array a))+parseBreak = Drivers.parseBreakChunks++-- | Like 'parseBreak' but includes stream position information in the error+-- messages.+--+{-# INLINE parseBreakPos #-}+parseBreakPos+ :: (Monad m, Unbox a)+ => ParserK (Array a) m b+ -> StreamK m (Array a)+ -> m (Either ParseErrorPos b, StreamK m (Array a))+parseBreakPos = Drivers.parseBreakChunksPos++{-# INLINE parse #-}+parse :: (Monad m, Unbox a) =>+ ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b)+parse f = fmap fst . parseBreak f++-- | Like 'parse' but includes stream position information in the error+-- messages.+--+{-# INLINE parsePos #-}+parsePos :: (Monad m, Unbox a) =>+ ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseErrorPos b)+parsePos f = fmap fst . parseBreakPos f++-------------------------------------------------------------------------------+-- Convert ParserD to ParserK+-------------------------------------------------------------------------------++{-# INLINE adaptCWith #-}+adaptCWith+ :: forall m a s b r. (Monad m, Unbox a)+ => (s -> a -> m (ParserD.Step s b))+ -> m (ParserD.Initial s b)+ -> (s -> m (ParserD.Final s b))+ -> (ParseResult b -> Int -> Input (Array a) -> m (Step (Array a) m r))+ -> Int+ -> Int+ -> Input (Array a)+ -> m (Step (Array a) m r)+adaptCWith pstep initial extract cont !offset0 !usedCount !input = do+ res <- initial+ case res of+ ParserD.IPartial pst -> do+ case input of+ Chunk arr -> parseContChunk usedCount offset0 pst arr+ None -> parseContNothing usedCount pst+ ParserD.IDone b -> cont (Success offset0 b) usedCount input+ ParserD.IError err -> cont (Failure offset0 err) usedCount input++ where++ -- XXX We can maintain an absolute position instead of relative that will+ -- help in reporting of error location in the stream.+ {-# NOINLINE parseContChunk #-}+ parseContChunk !count !offset !state arr@(Array contents start end) = do+ if offset >= 0+ then go SPEC (start + offset * SIZE_OF(a)) state+ else return $ Continue offset (parseCont count state)++ where++ {-# INLINE onDone #-}+ onDone n b =+ assert (n <= length arr)+ (cont (Success n b) (count + n - offset) (Chunk arr))++ {-# INLINE callParseCont #-}+ callParseCont constr n pst1 =+ assert (n < 0 || n >= length arr)+ (return $ constr n (parseCont (count + n - offset) pst1))++ {-# INLINE onPartial #-}+ onPartial = callParseCont Partial++ {-# INLINE onContinue #-}+ onContinue = callParseCont Continue++ {-# INLINE onError #-}+ onError n err =+ cont (Failure n err) (count + n - offset) (Chunk arr)++ {-# INLINE onBack #-}+ onBack offset1 elemSize constr pst = do+ let pos = offset1 - start+ in if pos >= 0+ then go SPEC offset1 pst+ else constr (pos `div` elemSize) pst++ -- Note: div may be expensive but the alternative is to maintain an element+ -- offset in addition to a byte offset or just the element offset and use+ -- multiplication to get the byte offset every time, both these options+ -- turned out to be more expensive than using div.+ go !_ !cur !pst | cur >= end =+ onContinue ((end - start) `div` SIZE_OF(a)) pst+ go !_ !cur !pst = do+ let !x = unsafeInlineIO $ peekAt cur contents+ pRes <- pstep pst x+ let elemSize = SIZE_OF(a)+ next = INDEX_NEXT(cur,a)+ move n = cur + n * elemSize+ curOff = (cur - start) `div` elemSize+ nextOff = (next - start) `div` elemSize+ case pRes of+ ParserD.SDone 1 b ->+ onDone nextOff b+ ParserD.SDone 0 b ->+ onDone curOff b+ ParserD.SDone n b ->+ onDone ((move n - start) `div` elemSize) b+ ParserD.SPartial 1 pst1 ->+ go SPEC next pst1+ ParserD.SPartial 0 pst1 ->+ go SPEC cur pst1+ ParserD.SPartial n pst1 ->+ onBack (move n) elemSize onPartial pst1+ ParserD.SContinue 1 pst1 ->+ go SPEC next pst1+ ParserD.SContinue 0 pst1 ->+ go SPEC cur pst1+ ParserD.SContinue n pst1 ->+ onBack (move n) elemSize onContinue pst1+ ParserD.SError err ->+ onError curOff err++ {-# NOINLINE parseContNothing #-}+ parseContNothing !count !pst = do+ r <- extract pst+ case r of+ ParserD.FDone n b ->+ assert (n <= 0) (cont (Success n b) (count + n) None)+ ParserD.FContinue n pst1 ->+ assert (n <= 0)+ (return $ Continue n (parseCont (count + n) pst1))+ ParserD.FError err ->+ -- XXX It is called only when there is no input arr. So using 0+ -- as the position is correct?+ cont (Failure 0 err) count None++ -- XXX Maybe we can use two separate continuations instead of using+ -- Just/Nothing cases here. That may help in avoiding the parseContJust+ -- function call.+ {-# INLINE parseCont #-}+ parseCont !cnt !pst (Chunk arr) = parseContChunk cnt 0 pst arr+ parseCont !cnt !pst None = parseContNothing cnt pst++-- | Convert a 'Parser' to 'ParserK' working on an Array stream.+--+-- /Pre-release/+--+{-# INLINE_LATE toParserK #-}+toParserK :: (Monad m, Unbox a) => ParserD.Parser a m b -> ParserK (Array a) m b+toParserK (ParserD.Parser step initial extract) =+ ParserK.MkParser $ adaptCWith step initial extract
src/Streamly/Internal/Data/Array/Generic.hs view
@@ -4,278 +4,64 @@ -- -- License : BSD-3-Clause -- Maintainer : streamly@composewell.com--- Stability : pre-release -- Portability : GHC -- module Streamly.Internal.Data.Array.Generic- ( Array(..)-- -- * Construction- , nil- , writeN- , write- , writeWith- , writeLastN-- , fromStreamN- , fromStream-- , fromListN- , fromList-- -- * Elimination- , length- , reader-- , toList- , read- , readRev-- , foldl'- , foldr- , streamFold- , fold+ (+ module Streamly.Internal.Data.Array.Generic.Type - -- * Random Access- , getIndexUnsafe- , getSliceUnsafe- , strip+ -- * Parsing Stream of Arrays+ , parse+ , parsePos+ , parseBreak+ , parseBreakPos ) where -#include "inline.hs"--import Control.Monad (replicateM)-import Control.Monad.IO.Class (MonadIO)-import GHC.Base (MutableArray#, RealWorld)-import GHC.IO (unsafePerformIO)-import Text.Read (readPrec)--import Streamly.Internal.Data.Fold.Type (Fold(..))-import Streamly.Internal.Data.Stream.StreamD.Type (Stream)-import Streamly.Internal.Data.Unfold.Type (Unfold(..))-import Streamly.Internal.System.IO (unsafeInlineIO)--import qualified Streamly.Internal.Data.Array.Generic.Mut.Type as MArray-import qualified Streamly.Internal.Data.Fold.Type as FL-import qualified Streamly.Internal.Data.Producer.Type as Producer-import qualified Streamly.Internal.Data.Producer as Producer-import qualified Streamly.Internal.Data.Ring as RB-import qualified Streamly.Internal.Data.Stream.StreamD.Type as D-import qualified Streamly.Internal.Data.Stream.StreamD.Generate as D-import qualified Text.ParserCombinators.ReadPrec as ReadPrec--import Prelude hiding (foldr, length, read)------------------------------------------------------------------------------------ Array Data Type----------------------------------------------------------------------------------data Array a =- Array- { arrContents# :: MutableArray# RealWorld a- -- ^ The internal contents of the array representing the entire array.-- , arrStart :: {-# UNPACK #-}!Int- -- ^ The starting index of this slice.-- , arrLen :: {-# UNPACK #-}!Int- -- ^ The length of this slice.- }--unsafeFreeze :: MArray.MutArray a -> Array a-unsafeFreeze (MArray.MutArray cont# arrS arrL _) = Array cont# arrS arrL--unsafeThaw :: Array a -> MArray.MutArray a-unsafeThaw (Array cont# arrS arrL) = MArray.MutArray cont# arrS arrL arrL--{-# NOINLINE nil #-}-nil :: Array a-nil = unsafePerformIO $ unsafeFreeze <$> MArray.nil------------------------------------------------------------------------------------ Construction - Folds----------------------------------------------------------------------------------{-# INLINE_NORMAL writeN #-}-writeN :: MonadIO m => Int -> Fold m a (Array a)-writeN = fmap unsafeFreeze <$> MArray.writeN--{-# INLINE_NORMAL writeWith #-}-writeWith :: MonadIO m => Int -> Fold m a (Array a)-writeWith elemCount = unsafeFreeze <$> MArray.writeWith elemCount---- | Fold the whole input to a single array.------ /Caution! Do not use this on infinite streams./----{-# INLINE write #-}-write :: MonadIO m => Fold m a (Array a)-write = fmap unsafeFreeze MArray.write------------------------------------------------------------------------------------ Construction - from streams----------------------------------------------------------------------------------{-# INLINE fromStreamN #-}-fromStreamN :: MonadIO m => Int -> Stream m a -> m (Array a)-fromStreamN n = D.fold (writeN n)--{-# INLINE fromStream #-}-fromStream :: MonadIO m => Stream m a -> m (Array a)-fromStream = D.fold write---- XXX Consider foldr/build fusion in toList/fromList--{-# INLINABLE fromListN #-}-fromListN :: Int -> [a] -> Array a-fromListN n xs = unsafePerformIO $ fromStreamN n $ D.fromList xs--{-# INLINABLE fromList #-}-fromList :: [a] -> Array a-fromList xs = unsafePerformIO $ fromStream $ D.fromList xs------------------------------------------------------------------------------------ Elimination - Unfolds----------------------------------------------------------------------------------{-# INLINE length #-}-length :: Array a -> Int-length = arrLen--{-# INLINE_NORMAL reader #-}-reader :: Monad m => Unfold m (Array a) a-reader =- Producer.simplify- $ Producer.translate unsafeThaw unsafeFreeze- $ MArray.producerWith (return . unsafeInlineIO)------------------------------------------------------------------------------------ Elimination - to streams----------------------------------------------------------------------------------{-# INLINE_NORMAL toList #-}-toList :: Array a -> [a]-toList arr = loop 0-- where-- len = length arr- loop i | i == len = []- loop i = getIndexUnsafe i arr : loop (i + 1)+import Streamly.Internal.Data.Parser (ParseError(..), ParseErrorPos(..))+import Streamly.Internal.Data.StreamK.Type (StreamK) -{-# INLINE_NORMAL read #-}-read :: Monad m => Array a -> Stream m a-read arr@Array{..} =- D.map (`getIndexUnsafe` arr) $ D.enumerateFromToIntegral 0 (arrLen - 1)+import qualified Streamly.Internal.Data.ParserDrivers as Drivers+import qualified Streamly.Internal.Data.ParserK.Type as ParserK -{-# INLINE_NORMAL readRev #-}-readRev :: Monad m => Array a -> Stream m a-readRev arr@Array{..} =- D.map (`getIndexUnsafe` arr)- $ D.enumerateFromThenToIntegral (arrLen - 1) (arrLen - 2) 0+import Prelude hiding (Foldable(..), read)+import Streamly.Internal.Data.Array.Generic.Type ---------------------------------------------------------------------------------- Elimination - using Folds+-- ParserK Chunked Generic ------------------------------------------------------------------------------- -{-# INLINE_NORMAL foldl' #-}-foldl' :: (b -> a -> b) -> b -> Array a -> b-foldl' f z arr = unsafePerformIO $ D.foldl' f z $ read arr--{-# INLINE_NORMAL foldr #-}-foldr :: (a -> b -> b) -> b -> Array a -> b-foldr f z arr = unsafePerformIO $ D.foldr f z $ read arr--{-# INLINE fold #-}-fold :: Monad m => Fold m a b -> Array a -> m b-fold f arr = D.fold f (read arr)--{-# INLINE streamFold #-}-streamFold :: Monad m => (Stream m a -> m b) -> Array a -> m b-streamFold f arr = f (read arr)------------------------------------------------------------------------------------ Random reads and writes--------------------------------------------------------------------------------+{-# INLINE parseBreak #-}+parseBreak+ :: forall m a b. Monad m+ => ParserK.ParserK (Array a) m b+ -> StreamK m (Array a)+ -> m (Either ParseError b, StreamK m (Array a))+parseBreak = Drivers.parseBreakChunksGeneric --- | /O(1)/ Lookup the element at the given index. Index starts from 0. Does--- not check the bounds.+-- | Like 'parseBreak' but includes stream position information in the error+-- messages. ----- @since 0.8.0-{-# INLINE getIndexUnsafe #-}-getIndexUnsafe :: Int -> Array a -> a-getIndexUnsafe i arr =- unsafePerformIO $ MArray.getIndexUnsafe i (unsafeThaw arr)--{-# INLINE writeLastN #-}-writeLastN :: MonadIO m => Int -> Fold m a (Array a)-writeLastN n = FL.rmapM f (RB.writeLastN n)-- where-- f rb = do- arr <- RB.toMutArray 0 n rb- return $ unsafeFreeze arr--{-# INLINE getSliceUnsafe #-}-getSliceUnsafe :: Int -> Int -> Array a -> Array a-getSliceUnsafe offset len (Array cont off1 _) = Array cont (off1 + offset) len---- XXX This is not efficient as it copies the array. We should support array--- slicing so that we can just refer to the underlying array memory instead of--- copying.---- | Truncate the array at the beginning and end as long as the predicate--- holds true. Returns a slice of the original array.-{-# INLINE strip #-}-strip :: (a -> Bool) -> Array a -> Array a-strip p arr = unsafeFreeze $ unsafePerformIO $ MArray.strip p (unsafeThaw arr)------------------------------------------------------------------------------------ Instances----------------------------------------------------------------------------------instance Eq a => Eq (Array a) where- {-# INLINE (==) #-}- arr1 == arr2 =- unsafeInlineIO $! unsafeThaw arr1 `MArray.eq` unsafeThaw arr2--instance Ord a => Ord (Array a) where- {-# INLINE compare #-}- compare arr1 arr2 =- unsafeInlineIO $! unsafeThaw arr1 `MArray.cmp` unsafeThaw arr2-- -- Default definitions defined in base do not have an INLINE on them, so we- -- replicate them here with an INLINE.- {-# 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 }-- -- These two default methods use '<=' rather than 'compare'- -- because the latter is often more expensive- {-# INLINE max #-}- max x y = if x <= y then y else x-- {-# INLINE min #-}- min x y = if x <= y then x else y+{-# INLINE parseBreakPos #-}+parseBreakPos+ :: forall m a b. Monad m+ => ParserK.ParserK (Array a) m b+ -> StreamK m (Array a)+ -> m (Either ParseErrorPos b, StreamK m (Array a))+parseBreakPos = Drivers.parseBreakChunksGenericPos -instance Show a => Show (Array a) where- {-# INLINE show #-}- show arr = "fromList " ++ show (toList arr)+{-# INLINE parse #-}+parse ::+ (Monad m)+ => ParserK.ParserK (Array a) m b+ -> StreamK m (Array a)+ -> m (Either ParseError b)+parse f = fmap fst . parseBreak f -instance Read a => Read (Array a) where- {-# INLINE readPrec #-}- readPrec = do- fromListWord <- replicateM 9 ReadPrec.get- if fromListWord == "fromList "- then fromList <$> readPrec- else ReadPrec.pfail+{-# INLINE parsePos #-}+parsePos ::+ (Monad m)+ => ParserK.ParserK (Array a) m b+ -> StreamK m (Array a)+ -> m (Either ParseErrorPos b)+parsePos f = fmap fst . parseBreakPos f
− src/Streamly/Internal/Data/Array/Generic/Mut/Type.hs
@@ -1,796 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE UnboxedTuples #-}--- |--- Module : Streamly.Internal.Data.Array.Generic.Mut.Type--- Copyright : (c) 2020 Composewell Technologies--- License : BSD3-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC----module Streamly.Internal.Data.Array.Generic.Mut.Type-(- -- * Type- -- $arrayNotes- MutArray (..)-- -- * Constructing and Writing- -- ** Construction- , nil-- -- *** Uninitialized Arrays- , new- -- , newArrayWith-- -- *** From streams- , writeNUnsafe- , writeN- , writeWith- , write-- -- , writeRevN- -- , writeRev-- -- ** From containers- -- , fromListN- -- , fromList- -- , fromStreamDN- -- , fromStreamD-- -- * Random writes- , putIndex- , putIndexUnsafe- , putIndices- -- , putFromThenTo- -- , putFrom -- start writing at the given position- -- , putUpto -- write from beginning up to the given position- -- , putFromTo- -- , putFromRev- -- , putUptoRev- , modifyIndexUnsafe- , modifyIndex- -- , modifyIndices- -- , modify- -- , swapIndices-- -- * Growing and Shrinking- -- Arrays grow only at the end, though it is possible to grow on both sides- -- and therefore have a cons as well as snoc. But that will require two- -- bounds in the array representation.-- -- ** Reallocation- , realloc- , uninit-- -- ** Appending elements- , snocWith- , snoc- -- , snocLinear- -- , snocMay- , snocUnsafe-- -- ** Appending streams- -- , writeAppendNUnsafe- -- , writeAppendN- -- , writeAppendWith- -- , writeAppend-- -- ** Truncation- -- These are not the same as slicing the array at the beginning, they may- -- reduce the length as well as the capacity of the array.- -- , truncateWith- -- , truncate- -- , truncateExp-- -- * Eliminating and Reading-- -- ** Unfolds- , reader- -- , readerRev- , producerWith -- experimental- , producer -- experimental-- -- ** To containers- , toStreamD- , readRev- , toStreamK- -- , toStreamKRev- , toList-- -- ** Random reads- , getIndex- , getIndexUnsafe- -- , getIndices- -- , getFromThenTo- -- , getIndexRev-- -- * Size- , length-- -- * In-place Mutation Algorithms- , strip- -- , reverse- -- , permute- -- , partitionBy- -- , shuffleBy- -- , divideBy- -- , mergeBy-- -- * Folding- -- , foldl'- -- , foldr- , cmp- , eq-- -- * Arrays of arrays- -- We can add dimensionality parameter to the array type to get- -- multidimensional arrays. Multidimensional arrays would just be a- -- convenience wrapper on top of single dimensional arrays.-- -- | Operations dealing with multiple arrays, streams of arrays or- -- multidimensional array representations.-- -- ** Construct from streams- -- , chunksOf- -- , arrayStreamKFromStreamD- -- , writeChunks-- -- ** Eliminate to streams- -- , flattenArrays- -- , flattenArraysRev- -- , fromArrayStreamK-- -- ** Construct from arrays- -- get chunks without copying- , getSliceUnsafe- , getSlice- -- , getSlicesFromLenN- -- , splitAt -- XXX should be able to express using getSlice- -- , breakOn-- -- ** Appending arrays- -- , spliceCopy- -- , spliceWith- -- , splice- -- , spliceExp- , putSliceUnsafe- -- , appendSlice- -- , appendSliceFrom-- , clone- )-where--#include "inline.hs"-#include "assert.hs"--import Control.Monad (when)-import Control.Monad.IO.Class (MonadIO(..))-import GHC.Base- ( MutableArray#- , RealWorld- , copyMutableArray#- , newArray#- , readArray#- , writeArray#- )-import GHC.IO (IO(..))-import GHC.Int (Int(..))-import Streamly.Internal.Data.Fold.Type (Fold(..))-import Streamly.Internal.Data.Producer.Type (Producer (..))-import Streamly.Internal.Data.Unfold.Type (Unfold(..))--import qualified Streamly.Internal.Data.Fold.Type as FL-import qualified Streamly.Internal.Data.Producer as Producer-import qualified Streamly.Internal.Data.Stream.StreamD.Type as D-import qualified Streamly.Internal.Data.Stream.StreamD.Generate as D-import qualified Streamly.Internal.Data.Stream.StreamK.Type as K--import Prelude hiding (read, length)--#include "DocTestDataMutArrayGeneric.hs"------------------------------------------------------------------------------------ MutArray Data Type----------------------------------------------------------------------------------data MutArray a =- MutArray- { arrContents# :: MutableArray# RealWorld a- -- ^ The internal contents of the array representing the entire array.-- , arrStart :: {-# UNPACK #-}!Int- -- ^ The starting index of this slice.-- , arrLen :: {-# UNPACK #-}!Int- -- ^ The length of this slice.-- , arrTrueLen :: {-# UNPACK #-}!Int- -- ^ This is the true length of the array. Coincidentally, this also- -- represents the first index beyond the maximum acceptable index of- -- the array. This is specific to the array contents itself and not- -- dependent on the slice. This value should not change and is shared- -- across all the slices.- }--{-# INLINE bottomElement #-}-bottomElement :: a-bottomElement =- error- $ unwords- [ funcName- , "This is the bottom element of the array."- , "This is a place holder and should never be reached!"- ]-- where-- funcName = "Streamly.Internal.Data.Array.Generic.Mut.Type.bottomElement:"---- XXX Would be nice if GHC can provide something like newUninitializedArray# so--- that we do not have to write undefined or error in the whole array.---- | @new count@ allocates a zero length array that can be extended to hold--- up to 'count' items without reallocating.------ /Pre-release/-{-# INLINE new #-}-new :: MonadIO m => Int -> m (MutArray a)-new n@(I# n#) =- liftIO- $ IO- $ \s# ->- case newArray# n# bottomElement s# of- (# s1#, arr# #) ->- let ma = MutArray arr# 0 0 n- in (# s1#, ma #)---- XXX This could be pure?---- |--- Definition:------ >>> nil = MutArray.new 0-{-# INLINE nil #-}-nil :: MonadIO m => m (MutArray a)-nil = new 0------------------------------------------------------------------------------------ Random writes------------------------------------------------------------------------------------ | Write the given element to the given index of the array. Does not check if--- the index is out of bounds of the array.------ /Pre-release/-{-# INLINE putIndexUnsafe #-}-putIndexUnsafe :: forall m a. MonadIO m => Int -> MutArray a -> a -> m ()-putIndexUnsafe i MutArray {..} x =- assert (i >= 0 && i < arrLen)- (liftIO- $ IO- $ \s# ->- case i + arrStart of- I# n# ->- let s1# = writeArray# arrContents# n# x s#- in (# s1#, () #))--invalidIndex :: String -> Int -> a-invalidIndex label i =- error $ label ++ ": invalid array index " ++ show i---- | /O(1)/ Write the given element at the given index in the array.--- Performs in-place mutation of the array.------ >>> putIndex ix arr val = MutArray.modifyIndex ix arr (const (val, ()))------ /Pre-release/-{-# INLINE putIndex #-}-putIndex :: MonadIO m => Int -> MutArray a -> a -> m ()-putIndex i arr@MutArray {..} x =- if i >= 0 && i < arrLen- then putIndexUnsafe i arr x- else invalidIndex "putIndex" i---- | Write an input stream of (index, value) pairs to an array. Throws an--- error if any index is out of bounds.------ /Pre-release/-{-# INLINE putIndices #-}-putIndices :: MonadIO m- => MutArray a -> Fold m (Int, a) ()-putIndices arr = FL.foldlM' step (return ())-- where-- step () (i, x) = liftIO (putIndex i arr x)---- | Modify a given index of an array using a modifier function without checking--- the bounds.------ Unsafe because it does not check the bounds of the array.------ /Pre-release/-modifyIndexUnsafe :: MonadIO m => Int -> MutArray a -> (a -> (a, b)) -> m b-modifyIndexUnsafe i MutArray {..} f = do- liftIO- $ IO- $ \s# ->- case i + arrStart of- I# n# ->- case readArray# arrContents# n# s# of- (# s1#, a #) ->- let (a1, b) = f a- s2# = writeArray# arrContents# n# a1 s1#- in (# s2#, b #)---- | Modify a given index of an array using a modifier function.------ /Pre-release/-modifyIndex :: MonadIO m => Int -> MutArray a -> (a -> (a, b)) -> m b-modifyIndex i arr@MutArray {..} f = do- if i >= 0 && i < arrLen- then modifyIndexUnsafe i arr f- else invalidIndex "modifyIndex" i------------------------------------------------------------------------------------ Resizing------------------------------------------------------------------------------------ | Reallocates the array according to the new size. This is a safe function--- that always creates a new array and copies the old array into the new one.--- If the reallocated size is less than the original array it results in a--- truncated version of the original array.----realloc :: MonadIO m => Int -> MutArray a -> m (MutArray a)-realloc n arr = do- arr1 <- new n- let !newLen@(I# newLen#) = min n (arrLen arr)- !(I# arrS#) = arrStart arr- !(I# arr1S#) = arrStart arr1- arrC# = arrContents# arr- arr1C# = arrContents# arr1- liftIO- $ IO- $ \s# ->- let s1# = copyMutableArray# arrC# arrS# arr1C# arr1S# newLen# s#- in (# s1#, arr1 {arrLen = newLen, arrTrueLen = n} #)--reallocWith ::- MonadIO m => String -> (Int -> Int) -> Int -> MutArray a -> m (MutArray a)-reallocWith label sizer reqSize arr = do- let oldSize = arrLen arr- newSize = sizer oldSize- safeSize = max newSize (oldSize + reqSize)- assert (newSize >= oldSize + reqSize || error badSize) (return ())- realloc safeSize arr-- where-- badSize = concat- [ label- , ": new array size is less than required size "- , show reqSize- , ". Please check the sizing function passed."- ]------------------------------------------------------------------------------------ Snoc------------------------------------------------------------------------------------ XXX Not sure of the behavior of writeArray# if we specify an index which is--- out of bounds. This comment should be rewritten based on that.--- | Really really unsafe, appends the element into the first array, may--- cause silent data corruption or if you are lucky a segfault if the index--- is out of bounds.------ /Internal/-{-# INLINE snocUnsafe #-}-snocUnsafe :: MonadIO m => MutArray a -> a -> m (MutArray a)-snocUnsafe arr@MutArray {..} a = do- assert (arrStart + arrLen < arrTrueLen) (return ())- let arr1 = arr {arrLen = arrLen + 1}- putIndexUnsafe arrLen arr1 a- return arr1---- NOINLINE to move it out of the way and not pollute the instruction cache.-{-# NOINLINE snocWithRealloc #-}-snocWithRealloc :: MonadIO m => (Int -> Int) -> MutArray a -> a -> m (MutArray a)-snocWithRealloc sizer arr x = do- arr1 <- reallocWith "snocWithRealloc" sizer 1 arr- snocUnsafe arr1 x---- | @snocWith sizer arr elem@ mutates @arr@ to append @elem@. The length of--- the array increases by 1.------ If there is no reserved space available in @arr@ it is reallocated to a size--- in bytes determined by the @sizer oldSize@ function, where @oldSize@ is the--- original size of the array.------ Note that the returned array may be a mutated version of the original array.------ /Pre-release/-{-# INLINE snocWith #-}-snocWith :: MonadIO m => (Int -> Int) -> MutArray a -> a -> m (MutArray a)-snocWith sizer arr@MutArray {..} x = do- if arrStart + arrLen < arrTrueLen- then snocUnsafe arr x- else snocWithRealloc sizer arr x---- XXX round it to next power of 2.---- | The array is mutated to append an additional element to it. If there is no--- reserved space available in the array then it is reallocated to double the--- original size.------ This is useful to reduce allocations when appending unknown number of--- elements.------ Note that the returned array may be a mutated version of the original array.------ >>> snoc = MutArray.snocWith (* 2)------ Performs O(n * log n) copies to grow, but is liberal with memory allocation.------ /Pre-release/-{-# INLINE snoc #-}-snoc :: MonadIO m => MutArray a -> a -> m (MutArray a)-snoc = snocWith (* 2)---- | Make the uninitialized memory in the array available for use extending it--- by the supplied length beyond the current length of the array. The array may--- be reallocated.----{-# INLINE uninit #-}-uninit :: MonadIO m => MutArray a -> Int -> m (MutArray a)-uninit arr@MutArray{..} len =- if arrStart + arrLen + len <= arrTrueLen- then return $ arr {arrLen = arrLen + len}- else realloc (arrLen + len) arr------------------------------------------------------------------------------------ Random reads------------------------------------------------------------------------------------ | Return the element at the specified index without checking the bounds.------ Unsafe because it does not check the bounds of the array.-{-# INLINE_NORMAL getIndexUnsafe #-}-getIndexUnsafe :: MonadIO m => Int -> MutArray a -> m a-getIndexUnsafe n MutArray {..} =- liftIO- $ IO- $ \s# ->- let !(I# i#) = arrStart + n- in readArray# arrContents# i# s#---- | /O(1)/ Lookup the element at the given index. Index starts from 0.----{-# INLINE getIndex #-}-getIndex :: MonadIO m => Int -> MutArray a -> m a-getIndex i arr@MutArray {..} =- if i >= 0 && i < arrLen- then getIndexUnsafe i arr- else invalidIndex "getIndex" i------------------------------------------------------------------------------------ Subarrays------------------------------------------------------------------------------------ XXX We can also get immutable slices.---- | /O(1)/ Slice an array in constant time.------ Unsafe: The bounds of the slice are not checked.------ /Unsafe/------ /Pre-release/-{-# INLINE getSliceUnsafe #-}-getSliceUnsafe- :: Int -- ^ from index- -> Int -- ^ length of the slice- -> MutArray a- -> MutArray a-getSliceUnsafe index len arr@MutArray {..} =- assert (index >= 0 && len >= 0 && index + len <= arrLen)- $ arr {arrStart = arrStart + index, arrLen = len}---- | /O(1)/ Slice an array in constant time. Throws an error if the slice--- extends out of the array bounds.------ /Pre-release/-{-# INLINE getSlice #-}-getSlice- :: Int -- ^ from index- -> Int -- ^ length of the slice- -> MutArray a- -> MutArray a-getSlice index len arr@MutArray{..} =- if index >= 0 && len >= 0 && index + len <= arrLen- then arr {arrStart = arrStart + index, arrLen = len}- else error- $ "getSlice: invalid slice, index "- ++ show index ++ " length " ++ show len------------------------------------------------------------------------------------ to Lists and streams------------------------------------------------------------------------------------ XXX Maybe faster to create a list explicitly instead of mapM, if list fusion--- does not work well.---- | Convert an 'Array' into a list.------ /Pre-release/-{-# INLINE toList #-}-toList :: MonadIO m => MutArray a -> m [a]-toList arr@MutArray{..} = mapM (`getIndexUnsafe` arr) [0 .. (arrLen - 1)]---- | Use the 'read' unfold instead.------ @toStreamD = D.unfold read@------ We can try this if the unfold has any performance issues.-{-# INLINE_NORMAL toStreamD #-}-toStreamD :: MonadIO m => MutArray a -> D.Stream m a-toStreamD arr@MutArray{..} =- D.mapM (`getIndexUnsafe` arr) $ D.enumerateFromToIntegral 0 (arrLen - 1)---- Check equivalence with StreamK.fromStream . toStreamD and remove-{-# INLINE toStreamK #-}-toStreamK :: MonadIO m => MutArray a -> K.StreamK m a-toStreamK arr@MutArray{..} = K.unfoldrM step 0-- where-- step i- | i == arrLen = return Nothing- | otherwise = do- x <- getIndexUnsafe i arr- return $ Just (x, i + 1)--{-# INLINE_NORMAL readRev #-}-readRev :: MonadIO m => MutArray a -> D.Stream m a-readRev arr@MutArray{..} =- D.mapM (`getIndexUnsafe` arr)- $ D.enumerateFromThenToIntegral (arrLen - 1) (arrLen - 2) 0------------------------------------------------------------------------------------ Folds------------------------------------------------------------------------------------ XXX deduplicate this across unboxed array and this module?---- | The default chunk size by which the array creation routines increase the--- size of the array when the array is grown linearly.-arrayChunkSize :: Int-arrayChunkSize = 1024---- | Like 'writeN' but does not check the array bounds when writing. The fold--- driver must not call the step function more than 'n' times otherwise it will--- corrupt the memory and crash. This function exists mainly because any--- conditional in the step function blocks fusion causing 10x performance--- slowdown.------ /Pre-release/-{-# INLINE_NORMAL writeNUnsafe #-}-writeNUnsafe :: MonadIO m => Int -> Fold m a (MutArray a)-writeNUnsafe n = Fold step initial return-- where-- initial = FL.Partial <$> new (max n 0)-- step arr x = FL.Partial <$> snocUnsafe arr x---- | @writeN n@ folds a maximum of @n@ elements from the input stream to an--- 'Array'.------ >>> writeN n = Fold.take n (MutArray.writeNUnsafe n)------ /Pre-release/-{-# INLINE_NORMAL writeN #-}-writeN :: MonadIO m => Int -> Fold m a (MutArray a)-writeN n = FL.take n $ writeNUnsafe n---- >>> f n = MutArray.writeAppendWith (* 2) (MutArray.newPinned n)--- >>> writeWith n = Fold.rmapM MutArray.rightSize (f n)--- >>> writeWith n = Fold.rmapM MutArray.fromArrayStreamK (MutArray.writeChunks n)---- | @writeWith minCount@ folds the whole input to a single array. The array--- starts at a size big enough to hold minCount elements, the size is doubled--- every time the array needs to be grown.------ /Caution! Do not use this on infinite streams./------ /Pre-release/-{-# INLINE_NORMAL writeWith #-}-writeWith :: MonadIO m => Int -> Fold m a (MutArray a)--- writeWith n = FL.rmapM rightSize $ writeAppendWith (* 2) (newPinned n)-writeWith elemCount = FL.rmapM extract $ FL.foldlM' step initial-- where-- initial = do- when (elemCount < 0) $ error "writeWith: elemCount is negative"- liftIO $ new elemCount-- step arr@(MutArray _ start end bound) x- | end == bound = do- let oldSize = end - start- newSize = max (oldSize * 2) 1- arr1 <- liftIO $ realloc newSize arr- snocUnsafe arr1 x- step arr x = snocUnsafe arr x-- -- extract = liftIO . rightSize- extract = return---- | Fold the whole input to a single array.------ Same as 'writeWith' using an initial array size of 'arrayChunkSize' bytes--- rounded up to the element size.------ /Caution! Do not use this on infinite streams./----{-# INLINE write #-}-write :: MonadIO m => Fold m a (MutArray a)-write = writeWith arrayChunkSize------------------------------------------------------------------------------------ Unfolds------------------------------------------------------------------------------------ | Resumable unfold of an array.----{-# INLINE_NORMAL producerWith #-}-producerWith :: Monad m => (forall b. IO b -> m b) -> Producer m (MutArray a) a-producerWith liftio = Producer step inject extract-- where-- {-# INLINE inject #-}- inject arr = return (arr, 0)-- {-# INLINE extract #-}- extract (arr, i) =- return $ arr {arrStart = arrStart arr + i, arrLen = arrLen arr - i}-- {-# INLINE_LATE step #-}- step (arr, i)- | assert (arrLen arr >= 0) (i == arrLen arr) = return D.Stop- step (arr, i) = do- x <- liftio $ getIndexUnsafe i arr- return $ D.Yield x (arr, i + 1)---- | Resumable unfold of an array.----{-# INLINE_NORMAL producer #-}-producer :: MonadIO m => Producer m (MutArray a) a-producer = producerWith liftIO---- | Unfold an array into a stream.----{-# INLINE_NORMAL reader #-}-reader :: MonadIO m => Unfold m (MutArray a) a-reader = Producer.simplify producer------------------------------------------------------------------------------------- Appending arrays------------------------------------------------------------------------------------- | Put a sub range of a source array into a subrange of a destination array.--- This is not safe as it does not check the bounds.-{-# INLINE putSliceUnsafe #-}-putSliceUnsafe :: MonadIO m =>- MutArray a -> Int -> MutArray a -> Int -> Int -> m ()-putSliceUnsafe src srcStart dst dstStart len = liftIO $ do- assertM(len <= arrLen dst)- assertM(len <= arrLen src)- let !(I# srcStart#) = srcStart + arrStart src- !(I# dstStart#) = dstStart + arrStart dst- !(I# len#) = len- let arrS# = arrContents# src- arrD# = arrContents# dst- IO $ \s# -> (# copyMutableArray#- arrS# srcStart# arrD# dstStart# len# s#- , () #)--{-# INLINE clone #-}-clone :: MonadIO m => MutArray a -> m (MutArray a)-clone src = liftIO $ do- let len = arrLen src- dst <- new len- putSliceUnsafe src 0 dst 0 len- return dst------------------------------------------------------------------------------------ Size----------------------------------------------------------------------------------{-# INLINE length #-}-length :: MutArray a -> Int-length = arrLen------------------------------------------------------------------------------------ Equality------------------------------------------------------------------------------------ | Compare the length of the arrays. If the length is equal, compare the--- lexicographical ordering of two underlying byte arrays otherwise return the--- result of length comparison.------ /Pre-release/-{-# INLINE cmp #-}-cmp :: (MonadIO m, Ord a) => MutArray a -> MutArray a -> m Ordering-cmp a1 a2 =- case compare lenA1 lenA2 of- EQ -> loop (lenA1 - 1)- x -> return x-- where-- lenA1 = length a1- lenA2 = length a2-- loop i- | i < 0 = return EQ- | otherwise = do- v1 <- getIndexUnsafe i a1- v2 <- getIndexUnsafe i a2- case compare v1 v2 of- EQ -> loop (i - 1)- x -> return x--{-# INLINE eq #-}-eq :: (MonadIO m, Eq a) => MutArray a -> MutArray a -> m Bool-eq a1 a2 =- if lenA1 == lenA2- then loop (lenA1 - 1)- else return False-- where-- lenA1 = length a1- lenA2 = length a2-- loop i- | i < 0 = return True- | otherwise = do- v1 <- getIndexUnsafe i a1- v2 <- getIndexUnsafe i a2- if v1 == v2- then loop (i - 1)- else return False--{-# INLINE strip #-}-strip :: MonadIO m => (a -> Bool) -> MutArray a -> m (MutArray a)-strip p arr = liftIO $ do- let lastIndex = length arr - 1- indexR <- getIndexR lastIndex -- last predicate failing index- if indexR < 0- then nil- else do- indexL <- getIndexL 0 -- first predicate failing index- if indexL == 0 && indexR == lastIndex- then return arr- else- let newLen = indexR - indexL + 1- in return $ getSliceUnsafe indexL newLen arr-- where-- getIndexR idx- | idx < 0 = return idx- | otherwise = do- r <- getIndexUnsafe idx arr- if p r- then getIndexR (idx - 1)- else return idx-- getIndexL idx = do- r <- getIndexUnsafe idx arr- if p r- then getIndexL (idx + 1)- else return idx
+ src/Streamly/Internal/Data/Array/Generic/Type.hs view
@@ -0,0 +1,495 @@+-- |+-- Module : Streamly.Internal.Data.Array.Generic.Type+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--+module Streamly.Internal.Data.Array.Generic.Type+ ( Array(..)++ -- * Conversion+ , unsafeFreeze+ , unsafeThaw++ -- * Construction+ , nil+ , createOf+ , create+ , createWith+ , createOfLast++ , fromStreamN+ , fromStream+ , fromPureStream+ , fromCString#++ , fromListN+ , fromList++ , chunksOf++ -- * Elimination+ , length+ , reader++ , toList+ , read+ , readRev++ , foldl'+ , foldr+ , streamFold+ , fold++ -- * Random Access+ , unsafeGetIndex+ , getIndex+ , unsafeSliceOffLen+ , dropAround++ -- * Parsing Stream of Arrays+ , toParserK++ -- * Deprecated+ , strip+ , getIndexUnsafe+ , getSliceUnsafe+ , writeN+ , write+ , fromByteStr#+ )+where++#include "inline.hs"+#include "assert.hs"+#include "deprecation.h"++import Control.Monad (replicateM)+import Control.Monad.IO.Class (MonadIO)+import Data.Functor.Identity (Identity(..))+import Data.Word (Word8)+import GHC.Base (MutableArray#, RealWorld)+import GHC.Exts (Addr#)+import GHC.Types (SPEC(..))+import GHC.IO (unsafePerformIO)+import Text.Read (readPrec)++import Streamly.Internal.Data.Fold.Type (Fold(..))+import Streamly.Internal.Data.ParserK.Type+ (ParserK, ParseResult(..), Input(..), Step(..))+import Streamly.Internal.Data.Stream.Type (Stream)+import Streamly.Internal.Data.Unfold.Type (Unfold(..))+import Streamly.Internal.System.IO (unsafeInlineIO)++import qualified Streamly.Internal.Data.Fold.Type as FL+import qualified Streamly.Internal.Data.MutArray.Generic as MArray+import qualified Streamly.Internal.Data.Parser.Type as ParserD+import qualified Streamly.Internal.Data.ParserK.Type as ParserK+import qualified Streamly.Internal.Data.Producer as Producer+import qualified Streamly.Internal.Data.RingArray.Generic as RB+import qualified Streamly.Internal.Data.Stream.Type as D+import qualified Streamly.Internal.Data.Stream.Generate as D+import qualified Text.ParserCombinators.ReadPrec as ReadPrec++import Prelude hiding (Foldable(..), read)++-------------------------------------------------------------------------------+-- Array Data Type+-------------------------------------------------------------------------------++data Array a =+ Array+ { arrContents# :: MutableArray# RealWorld a+ -- ^ The internal contents of the array representing the entire array.++ , arrStart :: {-# UNPACK #-}!Int+ -- ^ The starting index of this slice.++ , arrEnd :: {-# UNPACK #-}!Int+ -- ^ First invalid index of the array.+ }++unsafeFreeze :: MArray.MutArray a -> Array a+unsafeFreeze (MArray.MutArray cont# arrS arrE _) = Array cont# arrS arrE++unsafeThaw :: Array a -> MArray.MutArray a+unsafeThaw (Array cont# arrS arrE) = MArray.MutArray cont# arrS arrE arrE++{-# NOINLINE nil #-}+nil :: Array a+nil = unsafePerformIO $ unsafeFreeze <$> MArray.nil++-------------------------------------------------------------------------------+-- Construction - Folds+-------------------------------------------------------------------------------++{-# INLINE_NORMAL createOf #-}+createOf :: MonadIO m => Int -> Fold m a (Array a)+createOf = fmap unsafeFreeze <$> MArray.createOf++{-# DEPRECATED writeN "Please use createOf instead." #-}+{-# INLINE writeN #-}+writeN :: MonadIO m => Int -> Fold m a (Array a)+writeN = createOf++{-# INLINE_NORMAL createWith #-}+createWith :: MonadIO m => Int -> Fold m a (Array a)+createWith elemCount = unsafeFreeze <$> MArray.createWith elemCount++-- | Fold the whole input to a single array.+--+-- /Caution! Do not use this on infinite streams./+--+{-# INLINE create #-}+create :: MonadIO m => Fold m a (Array a)+create = fmap unsafeFreeze MArray.create++{-# DEPRECATED write "Please use create instead." #-}+{-# INLINE write #-}+write :: MonadIO m => Fold m a (Array a)+write = create++fromPureStream :: Stream Identity a -> Array a+fromPureStream x =+ unsafePerformIO $ fmap unsafeFreeze (MArray.fromPureStream x)+-- fromPureStream = runIdentity . D.fold (unsafeMakePure write)+-- fromPureStream = fromList . runIdentity . D.toList++fromCString# :: MonadIO m => Addr# -> m (Array Word8)+fromCString# addr = fromStream $ D.fromCString# addr++{-# DEPRECATED fromByteStr# "Please use 'unsafePerformIO . fromCString#' instead" #-}+fromByteStr# :: Addr# -> Array Word8+fromByteStr# addr = fromPureStream (D.fromCString# addr)++-------------------------------------------------------------------------------+-- Stream Ops+-------------------------------------------------------------------------------++{-# INLINE_NORMAL chunksOf #-}+chunksOf :: forall m a. MonadIO m+ => Int -> Stream m a -> Stream m (Array a)+chunksOf n strm = fmap unsafeFreeze $ MArray.chunksOf n strm++-------------------------------------------------------------------------------+-- Construction - from streams+-------------------------------------------------------------------------------++{-# INLINE fromStreamN #-}+fromStreamN :: MonadIO m => Int -> Stream m a -> m (Array a)+fromStreamN n = D.fold (writeN n)++{-# INLINE fromStream #-}+fromStream :: MonadIO m => Stream m a -> m (Array a)+fromStream = D.fold write++-- XXX Consider foldr/build fusion in toList/fromList++{-# INLINABLE fromListN #-}+fromListN :: Int -> [a] -> Array a+fromListN n xs = unsafePerformIO $ fromStreamN n $ D.fromList xs++{-# INLINABLE fromList #-}+fromList :: [a] -> Array a+fromList xs = unsafePerformIO $ fromStream $ D.fromList xs++-------------------------------------------------------------------------------+-- Elimination - Unfolds+-------------------------------------------------------------------------------++{-# INLINE length #-}+length :: Array a -> Int+length arr = arrEnd arr - arrStart arr++{-# INLINE_NORMAL reader #-}+reader :: Monad m => Unfold m (Array a) a+reader =+ Producer.simplify+ $ Producer.translate unsafeThaw unsafeFreeze+ $ MArray.producerWith (return . unsafeInlineIO)++-------------------------------------------------------------------------------+-- Elimination - to streams+-------------------------------------------------------------------------------++{-# INLINE_NORMAL toList #-}+toList :: Array a -> [a]+toList arr = loop 0++ where++ len = length arr+ loop i | i == len = []+ loop i = unsafeGetIndex i arr : loop (i + 1)++{-# INLINE_NORMAL read #-}+read :: Monad m => Array a -> Stream m a+read arr =+ D.map (`unsafeGetIndex` arr) $ D.enumerateFromToIntegral 0 (length arr - 1)++{-# INLINE_NORMAL readRev #-}+readRev :: Monad m => Array a -> Stream m a+readRev arr =+ D.map (`unsafeGetIndex` arr)+ $ D.enumerateFromThenToIntegral (arrLen - 1) (arrLen - 2) 0+ where+ arrLen = length arr++-------------------------------------------------------------------------------+-- Elimination - using Folds+-------------------------------------------------------------------------------++{-# INLINE_NORMAL foldl' #-}+foldl' :: (b -> a -> b) -> b -> Array a -> b+foldl' f z arr = unsafePerformIO $ D.foldl' f z $ read arr++{-# INLINE_NORMAL foldr #-}+foldr :: (a -> b -> b) -> b -> Array a -> b+foldr f z arr = unsafePerformIO $ D.foldr f z $ read arr++{-# INLINE fold #-}+fold :: Monad m => Fold m a b -> Array a -> m b+fold f arr = D.fold f (read arr)++{-# INLINE streamFold #-}+streamFold :: Monad m => (Stream m a -> m b) -> Array a -> m b+streamFold f arr = f (read arr)++-------------------------------------------------------------------------------+-- Random reads and writes+-------------------------------------------------------------------------------++-- | /O(1)/ Lookup the element at the given index. Index starts from 0. Does+-- not check the bounds.+--+-- @since 0.8.0+{-# INLINE unsafeGetIndex #-}+unsafeGetIndex, getIndexUnsafe :: Int -> Array a -> a+unsafeGetIndex i arr =+ unsafePerformIO $ MArray.unsafeGetIndex i (unsafeThaw arr)++-- | Lookup the element at the given index. Index starts from 0.+--+{-# INLINE getIndex #-}+getIndex :: Int -> Array a -> Maybe a+getIndex i arr =+ if i >= 0 && i < length arr+ then Just $ unsafeGetIndex i arr+ else Nothing++-- >>> import qualified Streamly.Data.Stream as Stream+-- >>> import qualified Streamly.Data.Fold as Fold+-- >>> import qualified Streamly.Internal.Data.Array.Generic as Array+-- >>> import Data.Function ((&))+-- >>> :{+-- Stream.fromList [1,2,3,4,5::Int]+-- & Stream.scan (Array.createOfLast 2)+-- & Stream.fold Fold.toList+-- :}+-- [fromList [],fromList [1],fromList [1,2],fromList [2,3],fromList [3,4],fromList [4,5]]+--+{-# INLINE createOfLast #-}+createOfLast :: MonadIO m => Int -> Fold m a (Array a)+createOfLast n = FL.rmapM f (RB.createOf n)++ where++ f rb = do+ arr <- RB.copyToMutArray 0 n rb+ return $ unsafeFreeze arr++{-# INLINE unsafeSliceOffLen #-}+unsafeSliceOffLen, getSliceUnsafe+ :: Int -> Int -> Array a -> Array a+unsafeSliceOffLen offset len =+ unsafeFreeze . MArray.unsafeSliceOffLen offset len . unsafeThaw++-- XXX This is not efficient as it copies the array. We should support array+-- slicing so that we can just refer to the underlying array memory instead of+-- copying.++-- | Truncate the array at the beginning and end as long as the predicate+-- holds true. Returns a slice of the original array.+{-# INLINE dropAround #-}+dropAround, strip :: (a -> Bool) -> Array a -> Array a+dropAround p arr =+ unsafeFreeze $ unsafePerformIO $ MArray.dropAround p (unsafeThaw arr)++-------------------------------------------------------------------------------+-- Instances+-------------------------------------------------------------------------------++instance Eq a => Eq (Array a) where+ {-# INLINE (==) #-}+ arr1 == arr2 =+ unsafeInlineIO $! unsafeThaw arr1 `MArray.eq` unsafeThaw arr2++instance Ord a => Ord (Array a) where+ {-# INLINE compare #-}+ compare arr1 arr2 =+ unsafeInlineIO $! unsafeThaw arr1 `MArray.cmp` unsafeThaw arr2++ -- Default definitions defined in base do not have an INLINE on them, so we+ -- replicate them here with an INLINE.+ {-# 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 }++ -- These two default methods use '<=' rather than 'compare'+ -- because the latter is often more expensive+ {-# 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 (Array a) where+ {-# INLINE show #-}+ show arr = "fromList " ++ show (toList arr)++instance Read a => Read (Array a) where+ {-# INLINE readPrec #-}+ readPrec = do+ fromListWord <- replicateM 9 ReadPrec.get+ if fromListWord == "fromList "+ then fromList <$> readPrec+ else ReadPrec.pfail++-------------------------------------------------------------------------------+-- Backward Compatibility+-------------------------------------------------------------------------------++RENAME(strip,dropAround)+RENAME(getSliceUnsafe,unsafeSliceOffLen)+RENAME(getIndexUnsafe,unsafeGetIndex)++--------------------------------------------------------------------------------+-- Convert Parser to Parserk on Generic Arrays+--------------------------------------------------------------------------------++{-# INLINE adaptCGWith #-}+adaptCGWith+ :: forall m a s b r. (Monad m)+ => (s -> a -> m (ParserD.Step s b))+ -> m (ParserD.Initial s b)+ -> (s -> m (ParserD.Final s b))+ -> (ParseResult b -> Int -> Input (Array a) -> m (Step (Array a) m r))+ -> Int+ -> Int+ -> Input (Array a)+ -> m (Step (Array a) m r)+adaptCGWith pstep initial extract cont !offset0 !usedCount !input = do+ res <- initial+ case res of+ ParserD.IPartial pst -> do+ case input of+ Chunk arr -> parseContChunk usedCount offset0 pst arr+ None -> parseContNothing usedCount pst+ ParserD.IDone b -> cont (Success offset0 b) usedCount input+ ParserD.IError err -> cont (Failure offset0 err) usedCount input++ where++ {-# NOINLINE parseContChunk #-}+ parseContChunk !count !offset !state arr@(Array contents start end) = do+ if offset >= 0+ then go SPEC (start + offset) state+ else return $ Continue offset (parseCont count state)++ where++ {-# INLINE onDone #-}+ onDone n b =+ assert (n <= length arr)+ (cont (Success n b) (count + n - offset) (Chunk arr))++ {-# INLINE callParseCont #-}+ callParseCont constr n pst1 =+ assert (n < 0 || n >= length arr)+ (return $ constr n (parseCont (count + n - offset) pst1))++ {-# INLINE onPartial #-}+ onPartial = callParseCont Partial++ {-# INLINE onContinue #-}+ onContinue = callParseCont Continue++ {-# INLINE onError #-}+ onError n err =+ cont (Failure n err) (count + n - offset) (Chunk arr)++ {-# INLINE onBack #-}+ onBack offset1 constr pst = do+ let pos = offset1 - start+ in if pos >= 0+ then go SPEC offset1 pst+ else constr pos pst++ go !_ !cur !pst | cur >= end =+ onContinue (end - start) pst+ go !_ !cur !pst = do+ let !x = unsafeInlineIO $ MArray.unsafeGetIndexWith contents cur+ pRes <- pstep pst x+ let next = cur + 1+ -- XXX Change this to moveOff and remove curOff and nextOff+ move n = cur + n+ curOff = cur - start+ nextOff = next - start+ case pRes of+ ParserD.SDone 1 b ->+ onDone nextOff b+ ParserD.SDone 0 b ->+ onDone curOff b+ ParserD.SDone n b ->+ onDone (move n - start) b+ ParserD.SPartial 1 pst1 ->+ go SPEC next pst1+ ParserD.SPartial 0 pst1 ->+ go SPEC cur pst1+ ParserD.SPartial n pst1 ->+ onBack (move n) onPartial pst1+ ParserD.SContinue 1 pst1 ->+ go SPEC next pst1+ ParserD.SContinue 0 pst1 ->+ go SPEC cur pst1+ ParserD.SContinue n pst1 ->+ onBack (move n) onContinue pst1+ ParserD.SError err ->+ onError curOff err++ {-# NOINLINE parseContNothing #-}+ parseContNothing !count !pst = do+ r <- extract pst+ case r of+ ParserD.FDone n b ->+ assert (n <= 0) (cont (Success n b) (count + n) None)+ ParserD.FContinue n pst1 ->+ assert (n <= 1)+ (return $ Continue n (parseCont (count + n) pst1))+ ParserD.FError err ->+ -- XXX It is called only when there is no input arr. So using 0+ -- as the position is correct?+ cont (Failure 0 err) count None++ {-# INLINE parseCont #-}+ parseCont !cnt !pst (Chunk arr) = parseContChunk cnt 0 pst arr+ parseCont !cnt !pst None = parseContNothing cnt pst++-- | Convert a 'Parser' to 'ParserK' working on generic Array stream.+--+-- /Pre-release/+--+{-# INLINE_LATE toParserK #-}+toParserK :: Monad m => ParserD.Parser a m b -> ParserK (Array a) m b+toParserK (ParserD.Parser step initial extract) =+ ParserK.MkParser $ adaptCGWith step initial extract
− src/Streamly/Internal/Data/Array/Mut.hs
@@ -1,86 +0,0 @@--- |--- Module : Streamly.Internal.Data.Array.Mut--- Copyright : (c) 2020 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC----module Streamly.Internal.Data.Array.Mut- (- module Streamly.Internal.Data.Array.Mut.Type- , splitOn- , genSlicesFromLen- , getSlicesFromLen- , fromStream- )-where--#include "inline.hs"--import Control.Monad.IO.Class (MonadIO(..))-import Streamly.Internal.Data.Unboxed (Unbox)-import Streamly.Internal.Data.Stream.StreamD (Stream)-import Streamly.Internal.Data.Unfold.Type (Unfold(..))--import qualified Streamly.Internal.Data.Stream.StreamD as D-import qualified Streamly.Internal.Data.Unfold as Unfold--import Prelude hiding (foldr, length, read, splitAt)-import Streamly.Internal.Data.Array.Mut.Type---- | Split the array into a stream of slices using a predicate. The element--- matching the predicate is dropped.------ /Pre-release/-{-# INLINE splitOn #-}-splitOn :: (MonadIO m, Unbox a) =>- (a -> Bool) -> MutArray a -> Stream m (MutArray a)-splitOn predicate arr =- fmap (\(i, len) -> getSliceUnsafe i len arr)- $ D.sliceOnSuffix predicate (toStreamD arr)---- | Generate a stream of array slice descriptors ((index, len)) of specified--- length from an array, starting from the supplied array index. The last slice--- may be shorter than the requested length depending on the array length.------ /Pre-release/-{-# INLINE genSlicesFromLen #-}-genSlicesFromLen :: forall m a. (Monad m, Unbox a)- => Int -- ^ from index- -> Int -- ^ length of the slice- -> Unfold m (MutArray a) (Int, Int)-genSlicesFromLen from len =- let fromThenTo n = (from, from + len, n - 1)- mkSlice n i = return (i, min len (n - i))- in Unfold.lmap length- $ Unfold.mapM2 mkSlice- $ Unfold.lmap fromThenTo Unfold.enumerateFromThenTo---- | Generate a stream of slices of specified length from an array, starting--- from the supplied array index. The last slice may be shorter than the--- requested length depending on the array length.------ /Pre-release/-{-# INLINE getSlicesFromLen #-}-getSlicesFromLen :: forall m a. (Monad m, Unbox a)- => Int -- ^ from index- -> Int -- ^ length of the slice- -> Unfold m (MutArray a) (MutArray a)-getSlicesFromLen from len =- let mkSlice arr (i, n) = return $ getSliceUnsafe i n arr- in Unfold.mapM2 mkSlice (genSlicesFromLen from len)---- | Create an 'Array' from a stream. This is useful when we want to create a--- single array from a stream of unknown size. 'writeN' is at least twice--- as efficient when the size is already known.------ Note that if the input stream is too large memory allocation for the array--- may fail. When the stream size is not known, `chunksOf` followed by--- processing of indvidual arrays in the resulting stream should be preferred.------ /Pre-release/-{-# INLINE fromStream #-}-fromStream :: (MonadIO m, Unbox a) => Stream m a -> m (MutArray a)-fromStream = fromStreamD--- fromStream (Stream m) = P.fold write m
− src/Streamly/Internal/Data/Array/Mut/Stream.hs
@@ -1,323 +0,0 @@--- |--- Module : Streamly.Internal.Data.Array.Mut.Stream--- Copyright : (c) 2019 Composewell Technologies--- License : BSD3-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ Combinators to efficiently manipulate streams of mutable arrays.----module Streamly.Internal.Data.Array.Mut.Stream- (- -- * Generation- chunksOf-- -- * Compaction- , packArraysChunksOf- , SpliceState (..)- , lpackArraysChunksOf- , compact- , compactLE- , compactEQ- , compactGE- )-where--#include "inline.hs"-#include "ArrayMacros.h"--import Control.Monad.IO.Class (MonadIO(..))-import Control.Monad (when)-import Data.Bifunctor (first)-import Data.Proxy (Proxy(..))-import Streamly.Internal.Data.Unboxed (Unbox, sizeOf)-import Streamly.Internal.Data.Array.Mut.Type (MutArray(..))-import Streamly.Internal.Data.Fold.Type (Fold(..))-import Streamly.Internal.Data.Parser (ParseError)-import Streamly.Internal.Data.Stream.StreamD.Type (Stream)-import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))--import qualified Streamly.Internal.Data.Array.Mut.Type as MArray-import qualified Streamly.Internal.Data.Fold.Type as FL-import qualified Streamly.Internal.Data.Stream.StreamD as D-import qualified Streamly.Internal.Data.Parser.ParserD as ParserD---- | @chunksOf n stream@ groups the elements in the input stream into arrays of--- @n@ elements each.------ Same as the following but may be more efficient:------ > chunksOf n = Stream.foldMany (MArray.writeN n)------ /Pre-release/-{-# INLINE chunksOf #-}-chunksOf :: (MonadIO m, Unbox a)- => Int -> Stream m a -> Stream m (MutArray a)-chunksOf = MArray.chunksOf------------------------------------------------------------------------------------ Compact----------------------------------------------------------------------------------data SpliceState s arr- = SpliceInitial s- | SpliceBuffering s arr- | SpliceYielding arr (SpliceState s arr)- | SpliceFinish---- XXX This can be removed once compactLEFold/compactLE are implemented.------ | This mutates the first array (if it has space) to append values from the--- second one. This would work for immutable arrays as well because an--- immutable array never has space so a new array is allocated instead of--- mutating it.------ | Coalesce adjacent arrays in incoming stream to form bigger arrays of a--- maximum specified size. Note that if a single array is bigger than the--- specified size we do not split it to fit. When we coalesce multiple arrays--- if the size would exceed the specified size we do not coalesce therefore the--- actual array size may be less than the specified chunk size.------ @since 0.7.0-{-# INLINE_NORMAL packArraysChunksOf #-}-packArraysChunksOf :: (MonadIO m, Unbox a)- => Int -> D.Stream m (MutArray a) -> D.Stream m (MutArray a)-packArraysChunksOf n (D.Stream step state) =- D.Stream step' (SpliceInitial state)-- where-- {-# INLINE_LATE step' #-}- step' gst (SpliceInitial st) = do- when (n <= 0) $- -- XXX we can pass the module string from the higher level API- error $ "Streamly.Internal.Data.Array.Mut.Type.packArraysChunksOf: the size of "- ++ "arrays [" ++ show n ++ "] must be a natural number"- r <- step gst st- case r of- D.Yield arr s -> return $- let len = MArray.byteLength arr- in if len >= n- then D.Skip (SpliceYielding arr (SpliceInitial s))- else D.Skip (SpliceBuffering s arr)- D.Skip s -> return $ D.Skip (SpliceInitial s)- D.Stop -> return D.Stop-- step' gst (SpliceBuffering st buf) = do- r <- step gst st- case r of- D.Yield arr s -> do- let len = MArray.byteLength buf + MArray.byteLength arr- if len > n- then return $- D.Skip (SpliceYielding buf (SpliceBuffering s arr))- else do- buf' <- if MArray.byteCapacity buf < n- then liftIO $ MArray.realloc n buf- else return buf- buf'' <- MArray.splice buf' arr- return $ D.Skip (SpliceBuffering s buf'')- D.Skip s -> return $ D.Skip (SpliceBuffering s buf)- D.Stop -> return $ D.Skip (SpliceYielding buf SpliceFinish)-- step' _ SpliceFinish = return D.Stop-- step' _ (SpliceYielding arr next) = return $ D.Yield arr next---- XXX Remove this once compactLEFold is implemented--- lpackArraysChunksOf = Fold.many compactLEFold----{-# INLINE_NORMAL lpackArraysChunksOf #-}-lpackArraysChunksOf :: (MonadIO m, Unbox a)- => Int -> Fold m (MutArray a) () -> Fold m (MutArray a) ()-lpackArraysChunksOf n (Fold step1 initial1 extract1) =- Fold step initial extract-- where-- initial = do- when (n <= 0) $- -- XXX we can pass the module string from the higher level API- error $ "Streamly.Internal.Data.Array.Mut.Type.packArraysChunksOf: the size of "- ++ "arrays [" ++ show n ++ "] must be a natural number"-- r <- initial1- return $ first (Tuple' Nothing) r-- extract (Tuple' Nothing r1) = extract1 r1- extract (Tuple' (Just buf) r1) = do- r <- step1 r1 buf- case r of- FL.Partial rr -> extract1 rr- FL.Done _ -> return ()-- step (Tuple' Nothing r1) arr =- let len = MArray.byteLength arr- in if len >= n- then do- r <- step1 r1 arr- case r of- FL.Done _ -> return $ FL.Done ()- FL.Partial s -> do- extract1 s- res <- initial1- return $ first (Tuple' Nothing) res- else return $ FL.Partial $ Tuple' (Just arr) r1-- step (Tuple' (Just buf) r1) arr = do- let len = MArray.byteLength buf + MArray.byteLength arr- buf' <- if MArray.byteCapacity buf < len- then liftIO $ MArray.realloc (max n len) buf- else return buf- buf'' <- MArray.splice buf' arr-- -- XXX this is common in both the equations of step- if len >= n- then do- r <- step1 r1 buf''- case r of- FL.Done _ -> return $ FL.Done ()- FL.Partial s -> do- extract1 s- res <- initial1- return $ first (Tuple' Nothing) res- else return $ FL.Partial $ Tuple' (Just buf'') r1---- XXX Same as compactLE, to be removed once that is implemented.------ | Coalesce adjacent arrays in incoming stream to form bigger arrays of a--- maximum specified size in bytes.------ /Internal/-{-# INLINE compact #-}-compact :: (MonadIO m, Unbox a)- => Int -> Stream m (MutArray a) -> Stream m (MutArray a)-compact = packArraysChunksOf---- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a--- maximum specified size. Note that if a single array is bigger than the--- specified size we do not split it to fit. When we coalesce multiple arrays--- if the size would exceed the specified size we do not coalesce therefore the--- actual array size may be less than the specified chunk size.------ /Internal/-{-# INLINE_NORMAL compactLEParserD #-}-compactLEParserD ::- forall m a. (MonadIO m, Unbox a)- => Int -> ParserD.Parser (MutArray a) m (MutArray a)-compactLEParserD n = ParserD.Parser step initial extract-- where-- nBytes = n * SIZE_OF(a)-- initial =- return- $ if n <= 0- then error- $ functionPath- ++ ": the size of arrays ["- ++ show n ++ "] must be a natural number"- else ParserD.IPartial Nothing-- step Nothing arr =- return- $ let len = MArray.byteLength arr- in if len >= nBytes- then ParserD.Done 0 arr- else ParserD.Partial 0 (Just arr)- step (Just buf) arr =- let len = MArray.byteLength buf + MArray.byteLength arr- in if len > nBytes- then return $ ParserD.Done 1 buf- else do- buf1 <-- if MArray.byteCapacity buf < nBytes- then liftIO $ MArray.realloc nBytes buf- else return buf- buf2 <- MArray.splice buf1 arr- return $ ParserD.Partial 0 (Just buf2)-- extract Nothing = return $ ParserD.Done 0 MArray.nil- extract (Just buf) = return $ ParserD.Done 0 buf-- functionPath =- "Streamly.Internal.Data.Array.Mut.Stream.compactLEParserD"---- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a--- minimum specified size. Note that if all the arrays in the stream together--- are smaller than the specified size the resulting array will be smaller than--- the specified size. When we coalesce multiple arrays if the size would exceed--- the specified size we stop coalescing further.------ /Internal/-{-# INLINE_NORMAL compactGEFold #-}-compactGEFold ::- forall m a. (MonadIO m, Unbox a)- => Int -> FL.Fold m (MutArray a) (MutArray a)-compactGEFold n = Fold step initial extract-- where-- nBytes = n * SIZE_OF(a)-- initial =- return- $ if n < 0- then error- $ functionPath- ++ ": the size of arrays ["- ++ show n ++ "] must be a natural number"- else FL.Partial Nothing-- step Nothing arr =- return- $ let len = MArray.byteLength arr- in if len >= nBytes- then FL.Done arr- else FL.Partial (Just arr)- step (Just buf) arr = do- let len = MArray.byteLength buf + MArray.byteLength arr- buf1 <-- if MArray.byteCapacity buf < len- then liftIO $ MArray.realloc (max len nBytes) buf- else return buf- buf2 <- MArray.splice buf1 arr- if len >= n- then return $ FL.Done buf2- else return $ FL.Partial (Just buf2)-- extract Nothing = return MArray.nil- extract (Just buf) = return buf-- functionPath =- "Streamly.Internal.Data.Array.Mut.Stream.compactGEFold"---- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a--- maximum specified size in bytes.------ /Internal/-compactLE :: (MonadIO m, Unbox a) =>- Int -> Stream m (MutArray a) -> Stream m (Either ParseError (MutArray a))-compactLE n = D.parseManyD (compactLEParserD n)---- | Like 'compactLE' but generates arrays of exactly equal to the size--- specified except for the last array in the stream which could be shorter.------ /Unimplemented/-{-# INLINE compactEQ #-}-compactEQ :: -- (MonadIO m, Unbox a) =>- Int -> Stream m (MutArray a) -> Stream m (MutArray a)-compactEQ _n _xs = undefined- -- IsStream.fromStreamD $ D.foldMany (compactEQFold n) (IsStream.toStreamD xs)---- | Like 'compactLE' but generates arrays of size greater than or equal to the--- specified except for the last array in the stream which could be shorter.------ /Internal/-{-# INLINE compactGE #-}-compactGE ::- (MonadIO m, Unbox a)- => Int -> Stream m (MutArray a) -> Stream m (MutArray a)-compactGE n = D.foldMany (compactGEFold n)
− src/Streamly/Internal/Data/Array/Mut/Type.hs
@@ -1,2356 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE UnboxedTuples #-}--- |--- Module : Streamly.Internal.Data.Array.Mut.Type--- Copyright : (c) 2020 Composewell Technologies--- License : BSD3-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ Pinned and unpinned mutable array for 'Unboxed' types. Fulfils the following--- goals:------ * Random access (array)--- * Efficient storage (unboxed)--- * Performance (unboxed access)--- * Performance - in-place operations (mutable)--- * Performance - GC (pinned, mutable)--- * interfacing with OS (pinned)------ Stream and Fold APIs allow easy, efficient and convenient operations on--- arrays.------ Mutable arrays and file system files are quite similar, they can grow and--- their content is mutable. Therefore, both have similar APIs as well. We--- strive to keep the API consistent for both. Ideally, you should be able to--- replace one with another with little changes to the code.--module Streamly.Internal.Data.Array.Mut.Type- (- -- * Type- -- $arrayNotes- MutArray (..)- , MutableByteArray- , touch- , pin- , unpin-- -- * Constructing and Writing- -- ** Construction- , nil-- -- *** Uninitialized Arrays- , newPinned- , newPinnedBytes- , newAlignedPinned- , new- , newArrayWith-- -- *** Initialized Arrays- , withNewArrayUnsafe-- -- *** From streams- , ArrayUnsafe (..)- , writeNWithUnsafe- , writeNWith- , writeNUnsafe- , writeN- , writeNAligned-- , writeWith- , write-- , writeRevN- -- , writeRev-- -- ** From containers- , fromListN- , fromList- , fromListRevN- , fromListRev- , fromStreamDN- , fromStreamD-- -- * Random writes- , putIndex- , putIndexUnsafe- , putIndices- -- , putFromThenTo- -- , putFrom -- start writing at the given position- -- , putUpto -- write from beginning up to the given position- -- , putFromTo- -- , putFromRev- -- , putUptoRev- , modifyIndexUnsafe- , modifyIndex- , modifyIndices- , modify- , swapIndices- , unsafeSwapIndices-- -- * Growing and Shrinking- -- Arrays grow only at the end, though it is possible to grow on both sides- -- and therefore have a cons as well as snoc. But that will require two- -- bounds in the array representation.-- -- ** Appending elements- , snocWith- , snoc- , snocLinear- , snocMay- , snocUnsafe-- -- ** Appending streams- , writeAppendNUnsafe- , writeAppendN- , writeAppendWith- , writeAppend-- -- * Eliminating and Reading-- -- ** To streams- , reader- , readerRevWith- , readerRev-- -- ** To containers- , toStreamDWith- , toStreamDRevWith- , toStreamKWith- , toStreamKRevWith- , toStreamD- , toStreamDRev- , toStreamK- , toStreamKRev- , toList-- -- experimental- , producerWith- , producer-- -- ** Random reads- , getIndex- , getIndexUnsafe- , getIndices- , getIndicesD- -- , getFromThenTo- , getIndexRev-- -- * Memory Management- , blockSize- , arrayChunkBytes- , allocBytesToElemCount- , realloc- , resize- , resizeExp- , rightSize-- -- * Size- , length- , byteLength- -- , capacity- , byteCapacity- , bytesFree-- -- * In-place Mutation Algorithms- , strip- , reverse- , permute- , partitionBy- , shuffleBy- , divideBy- , mergeBy- , bubble-- -- * Casting- , cast- , castUnsafe- , asBytes- , asPtrUnsafe-- -- * Folding- , foldl'- , foldr- , cmp-- -- * Arrays of arrays- -- We can add dimensionality parameter to the array type to get- -- multidimensional arrays. Multidimensional arrays would just be a- -- convenience wrapper on top of single dimensional arrays.-- -- | Operations dealing with multiple arrays, streams of arrays or- -- multidimensional array representations.-- -- ** Construct from streams- , chunksOf- , arrayStreamKFromStreamD- , writeChunks-- -- ** Eliminate to streams- , flattenArrays- , flattenArraysRev- , fromArrayStreamK-- -- ** Construct from arrays- -- get chunks without copying- , getSliceUnsafe- , getSlice- -- , getSlicesFromLenN- , splitAt -- XXX should be able to express using getSlice- , breakOn-- -- ** Appending arrays- , spliceCopy- , spliceWith- , splice- , spliceExp- , spliceUnsafe- , putSliceUnsafe- -- , putSlice- -- , appendSlice- -- , appendSliceFrom-- -- * Utilities- , roundUpToPower2- , memcpy- , memcmp- , c_memchr- )-where--#include "assert.hs"-#include "inline.hs"-#include "ArrayMacros.h"-#include "MachDeps.h"--import Control.Monad (when, void)-import Control.Monad.IO.Class (MonadIO(..))-import Data.Bits (shiftR, (.|.), (.&.))-import Data.Proxy (Proxy(..))-import Data.Word (Word8)-import Foreign.C.Types (CSize(..), CInt(..))-import Foreign.Ptr (plusPtr, minusPtr, nullPtr)-import Streamly.Internal.Data.Unboxed- ( MutableByteArray(..)- , Unbox- , getMutableByteArray#- , peekWith- , pokeWith- , sizeOf- , touch- )-import GHC.Base- ( IO(..)- , Int(..)- , byteArrayContents#- , compareByteArrays#- , copyMutableByteArray#- )-import GHC.Base (noinline)-import GHC.Exts (unsafeCoerce#)-import GHC.Ptr (Ptr(..))--import Streamly.Internal.Data.Fold.Type (Fold(..))-import Streamly.Internal.Data.Producer.Type (Producer (..))-import Streamly.Internal.Data.Stream.StreamD.Type (Stream)-import Streamly.Internal.Data.Stream.StreamK.Type (StreamK)-import Streamly.Internal.Data.SVar.Type (adaptState, defState)-import Streamly.Internal.Data.Unfold.Type (Unfold(..))-import Streamly.Internal.System.IO (arrayPayloadSize, defaultChunkSize)--import qualified Streamly.Internal.Data.Fold.Type as FL-import qualified Streamly.Internal.Data.Producer as Producer-import qualified Streamly.Internal.Data.Stream.StreamD.Type as D-import qualified Streamly.Internal.Data.Stream.StreamK.Type as K-import qualified Streamly.Internal.Data.Unboxed as Unboxed-import qualified Prelude--import Prelude hiding- (length, foldr, read, unlines, splitAt, reverse, truncate)--#include "DocTestDataMutArray.hs"------------------------------------------------------------------------------------ Foreign helpers----------------------------------------------------------------------------------foreign import ccall unsafe "string.h memcpy" c_memcpy- :: Ptr Word8 -> Ptr Word8 -> CSize -> IO (Ptr Word8)--foreign import ccall unsafe "string.h memchr" c_memchr- :: Ptr Word8 -> Word8 -> CSize -> IO (Ptr Word8)--foreign import ccall unsafe "string.h memcmp" c_memcmp- :: Ptr Word8 -> Ptr Word8 -> CSize -> IO CInt---- | Given an 'Unboxed' type (unused first arg) and a number of bytes, return--- how many elements of that type will completely fit in those bytes.----{-# INLINE bytesToElemCount #-}-bytesToElemCount :: forall a. Unbox a => a -> Int -> Int-bytesToElemCount _ n = n `div` SIZE_OF(a)---- XXX we are converting Int to CSize-memcpy :: Ptr Word8 -> Ptr Word8 -> Int -> IO ()-memcpy dst src len = void (c_memcpy dst src (fromIntegral len))---- XXX we are converting Int to CSize--- return True if the memory locations have identical contents-{-# INLINE memcmp #-}-memcmp :: Ptr Word8 -> Ptr Word8 -> Int -> IO Bool-memcmp p1 p2 len = do- r <- c_memcmp p1 p2 (fromIntegral len)- return $ r == 0------------------------------------------------------------------------------------ MutArray Data Type------------------------------------------------------------------------------------ $arrayNotes------ We can use an 'Unboxed' constraint in the MutArray type and the constraint--- can be automatically provided to a function that pattern matches on the--- MutArray type. However, it has huge performance cost, so we do not use it.--- Investigate a GHC improvement possiblity.---- | An unboxed mutable array. An array is created with a given length--- and capacity. Length is the number of valid elements in the array. Capacity--- is the maximum number of elements that the array can be expanded to without--- having to reallocate the memory.------ The elements in the array can be mutated in-place without changing the--- reference (constructor). However, the length of the array cannot be mutated--- in-place. A new array reference is generated when the length changes. When--- the length is increased (upto the maximum reserved capacity of the array),--- the array is not reallocated and the new reference uses the same underlying--- memory as the old one.------ Several routines in this module allow the programmer to control the capacity--- of the array. The programmer can control the trade-off between memory usage--- and performance impact due to reallocations when growing or shrinking the--- array.----data MutArray a =-#ifdef DEVBUILD- Unbox a =>-#endif- -- The array is a range into arrContents. arrContents may be a superset of- -- the slice represented by the array. All offsets are in bytes.- MutArray- { arrContents :: {-# UNPACK #-} !MutableByteArray- , arrStart :: {-# UNPACK #-} !Int -- ^ index into arrContents- , arrEnd :: {-# UNPACK #-} !Int -- ^ index into arrContents- -- Represents the first invalid index of- -- the array.- , arrBound :: {-# UNPACK #-} !Int -- ^ first invalid index of arrContents.- }------------------------------------------------------------------------------------ Pinning & Unpinning----------------------------------------------------------------------------------{-# INLINE pin #-}-pin :: MutArray a -> IO (MutArray a)-pin arr@MutArray{..} = do- contents <- Unboxed.pin arrContents- return $ arr {arrContents = contents}--{-# INLINE unpin #-}-unpin :: MutArray a -> IO (MutArray a)-unpin arr@MutArray{..} = do- contents <- Unboxed.unpin arrContents- return $ arr {arrContents = contents}------------------------------------------------------------------------------------ Construction------------------------------------------------------------------------------------ XXX Change the names to use "new" instead of "newArray". That way we can use--- the same names for managed file system objects as well. For unmanaged ones--- we can use open/create etc as usual.------ A new array is similar to "touch" creating a zero length file. An mmapped--- array would be similar to a sparse file with holes. TBD: support mmapped--- files and arrays.---- GHC always guarantees word-aligned memory, alignment is important only when--- we need more than that. See stg_newAlignedPinnedByteArrayzh and--- allocatePinned in GHC source.---- | @newArrayWith allocator alignment count@ allocates a new array of zero--- length and with a capacity to hold @count@ elements, using @allocator--- size alignment@ as the memory allocator function.------ Alignment must be greater than or equal to machine word size and a power of--- 2.------ Alignment is ignored if the allocator allocates unpinned memory.------ /Pre-release/-{-# INLINE newArrayWith #-}-newArrayWith :: forall m a. (MonadIO m, Unbox a)- => (Int -> Int -> m MutableByteArray) -> Int -> Int -> m (MutArray a)-newArrayWith alloc alignSize count = do- let size = max (count * SIZE_OF(a)) 0- contents <- alloc size alignSize- return $ MutArray- { arrContents = contents- , arrStart = 0- , arrEnd = 0- , arrBound = size- }--nil ::-#ifdef DEVBUILD- Unbox a =>-#endif- MutArray a-nil = MutArray Unboxed.nil 0 0 0----- | Allocates a pinned empty array that can hold 'count' items. The memory of--- the array is uninitialized and the allocation is aligned as per the--- 'Unboxed' instance of the type.------ /Pre-release/-{-# INLINE newPinnedBytes #-}-newPinnedBytes :: MonadIO m =>-#ifdef DEVBUILD- Unbox a =>-#endif- Int -> m (MutArray a)-newPinnedBytes bytes = do- contents <- liftIO $ Unboxed.newPinnedBytes bytes- return $ MutArray- { arrContents = contents- , arrStart = 0- , arrEnd = 0- , arrBound = bytes- }---- | Like 'newArrayWith' but using an allocator is a pinned memory allocator and--- the alignment is dictated by the 'Unboxed' instance of the type.------ /Internal/-{-# INLINE newAlignedPinned #-}-newAlignedPinned :: (MonadIO m, Unbox a) => Int -> Int -> m (MutArray a)-newAlignedPinned =- newArrayWith (\s a -> liftIO $ Unboxed.newAlignedPinnedBytes s a)---- XXX can unaligned allocation be more efficient when alignment is not needed?------ | Allocates an empty pinned array that can hold 'count' items. The memory of--- the array is uninitialized and the allocation is aligned as per the 'Unboxed'--- instance of the type.----{-# INLINE newPinned #-}-newPinned :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a)-newPinned =- newArrayWith- (\s _ -> liftIO $ Unboxed.newPinnedBytes s)- (error "newPinned: alignSize is not used")---- | Allocates an empty unpinned array that can hold 'count' items. The memory--- of the array is uninitialized.----{-# INLINE new #-}-new :: (MonadIO m, Unbox a) => Int -> m (MutArray a)-new =- newArrayWith- (\s _ -> liftIO $ Unboxed.newUnpinnedBytes s)- (error "new: alignment is not used in unpinned arrays.")---- XXX This should create a full length uninitialzed array so that the pointer--- can be used.---- | Allocate a pinned MutArray of the given size and run an IO action passing--- the array start pointer.------ /Internal/-{-# INLINE withNewArrayUnsafe #-}-withNewArrayUnsafe ::- (MonadIO m, Unbox a) => Int -> (Ptr a -> m ()) -> m (MutArray a)-withNewArrayUnsafe count f = do- arr <- newPinned count- asPtrUnsafe arr- $ \p -> f p >> return arr------------------------------------------------------------------------------------ Random writes------------------------------------------------------------------------------------ | Write the given element to the given index of the array. Does not check if--- the index is out of bounds of the array.------ /Pre-release/-{-# INLINE putIndexUnsafe #-}-putIndexUnsafe :: forall m a. (MonadIO m, Unbox a)- => Int -> MutArray a -> a -> m ()-putIndexUnsafe i MutArray{..} x = do- let index = INDEX_OF(arrStart, i, a)- assert (i >= 0 && INDEX_VALID(index, arrEnd, a)) (return ())- liftIO $ pokeWith arrContents index x--invalidIndex :: String -> Int -> a-invalidIndex label i =- error $ label ++ ": invalid array index " ++ show i---- | /O(1)/ Write the given element at the given index in the array.--- Performs in-place mutation of the array.------ >>> putIndex ix arr val = MutArray.modifyIndex ix arr (const (val, ()))--- >>> f = MutArray.putIndices--- >>> putIndex ix arr val = Stream.fold (f arr) (Stream.fromPure (ix, val))----{-# INLINE putIndex #-}-putIndex :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> a -> m ()-putIndex i MutArray{..} x = do- let index = INDEX_OF(arrStart,i,a)- if i >= 0 && INDEX_VALID(index,arrEnd,a)- then liftIO $ pokeWith arrContents index x- else invalidIndex "putIndex" i---- | Write an input stream of (index, value) pairs to an array. Throws an--- error if any index is out of bounds.------ /Pre-release/-{-# INLINE putIndices #-}-putIndices :: forall m a. (MonadIO m, Unbox a)- => MutArray a -> Fold m (Int, a) ()-putIndices arr = FL.foldlM' step (return ())-- where-- step () (i, x) = liftIO (putIndex i arr x)---- | Modify a given index of an array using a modifier function.------ /Pre-release/-modifyIndexUnsafe :: forall m a b. (MonadIO m, Unbox a) =>- Int -> MutArray a -> (a -> (a, b)) -> m b-modifyIndexUnsafe i MutArray{..} f = liftIO $ do- let index = INDEX_OF(arrStart,i,a)- assert (i >= 0 && INDEX_NEXT(index,a) <= arrEnd) (return ())- r <- peekWith arrContents index- let (x, res) = f r- pokeWith arrContents index x- return res---- | Modify a given index of an array using a modifier function.------ /Pre-release/-modifyIndex :: forall m a b. (MonadIO m, Unbox a) =>- Int -> MutArray a -> (a -> (a, b)) -> m b-modifyIndex i MutArray{..} f = do- let index = INDEX_OF(arrStart,i,a)- if i >= 0 && INDEX_VALID(index,arrEnd,a)- then liftIO $ do- r <- peekWith arrContents index- let (x, res) = f r- pokeWith arrContents index x- return res- else invalidIndex "modifyIndex" i----- | Modify the array indices generated by the supplied stream.------ /Pre-release/-{-# INLINE modifyIndices #-}-modifyIndices :: forall m a . (MonadIO m, Unbox a)- => MutArray a -> (Int -> a -> a) -> Fold m Int ()-modifyIndices arr f = FL.foldlM' step initial-- where-- initial = return ()-- step () i =- let f1 x = (f i x, ())- in modifyIndex i arr f1---- | Modify each element of an array using the supplied modifier function.------ /Pre-release/-modify :: forall m a. (MonadIO m, Unbox a)- => MutArray a -> (a -> a) -> m ()-modify MutArray{..} f = liftIO $- go arrStart-- where-- go i =- when (INDEX_VALID(i,arrEnd,a)) $ do- r <- peekWith arrContents i- pokeWith arrContents i (f r)- go (INDEX_NEXT(i,a))---- XXX We could specify the number of bytes to swap instead of Proxy. Need--- to ensure that the memory does not overlap.-{-# INLINE swapArrayByteIndices #-}-swapArrayByteIndices ::- forall a. Unbox a- => Proxy a- -> MutableByteArray- -> Int- -> Int- -> IO ()-swapArrayByteIndices _ arrContents i1 i2 = do- r1 <- peekWith arrContents i1- r2 <- peekWith arrContents i2- pokeWith arrContents i1 (r2 :: a)- pokeWith arrContents i2 (r1 :: a)---- | Swap the elements at two indices without validating the indices.------ /Unsafe/: This could result in memory corruption if indices are not valid.------ /Pre-release/-{-# INLINE unsafeSwapIndices #-}-unsafeSwapIndices :: forall m a. (MonadIO m, Unbox a)- => Int -> Int -> MutArray a -> m ()-unsafeSwapIndices i1 i2 MutArray{..} = liftIO $ do- let t1 = INDEX_OF(arrStart,i1,a)- t2 = INDEX_OF(arrStart,i2,a)- swapArrayByteIndices (Proxy :: Proxy a) arrContents t1 t2---- | Swap the elements at two indices.------ /Pre-release/-swapIndices :: forall m a. (MonadIO m, Unbox a)- => Int -> Int -> MutArray a -> m ()-swapIndices i1 i2 MutArray{..} = liftIO $ do- let t1 = INDEX_OF(arrStart,i1,a)- t2 = INDEX_OF(arrStart,i2,a)- when (i1 < 0 || INDEX_INVALID(t1,arrEnd,a))- $ invalidIndex "swapIndices" i1- when (i2 < 0 || INDEX_INVALID(t2,arrEnd,a))- $ invalidIndex "swapIndices" i2- swapArrayByteIndices (Proxy :: Proxy a) arrContents t1 t2------------------------------------------------------------------------------------ Rounding------------------------------------------------------------------------------------ XXX Should we use bitshifts in calculations or it gets optimized by the--- compiler/processor itself?------ | The page or block size used by the GHC allocator. Allocator allocates at--- least a block and then allocates smaller allocations from within a block.-blockSize :: Int-blockSize = 4 * 1024---- | Allocations larger than 'largeObjectThreshold' are in multiples of block--- size and are always pinned. The space beyond the end of a large object up to--- the end of the block is unused.-largeObjectThreshold :: Int-largeObjectThreshold = (blockSize * 8) `div` 10---- XXX Should be done only when we are using the GHC allocator.--- | Round up an array larger than 'largeObjectThreshold' to use the whole--- block.-{-# INLINE roundUpLargeArray #-}-roundUpLargeArray :: Int -> Int-roundUpLargeArray size =- if size >= largeObjectThreshold- then- assert- (blockSize /= 0 && ((blockSize .&. (blockSize - 1)) == 0))- ((size + blockSize - 1) .&. negate blockSize)- else size--{-# INLINE isPower2 #-}-isPower2 :: Int -> Bool-isPower2 n = n .&. (n - 1) == 0--{-# INLINE roundUpToPower2 #-}-roundUpToPower2 :: Int -> Int-roundUpToPower2 n =-#if WORD_SIZE_IN_BITS == 64- 1 + z6-#else- 1 + z5-#endif-- where-- z0 = n - 1- z1 = z0 .|. z0 `shiftR` 1- z2 = z1 .|. z1 `shiftR` 2- z3 = z2 .|. z2 `shiftR` 4- z4 = z3 .|. z3 `shiftR` 8- z5 = z4 .|. z4 `shiftR` 16- z6 = z5 .|. z5 `shiftR` 32---- | @allocBytesToBytes elem allocatedBytes@ returns the array size in bytes--- such that the real allocation is less than or equal to @allocatedBytes@,--- unless @allocatedBytes@ is less than the size of one array element in which--- case it returns one element's size.----{-# INLINE allocBytesToBytes #-}-allocBytesToBytes :: forall a. Unbox a => a -> Int -> Int-allocBytesToBytes _ n = max (arrayPayloadSize n) (SIZE_OF(a))---- | Given an 'Unboxed' type (unused first arg) and real allocation size--- (including overhead), return how many elements of that type will completely--- fit in it, returns at least 1.----{-# INLINE allocBytesToElemCount #-}-allocBytesToElemCount :: Unbox a => a -> Int -> Int-allocBytesToElemCount x bytes =- let n = bytesToElemCount x (allocBytesToBytes x bytes)- in assert (n >= 1) n---- | The default chunk size by which the array creation routines increase the--- size of the array when the array is grown linearly.-arrayChunkBytes :: Int-arrayChunkBytes = 1024------------------------------------------------------------------------------------ Resizing------------------------------------------------------------------------------------ | Round the second argument down to multiples of the first argument.-{-# INLINE roundDownTo #-}-roundDownTo :: Int -> Int -> Int-roundDownTo elemSize size = size - (size `mod` elemSize)---- XXX See if resizing can be implemented by reading the old array as a stream--- and then using writeN to the new array.------ NOTE: we are passing elemSize explicitly to avoid an Unboxed constraint.--- Since this is not inlined Unboxed consrraint leads to dictionary passing--- which complicates some inspection tests.----{-# NOINLINE reallocExplicit #-}-reallocExplicit :: Int -> Int -> MutArray a -> IO (MutArray a)-reallocExplicit elemSize newCapacityInBytes MutArray{..} = do- assertM(arrEnd <= arrBound)-- -- Allocate new array- let newCapMaxInBytes = roundUpLargeArray newCapacityInBytes- contents <- Unboxed.newPinnedBytes newCapMaxInBytes- let !(MutableByteArray mbarrFrom#) = arrContents- !(MutableByteArray mbarrTo#) = contents-- -- Copy old data- let oldStart = arrStart- !(I# oldStartInBytes#) = oldStart- oldSizeInBytes = arrEnd - oldStart- newCapInBytes = roundDownTo elemSize newCapMaxInBytes- !newLenInBytes@(I# newLenInBytes#) = min oldSizeInBytes newCapInBytes- assert (oldSizeInBytes `mod` elemSize == 0) (return ())- assert (newLenInBytes >= 0) (return ())- assert (newLenInBytes `mod` elemSize == 0) (return ())- IO $ \s# -> (# copyMutableByteArray# mbarrFrom# oldStartInBytes#- mbarrTo# 0# newLenInBytes# s#, () #)-- return $ MutArray- { arrStart = 0- , arrContents = contents- , arrEnd = newLenInBytes- , arrBound = newCapInBytes- }---- | @realloc newCapacity array@ reallocates the array to the specified--- capacity in bytes.------ If the new size is less than the original array the array gets truncated.--- If the new size is not a multiple of array element size then it is rounded--- down to multiples of array size. If the new size is more than--- 'largeObjectThreshold' then it is rounded up to the block size (4K).----{-# INLINABLE realloc #-}-realloc :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m (MutArray a)-realloc bytes arr = liftIO $ reallocExplicit (SIZE_OF(a)) bytes arr---- | @reallocWith label capSizer minIncrBytes array@. The label is used--- in error messages and the capSizer is used to determine the capacity of the--- new array in bytes given the current byte length of the array.-reallocWith :: forall m a. (MonadIO m , Unbox a) =>- String- -> (Int -> Int)- -> Int- -> MutArray a- -> m (MutArray a)-reallocWith label capSizer minIncrBytes arr = do- let oldSizeBytes = arrEnd arr - arrStart arr- newCapBytes = capSizer oldSizeBytes- newSizeBytes = oldSizeBytes + minIncrBytes- safeCapBytes = max newCapBytes newSizeBytes- assertM(safeCapBytes >= newSizeBytes || error (badSize newSizeBytes))-- realloc safeCapBytes arr-- where-- badSize newSize =- concat- [ label- , ": new array size (in bytes) is less than required size "- , show newSize- , ". Please check the sizing function passed."- ]---- | @resize newCapacity array@ changes the total capacity of the array so that--- it is enough to hold the specified number of elements. Nothing is done if--- the specified capacity is less than the length of the array.------ If the capacity is more than 'largeObjectThreshold' then it is rounded up to--- the block size (4K).------ /Pre-release/-{-# INLINE resize #-}-resize :: forall m a. (MonadIO m, Unbox a) =>- Int -> MutArray a -> m (MutArray a)-resize nElems arr@MutArray{..} = do- let req = SIZE_OF(a) * nElems- len = arrEnd - arrStart- if req < len- then return arr- else realloc req arr---- | Like 'resize' but if the byte capacity is more than 'largeObjectThreshold'--- then it is rounded up to the closest power of 2.------ /Pre-release/-{-# INLINE resizeExp #-}-resizeExp :: forall m a. (MonadIO m, Unbox a) =>- Int -> MutArray a -> m (MutArray a)-resizeExp nElems arr@MutArray{..} = do- let req = roundUpLargeArray (SIZE_OF(a) * nElems)- req1 =- if req > largeObjectThreshold- then roundUpToPower2 req- else req- len = arrEnd - arrStart- if req1 < len- then return arr- else realloc req1 arr---- | Resize the allocated memory to drop any reserved free space at the end of--- the array and reallocate it to reduce wastage.------ Up to 25% wastage is allowed to avoid reallocations. If the capacity is--- more than 'largeObjectThreshold' then free space up to the 'blockSize' is--- retained.------ /Pre-release/-{-# INLINE rightSize #-}-rightSize :: forall m a. (MonadIO m, Unbox a) => MutArray a -> m (MutArray a)-rightSize arr@MutArray{..} = do- assert (arrEnd <= arrBound) (return ())- let start = arrStart- len = arrEnd - start- capacity = arrBound - start- target = roundUpLargeArray len- waste = arrBound - arrEnd- assert (target >= len) (return ())- assert (len `mod` SIZE_OF(a) == 0) (return ())- -- We trade off some wastage (25%) to avoid reallocations and copying.- if target < capacity && len < 3 * waste- then realloc target arr- else return arr------------------------------------------------------------------------------------ Snoc------------------------------------------------------------------------------------ XXX We can possibly use a smallMutableByteArray to hold the start, end,--- bound pointers. Using fully mutable handle will ensure that we do not have--- multiple references to the same array of different lengths lying around and--- potentially misused. In that case "snoc" need not return a new array (snoc--- :: MutArray a -> a -> m ()), it will just modify the old reference. The array--- length will be mutable. This means the length function would also be--- monadic. Mutable arrays would behave more like files that grow in that--- case.---- | Snoc using a 'Ptr'. Low level reusable function.------ /Internal/-{-# INLINE snocNewEnd #-}-snocNewEnd :: (MonadIO m, Unbox a) => Int -> MutArray a -> a -> m (MutArray a)-snocNewEnd newEnd arr@MutArray{..} x = liftIO $ do- assert (newEnd <= arrBound) (return ())- pokeWith arrContents arrEnd x- return $ arr {arrEnd = newEnd}---- | Really really unsafe, appends the element into the first array, may--- cause silent data corruption or if you are lucky a segfault if the first--- array does not have enough space to append the element.------ /Internal/-{-# INLINE snocUnsafe #-}-snocUnsafe :: forall m a. (MonadIO m, Unbox a) =>- MutArray a -> a -> m (MutArray a)-snocUnsafe arr@MutArray{..} = snocNewEnd (INDEX_NEXT(arrEnd,a)) arr---- | Like 'snoc' but does not reallocate when pre-allocated array capacity--- becomes full.------ /Internal/-{-# INLINE snocMay #-}-snocMay :: forall m a. (MonadIO m, Unbox a) =>- MutArray a -> a -> m (Maybe (MutArray a))-snocMay arr@MutArray{..} x = liftIO $ do- let newEnd = INDEX_NEXT(arrEnd,a)- if newEnd <= arrBound- then Just <$> snocNewEnd newEnd arr x- else return Nothing---- NOINLINE to move it out of the way and not pollute the instruction cache.-{-# NOINLINE snocWithRealloc #-}-snocWithRealloc :: forall m a. (MonadIO m, Unbox a) =>- (Int -> Int)- -> MutArray a- -> a- -> m (MutArray a)-snocWithRealloc sizer arr x = do- arr1 <- liftIO $ reallocWith "snocWith" sizer (SIZE_OF(a)) arr- snocUnsafe arr1 x---- | @snocWith sizer arr elem@ mutates @arr@ to append @elem@. The length of--- the array increases by 1.------ If there is no reserved space available in @arr@ it is reallocated to a size--- in bytes determined by the @sizer oldSizeBytes@ function, where--- @oldSizeBytes@ is the original size of the array in bytes.------ If the new array size is more than 'largeObjectThreshold' we automatically--- round it up to 'blockSize'.------ Note that the returned array may be a mutated version of the original array.------ /Pre-release/-{-# INLINE snocWith #-}-snocWith :: forall m a. (MonadIO m, Unbox a) =>- (Int -> Int)- -> MutArray a- -> a- -> m (MutArray a)-snocWith allocSize arr x = liftIO $ do- let newEnd = INDEX_NEXT(arrEnd arr,a)- if newEnd <= arrBound arr- then snocNewEnd newEnd arr x- else snocWithRealloc allocSize arr x---- | The array is mutated to append an additional element to it. If there--- is no reserved space available in the array then it is reallocated to grow--- it by 'arrayChunkBytes' rounded up to 'blockSize' when the size becomes more--- than 'largeObjectThreshold'.------ Note that the returned array may be a mutated version of the original array.------ Performs O(n^2) copies to grow but is thrifty on memory.------ /Pre-release/-{-# INLINE snocLinear #-}-snocLinear :: forall m a. (MonadIO m, Unbox a) => MutArray a -> a -> m (MutArray a)-snocLinear = snocWith (+ allocBytesToBytes (undefined :: a) arrayChunkBytes)---- | The array is mutated to append an additional element to it. If there is no--- reserved space available in the array then it is reallocated to double the--- original size.------ This is useful to reduce allocations when appending unknown number of--- elements.------ Note that the returned array may be a mutated version of the original array.------ >>> snoc = MutArray.snocWith (* 2)------ Performs O(n * log n) copies to grow, but is liberal with memory allocation.----{-# INLINE snoc #-}-snoc :: forall m a. (MonadIO m, Unbox a) => MutArray a -> a -> m (MutArray a)-snoc = snocWith f-- where-- f oldSize =- if isPower2 oldSize- then oldSize * 2- else roundUpToPower2 oldSize * 2------------------------------------------------------------------------------------ Random reads------------------------------------------------------------------------------------ XXX Can this be deduplicated with array/foreign---- | Return the element at the specified index without checking the bounds.------ Unsafe because it does not check the bounds of the array.-{-# INLINE_NORMAL getIndexUnsafe #-}-getIndexUnsafe :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m a-getIndexUnsafe i MutArray{..} = do- let index = INDEX_OF(arrStart,i,a)- assert (i >= 0 && INDEX_VALID(index,arrEnd,a)) (return ())- liftIO $ peekWith arrContents index---- | /O(1)/ Lookup the element at the given index. Index starts from 0.----{-# INLINE getIndex #-}-getIndex :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m a-getIndex i MutArray{..} = do- let index = INDEX_OF(arrStart,i,a)- if i >= 0 && INDEX_VALID(index,arrEnd,a)- then liftIO $ peekWith arrContents index- else invalidIndex "getIndex" i---- | /O(1)/ Lookup the element at the given index from the end of the array.--- Index starts from 0.------ Slightly faster than computing the forward index and using getIndex.----{-# INLINE getIndexRev #-}-getIndexRev :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m a-getIndexRev i MutArray{..} = do- let index = RINDEX_OF(arrEnd,i,a)- if i >= 0 && index >= arrStart- then liftIO $ peekWith arrContents index- else invalidIndex "getIndexRev" i--data GetIndicesState contents start end st =- GetIndicesState contents start end st---- | Given an unfold that generates array indices, read the elements on those--- indices from the supplied MutArray. An error is thrown if an index is out of--- bounds.------ /Pre-release/-{-# INLINE getIndicesD #-}-getIndicesD :: (Monad m, Unbox a) =>- (forall b. IO b -> m b) -> D.Stream m Int -> Unfold m (MutArray a) a-getIndicesD liftio (D.Stream stepi sti) = Unfold step inject-- where-- inject (MutArray contents start end _) =- return $ GetIndicesState contents start end sti-- {-# INLINE_LATE step #-}- step (GetIndicesState contents start end st) = do- r <- stepi defState st- case r of- D.Yield i s -> do- x <- liftio $ getIndex i (MutArray contents start end undefined)- return $ D.Yield x (GetIndicesState contents start end s)- D.Skip s -> return $ D.Skip (GetIndicesState contents start end s)- D.Stop -> return D.Stop--{-# INLINE getIndices #-}-getIndices :: (MonadIO m, Unbox a) => Stream m Int -> Unfold m (MutArray a) a-getIndices = getIndicesD liftIO------------------------------------------------------------------------------------ Subarrays------------------------------------------------------------------------------------ XXX We can also get immutable slices.---- | /O(1)/ Slice an array in constant time.------ Unsafe: The bounds of the slice are not checked.------ /Unsafe/------ /Pre-release/-{-# INLINE getSliceUnsafe #-}-getSliceUnsafe :: forall a. Unbox a- => Int -- ^ from index- -> Int -- ^ length of the slice- -> MutArray a- -> MutArray a-getSliceUnsafe index len (MutArray contents start e _) =- let fp1 = INDEX_OF(start,index,a)- end = fp1 + (len * SIZE_OF(a))- in assert- (index >= 0 && len >= 0 && end <= e)- -- Note: In a slice we always use bound = end so that the slice- -- user cannot overwrite elements beyond the end of the slice.- (MutArray contents fp1 end end)---- | /O(1)/ Slice an array in constant time. Throws an error if the slice--- extends out of the array bounds.------ /Pre-release/-{-# INLINE getSlice #-}-getSlice :: forall a. Unbox a =>- Int -- ^ from index- -> Int -- ^ length of the slice- -> MutArray a- -> MutArray a-getSlice index len (MutArray contents start e _) =- let fp1 = INDEX_OF(start,index,a)- end = fp1 + (len * SIZE_OF(a))- in if index >= 0 && len >= 0 && end <= e- -- Note: In a slice we always use bound = end so that the slice user- -- cannot overwrite elements beyond the end of the slice.- then MutArray contents fp1 end end- else error- $ "getSlice: invalid slice, index "- ++ show index ++ " length " ++ show len------------------------------------------------------------------------------------ In-place mutation algorithms------------------------------------------------------------------------------------ XXX consider the bulk update/accumulation/permutation APIs from vector.---- | You may not need to reverse an array because you can consume it in reverse--- using 'readerRev'. To reverse large arrays you can read in reverse and write--- to another array. However, in-place reverse can be useful to take adavantage--- of cache locality and when you do not want to allocate additional memory.----{-# INLINE reverse #-}-reverse :: forall m a. (MonadIO m, Unbox a) => MutArray a -> m ()-reverse MutArray{..} = liftIO $ do- let l = arrStart- h = INDEX_PREV(arrEnd,a)- in swap l h-- where-- swap l h = do- when (l < h) $ do- swapArrayByteIndices (Proxy :: Proxy a) arrContents l h- swap (INDEX_NEXT(l,a)) (INDEX_PREV(h,a))---- | Generate the next permutation of the sequence, returns False if this is--- the last permutation.------ /Unimplemented/-{-# INLINE permute #-}-permute :: MutArray a -> m Bool-permute = undefined---- | Partition an array into two halves using a partitioning predicate. The--- first half retains values where the predicate is 'False' and the second half--- retains values where the predicate is 'True'.------ /Pre-release/-{-# INLINE partitionBy #-}-partitionBy :: forall m a. (MonadIO m, Unbox a)- => (a -> Bool) -> MutArray a -> m (MutArray a, MutArray a)-partitionBy f arr@MutArray{..} = liftIO $ do- if arrStart >= arrEnd- then return (arr, arr)- else do- ptr <- go arrStart (INDEX_PREV(arrEnd,a))- let pl = MutArray arrContents arrStart ptr ptr- pr = MutArray arrContents ptr arrEnd arrEnd- return (pl, pr)-- where-- -- Invariant low < high on entry, and on return as well- moveHigh low high = do- h <- peekWith arrContents high- if f h- then- -- Correctly classified, continue the loop- let high1 = INDEX_PREV(high,a)- in if low == high1- then return Nothing- else moveHigh low high1- else return (Just (high, h)) -- incorrectly classified-- -- Keep a low pointer starting at the start of the array (first partition)- -- and a high pointer starting at the end of the array (second partition).- -- Keep incrementing the low ptr and decrementing the high ptr until both- -- are wrongly classified, at that point swap the two and continue until- -- the two pointer cross each other.- --- -- Invariants when entering this loop:- -- low <= high- -- Both low and high are valid locations within the array- go low high = do- l <- peekWith arrContents low- if f l- then- -- low is wrongly classified- if low == high- then return low- else do -- low < high- r <- moveHigh low high- case r of- Nothing -> return low- Just (high1, h) -> do -- low < high1- pokeWith arrContents low h- pokeWith arrContents high1 l- let low1 = INDEX_NEXT(low,a)- high2 = INDEX_PREV(high1,a)- if low1 <= high2- then go low1 high2- else return low1 -- low1 > high2-- else do- -- low is correctly classified- let low1 = INDEX_NEXT(low,a)- if low == high- then return low1- else go low1 high---- | Shuffle corresponding elements from two arrays using a shuffle function.--- If the shuffle function returns 'False' then do nothing otherwise swap the--- elements. This can be used in a bottom up fold to shuffle or reorder the--- elements.------ /Unimplemented/-{-# INLINE shuffleBy #-}-shuffleBy :: (a -> a -> m Bool) -> MutArray a -> MutArray a -> m ()-shuffleBy = undefined---- XXX we can also make the folds partial by stopping at a certain level.------ | @divideBy level partition array@ performs a top down hierarchical--- recursive partitioning fold of items in the container using the given--- function as the partition function. Level indicates the level in the tree--- where the fold would stop.------ This performs a quick sort if the partition function is--- 'partitionBy (< pivot)'.------ /Unimplemented/-{-# INLINABLE divideBy #-}-divideBy ::- Int -> (MutArray a -> m (MutArray a, MutArray a)) -> MutArray a -> m ()-divideBy = undefined---- | @mergeBy level merge array@ performs a pairwise bottom up fold recursively--- merging the pairs using the supplied merge function. Level indicates the--- level in the tree where the fold would stop.------ This performs a random shuffle if the merge function is random. If we--- stop at level 0 and repeatedly apply the function then we can do a bubble--- sort.------ /Unimplemented/-mergeBy :: Int -> (MutArray a -> MutArray a -> m ()) -> MutArray a -> m ()-mergeBy = undefined------------------------------------------------------------------------------------ Size------------------------------------------------------------------------------------ | /O(1)/ Get the byte length of the array.----{-# INLINE byteLength #-}-byteLength :: MutArray a -> Int-byteLength MutArray{..} =- let len = arrEnd - arrStart- in assert (len >= 0) len---- Note: try to avoid the use of length in performance sensitive internal--- routines as it involves a costly 'div' operation. Instead use the end ptr--- in the array to check the bounds etc.------ | /O(1)/ Get the length of the array i.e. the number of elements in the--- array.------ Note that 'byteLength' is less expensive than this operation, as 'length'--- involves a costly division operation.----{-# INLINE length #-}-length :: forall a. Unbox a => MutArray a -> Int-length arr =- let elemSize = SIZE_OF(a)- blen = byteLength arr- in assert (blen `mod` elemSize == 0) (blen `div` elemSize)---- | Get the total capacity of an array. An array may have space reserved--- beyond the current used length of the array.------ /Pre-release/-{-# INLINE byteCapacity #-}-byteCapacity :: MutArray a -> Int-byteCapacity MutArray{..} =- let len = arrBound - arrStart- in assert (len >= 0) len---- | The remaining capacity in the array for appending more elements without--- reallocation.------ /Pre-release/-{-# INLINE bytesFree #-}-bytesFree :: MutArray a -> Int-bytesFree MutArray{..} =- let n = arrBound - arrEnd- in assert (n >= 0) n------------------------------------------------------------------------------------ Streams of arrays - Creation----------------------------------------------------------------------------------data GroupState s contents start end bound- = GroupStart s- | GroupBuffer s contents start end bound- | GroupYield- contents start end bound (GroupState s contents start end bound)- | GroupFinish---- | @chunksOf n stream@ groups the input stream into a stream of--- arrays of size n.------ @chunksOf n = StreamD.foldMany (MutArray.writeN n)@------ /Pre-release/-{-# INLINE_NORMAL chunksOf #-}-chunksOf :: forall m a. (MonadIO m, Unbox a)- => Int -> D.Stream m a -> D.Stream m (MutArray a)--- XXX the idiomatic implementation leads to large regression in the D.reverse'--- benchmark. It seems it has difficulty producing optimized code when--- converting to StreamK. Investigate GHC optimizations.--- chunksOf n = D.foldMany (writeN n)-chunksOf n (D.Stream step state) =- D.Stream step' (GroupStart state)-- where-- {-# INLINE_LATE step' #-}- step' _ (GroupStart st) = do- when (n <= 0) $- -- XXX we can pass the module string from the higher level API- error $ "Streamly.Internal.Data.MutArray.Mut.Type.chunksOf: "- ++ "the size of arrays [" ++ show n- ++ "] must be a natural number"- (MutArray contents start end bound :: MutArray a) <- liftIO $ newPinned n- return $ D.Skip (GroupBuffer st contents start end bound)-- step' gst (GroupBuffer st contents start end bound) = do- r <- step (adaptState gst) st- case r of- D.Yield x s -> do- liftIO $ pokeWith contents end x- let end1 = INDEX_NEXT(end,a)- return $- if end1 >= bound- then D.Skip- (GroupYield- contents start end1 bound (GroupStart s))- else D.Skip (GroupBuffer s contents start end1 bound)- D.Skip s ->- return $ D.Skip (GroupBuffer s contents start end bound)- D.Stop ->- return- $ D.Skip (GroupYield contents start end bound GroupFinish)-- step' _ (GroupYield contents start end bound next) =- return $ D.Yield (MutArray contents start end bound) next-- step' _ GroupFinish = return D.Stop---- XXX buffer to a list instead?--- | Buffer the stream into arrays in memory.-{-# INLINE arrayStreamKFromStreamD #-}-arrayStreamKFromStreamD :: forall m a. (MonadIO m, Unbox a) =>- D.Stream m a -> m (StreamK m (MutArray a))-arrayStreamKFromStreamD =- let n = allocBytesToElemCount (undefined :: a) defaultChunkSize- in D.foldr K.cons K.nil . chunksOf n------------------------------------------------------------------------------------ Streams of arrays - Flattening----------------------------------------------------------------------------------data FlattenState s contents a =- OuterLoop s- | InnerLoop s contents !Int !Int---- | Use the "reader" unfold instead.------ @flattenArrays = unfoldMany reader@------ We can try this if there are any fusion issues in the unfold.----{-# INLINE_NORMAL flattenArrays #-}-flattenArrays :: forall m a. (MonadIO m, Unbox a)- => D.Stream m (MutArray a) -> D.Stream m a-flattenArrays (D.Stream step state) = D.Stream step' (OuterLoop state)-- where-- {-# INLINE_LATE step' #-}- step' gst (OuterLoop st) = do- r <- step (adaptState gst) st- return $ case r of- D.Yield MutArray{..} s ->- D.Skip (InnerLoop s arrContents arrStart arrEnd)- D.Skip s -> D.Skip (OuterLoop s)- D.Stop -> D.Stop-- step' _ (InnerLoop st _ p end) | assert (p <= end) (p == end) =- return $ D.Skip $ OuterLoop st-- step' _ (InnerLoop st contents p end) = do- x <- liftIO $ peekWith contents p- return $ D.Yield x (InnerLoop st contents (INDEX_NEXT(p,a)) end)---- | Use the "readerRev" unfold instead.------ @flattenArrays = unfoldMany readerRev@------ We can try this if there are any fusion issues in the unfold.----{-# INLINE_NORMAL flattenArraysRev #-}-flattenArraysRev :: forall m a. (MonadIO m, Unbox a)- => D.Stream m (MutArray a) -> D.Stream m a-flattenArraysRev (D.Stream step state) = D.Stream step' (OuterLoop state)-- where-- {-# INLINE_LATE step' #-}- step' gst (OuterLoop st) = do- r <- step (adaptState gst) st- return $ case r of- D.Yield MutArray{..} s ->- let p = INDEX_PREV(arrEnd,a)- in D.Skip (InnerLoop s arrContents p arrStart)- D.Skip s -> D.Skip (OuterLoop s)- D.Stop -> D.Stop-- step' _ (InnerLoop st _ p start) | p < start =- return $ D.Skip $ OuterLoop st-- step' _ (InnerLoop st contents p start) = do- x <- liftIO $ peekWith contents p- let cur = INDEX_PREV(p,a)- return $ D.Yield x (InnerLoop st contents cur start)------------------------------------------------------------------------------------ Unfolds----------------------------------------------------------------------------------data ArrayUnsafe a = ArrayUnsafe- {-# UNPACK #-} !MutableByteArray -- contents- {-# UNPACK #-} !Int -- index 1- {-# UNPACK #-} !Int -- index 2--toArrayUnsafe :: MutArray a -> ArrayUnsafe a-toArrayUnsafe (MutArray contents start end _) = ArrayUnsafe contents start end--fromArrayUnsafe ::-#ifdef DEVBUILD- Unbox a =>-#endif- ArrayUnsafe a -> MutArray a-fromArrayUnsafe (ArrayUnsafe contents start end) =- MutArray contents start end end--{-# INLINE_NORMAL producerWith #-}-producerWith ::- forall m a. (Monad m, Unbox a)- => (forall b. IO b -> m b) -> Producer m (MutArray a) a-producerWith liftio = Producer step (return . toArrayUnsafe) extract- where-- {-# INLINE_LATE step #-}- step (ArrayUnsafe _ cur end)- | assert (cur <= end) (cur == end) = return D.Stop- step (ArrayUnsafe contents cur end) = do- -- When we use a purely lazy Monad like Identity, we need to force a- -- few actions for correctness and execution order sanity. We want- -- the peek to occur right here and not lazily at some later point- -- because we want the peek to be ordered with respect to the touch.- !x <- liftio $ peekWith contents cur- return $ D.Yield x (ArrayUnsafe contents (INDEX_NEXT(cur,a)) end)-- extract = return . fromArrayUnsafe---- | Resumable unfold of an array.----{-# INLINE_NORMAL producer #-}-producer :: forall m a. (MonadIO m, Unbox a) => Producer m (MutArray a) a-producer = producerWith liftIO---- | Unfold an array into a stream.----{-# INLINE_NORMAL reader #-}-reader :: forall m a. (MonadIO m, Unbox a) => Unfold m (MutArray a) a-reader = Producer.simplify producer--{-# INLINE_NORMAL readerRevWith #-}-readerRevWith ::- forall m a. (Monad m, Unbox a)- => (forall b. IO b -> m b) -> Unfold m (MutArray a) a-readerRevWith liftio = Unfold step inject- where-- inject (MutArray contents start end _) =- let p = INDEX_PREV(end,a)- in return $ ArrayUnsafe contents start p-- {-# INLINE_LATE step #-}- step (ArrayUnsafe _ start p) | p < start = return D.Stop- step (ArrayUnsafe contents start p) = do- !x <- liftio $ peekWith contents p- return $ D.Yield x (ArrayUnsafe contents start (INDEX_PREV(p,a)))---- | Unfold an array into a stream in reverse order.----{-# INLINE_NORMAL readerRev #-}-readerRev :: forall m a. (MonadIO m, Unbox a) => Unfold m (MutArray a) a-readerRev = readerRevWith liftIO------------------------------------------------------------------------------------ to Lists and streams----------------------------------------------------------------------------------{---- Use foldr/build fusion to fuse with list consumers--- This can be useful when using the IsList instance-{-# INLINE_LATE toListFB #-}-toListFB :: forall a b. Unbox a => (a -> b -> b) -> b -> MutArray a -> b-toListFB c n MutArray{..} = go arrStart- where-- go p | assert (p <= arrEnd) (p == arrEnd) = n- go p =- -- 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.- -- XXX- let !x = unsafeInlineIO $ do- r <- peekWith arrContents p- return r- in c x (go (PTR_NEXT(p,a)))--}---- XXX Monadic foldr/build fusion?--- Reference: https://www.researchgate.net/publication/220676509_Monadic_augment_and_generalised_short_cut_fusion---- | Convert a 'MutArray' into a list.----{-# INLINE toList #-}-toList :: forall m a. (MonadIO m, Unbox a) => MutArray a -> m [a]-toList MutArray{..} = liftIO $ go arrStart- where-- go p | assert (p <= arrEnd) (p == arrEnd) = return []- go p = do- x <- peekWith arrContents p- (:) x <$> go (INDEX_NEXT(p,a))--{-# INLINE_NORMAL toStreamDWith #-}-toStreamDWith ::- forall m a. (Monad m, Unbox a)- => (forall b. IO b -> m b) -> MutArray a -> D.Stream m a-toStreamDWith liftio MutArray{..} = D.Stream step arrStart-- where-- {-# INLINE_LATE step #-}- step _ p | assert (p <= arrEnd) (p == arrEnd) = return D.Stop- step _ p = liftio $ do- r <- peekWith arrContents p- return $ D.Yield r (INDEX_NEXT(p,a))---- | Use the 'reader' unfold instead.------ @toStreamD = D.unfold reader@------ We can try this if the unfold has any performance issues.-{-# INLINE_NORMAL toStreamD #-}-toStreamD :: forall m a. (MonadIO m, Unbox a) => MutArray a -> D.Stream m a-toStreamD = toStreamDWith liftIO--{-# INLINE toStreamKWith #-}-toStreamKWith ::- forall m a. (Monad m, Unbox a)- => (forall b. IO b -> m b) -> MutArray a -> StreamK m a-toStreamKWith liftio MutArray{..} = go arrStart-- where-- go p | assert (p <= arrEnd) (p == arrEnd) = K.nil- | otherwise =- let elemM = peekWith arrContents p- in liftio elemM `K.consM` go (INDEX_NEXT(p,a))--{-# INLINE toStreamK #-}-toStreamK :: forall m a. (MonadIO m, Unbox a) => MutArray a -> StreamK m a-toStreamK = toStreamKWith liftIO--{-# INLINE_NORMAL toStreamDRevWith #-}-toStreamDRevWith ::- forall m a. (Monad m, Unbox a)- => (forall b. IO b -> m b) -> MutArray a -> D.Stream m a-toStreamDRevWith liftio MutArray{..} =- let p = INDEX_PREV(arrEnd,a)- in D.Stream step p-- where-- {-# INLINE_LATE step #-}- step _ p | p < arrStart = return D.Stop- step _ p = liftio $ do- r <- peekWith arrContents p- return $ D.Yield r (INDEX_PREV(p,a))---- | Use the 'readerRev' unfold instead.------ @toStreamDRev = D.unfold readerRev@------ We can try this if the unfold has any perf issues.-{-# INLINE_NORMAL toStreamDRev #-}-toStreamDRev :: forall m a. (MonadIO m, Unbox a) => MutArray a -> D.Stream m a-toStreamDRev = toStreamDRevWith liftIO--{-# INLINE toStreamKRevWith #-}-toStreamKRevWith ::- forall m a. (Monad m, Unbox a)- => (forall b. IO b -> m b) -> MutArray a -> StreamK m a-toStreamKRevWith liftio MutArray {..} =- let p = INDEX_PREV(arrEnd,a)- in go p-- where-- go p | p < arrStart = K.nil- | otherwise =- let elemM = peekWith arrContents p- in liftio elemM `K.consM` go (INDEX_PREV(p,a))--{-# INLINE toStreamKRev #-}-toStreamKRev :: forall m a. (MonadIO m, Unbox a) => MutArray a -> StreamK m a-toStreamKRev = toStreamKRevWith liftIO------------------------------------------------------------------------------------ Folding------------------------------------------------------------------------------------ XXX Need something like "MutArray m a" enforcing monadic action to avoid the--- possibility of such APIs.------ | Strict left fold of an array.-{-# INLINE_NORMAL foldl' #-}-foldl' :: (MonadIO m, Unbox a) => (b -> a -> b) -> b -> MutArray a -> m b-foldl' f z arr = D.foldl' f z $ toStreamD arr---- | Right fold of an array.-{-# INLINE_NORMAL foldr #-}-foldr :: (MonadIO m, Unbox a) => (a -> b -> b) -> b -> MutArray a -> m b-foldr f z arr = D.foldr f z $ toStreamD arr------------------------------------------------------------------------------------ Folds------------------------------------------------------------------------------------ Note: Arrays may be allocated with a specific alignment at the beginning of--- the array. If you need to maintain that alignment on reallocations then you--- can resize the array manually before append, using an aligned resize--- operation.---- XXX Keep the bound intact to not lose any free space? Perf impact?---- | @writeAppendNUnsafe n alloc@ appends up to @n@ input items to the supplied--- array.------ Unsafe: Do not drive the fold beyond @n@ elements, it will lead to memory--- corruption or segfault.------ Any free space left in the array after appending @n@ elements is lost.------ /Internal/-{-# INLINE_NORMAL writeAppendNUnsafe #-}-writeAppendNUnsafe :: forall m a. (MonadIO m, Unbox a) =>- Int- -> m (MutArray a)- -> Fold m a (MutArray a)-writeAppendNUnsafe n action =- fmap fromArrayUnsafe $ FL.foldlM' step initial-- where-- initial = do- assert (n >= 0) (return ())- arr@(MutArray _ _ end bound) <- action- let free = bound - end- needed = n * SIZE_OF(a)- -- XXX We can also reallocate if the array has too much free space,- -- otherwise we lose that space.- arr1 <-- if free < needed- then noinline reallocWith "writeAppendNUnsafeWith" (+ needed) needed arr- else return arr- return $ toArrayUnsafe arr1-- step (ArrayUnsafe contents start end) x = do- liftIO $ pokeWith contents end x- return $ ArrayUnsafe contents start (INDEX_NEXT(end,a))---- | Append @n@ elements to an existing array. Any free space left in the array--- after appending @n@ elements is lost.------ >>> writeAppendN n initial = Fold.take n (MutArray.writeAppendNUnsafe n initial)----{-# INLINE_NORMAL writeAppendN #-}-writeAppendN :: forall m a. (MonadIO m, Unbox a) =>- Int -> m (MutArray a) -> Fold m a (MutArray a)-writeAppendN n initial = FL.take n (writeAppendNUnsafe n initial)---- | @writeAppendWith realloc action@ mutates the array generated by @action@ to--- append the input stream. If there is no reserved space available in the--- array it is reallocated to a size in bytes determined by @realloc oldSize@,--- where @oldSize@ is the current size of the array in bytes.------ Note that the returned array may be a mutated version of original array.------ >>> writeAppendWith sizer = Fold.foldlM' (MutArray.snocWith sizer)------ /Pre-release/-{-# INLINE writeAppendWith #-}-writeAppendWith :: forall m a. (MonadIO m, Unbox a) =>- (Int -> Int) -> m (MutArray a) -> Fold m a (MutArray a)-writeAppendWith sizer = FL.foldlM' (snocWith sizer)---- | @append action@ mutates the array generated by @action@ to append the--- input stream. If there is no reserved space available in the array it is--- reallocated to double the size.------ Note that the returned array may be a mutated version of original array.------ >>> writeAppend = MutArray.writeAppendWith (* 2)----{-# INLINE writeAppend #-}-writeAppend :: forall m a. (MonadIO m, Unbox a) =>- m (MutArray a) -> Fold m a (MutArray a)-writeAppend = writeAppendWith (* 2)---- XXX We can carry bound as well in the state to make sure we do not lose the--- remaining capacity. Need to check perf impact.------ | Like 'writeNUnsafe' but takes a new array allocator @alloc size@ function--- as argument.------ >>> writeNWithUnsafe alloc n = MutArray.writeAppendNUnsafe (alloc n) n------ /Pre-release/-{-# INLINE_NORMAL writeNWithUnsafe #-}-writeNWithUnsafe :: forall m a. (MonadIO m, Unbox a)- => (Int -> m (MutArray a)) -> Int -> Fold m a (MutArray a)-writeNWithUnsafe alloc n = fromArrayUnsafe <$> FL.foldlM' step initial-- where-- initial = toArrayUnsafe <$> alloc (max n 0)-- step (ArrayUnsafe contents start end) x = do- liftIO $ pokeWith contents end x- return- $ ArrayUnsafe contents start (INDEX_NEXT(end,a))---- | Like 'writeN' but does not check the array bounds when writing. The fold--- driver must not call the step function more than 'n' times otherwise it will--- corrupt the memory and crash. This function exists mainly because any--- conditional in the step function blocks fusion causing 10x performance--- slowdown.------ >>> writeNUnsafe = MutArray.writeNWithUnsafe MutArray.newPinned----{-# INLINE_NORMAL writeNUnsafe #-}-writeNUnsafe :: forall m a. (MonadIO m, Unbox a)- => Int -> Fold m a (MutArray a)-writeNUnsafe = writeNWithUnsafe newPinned---- | @writeNWith alloc n@ folds a maximum of @n@ elements into an array--- allocated using the @alloc@ function.------ >>> writeNWith alloc n = Fold.take n (MutArray.writeNWithUnsafe alloc n)--- >>> writeNWith alloc n = MutArray.writeAppendN (alloc n) n----{-# INLINE_NORMAL writeNWith #-}-writeNWith :: forall m a. (MonadIO m, Unbox a)- => (Int -> m (MutArray a)) -> Int -> Fold m a (MutArray a)-writeNWith alloc n = FL.take n (writeNWithUnsafe alloc n)---- | @writeN n@ folds a maximum of @n@ elements from the input stream to an--- 'MutArray'.------ >>> writeN = MutArray.writeNWith MutArray.newPinned--- >>> writeN n = Fold.take n (MutArray.writeNUnsafe n)--- >>> writeN n = MutArray.writeAppendN n (MutArray.newPinned n)----{-# INLINE_NORMAL writeN #-}-writeN :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)-writeN = writeNWith newPinned---- | Like writeNWithUnsafe but writes the array in reverse order.------ /Internal/-{-# INLINE_NORMAL writeRevNWithUnsafe #-}-writeRevNWithUnsafe :: forall m a. (MonadIO m, Unbox a)- => (Int -> m (MutArray a)) -> Int -> Fold m a (MutArray a)-writeRevNWithUnsafe alloc n = fromArrayUnsafe <$> FL.foldlM' step initial-- where-- toArrayUnsafeRev (MutArray contents _ _ bound) =- ArrayUnsafe contents bound bound-- initial = toArrayUnsafeRev <$> alloc (max n 0)-- step (ArrayUnsafe contents start end) x = do- let ptr = INDEX_PREV(start,a)- liftIO $ pokeWith contents ptr x- return- $ ArrayUnsafe contents ptr end---- | Like writeNWith but writes the array in reverse order.------ /Internal/-{-# INLINE_NORMAL writeRevNWith #-}-writeRevNWith :: forall m a. (MonadIO m, Unbox a)- => (Int -> m (MutArray a)) -> Int -> Fold m a (MutArray a)-writeRevNWith alloc n = FL.take n (writeRevNWithUnsafe alloc n)---- | Like writeN but writes the array in reverse order.------ /Pre-release/-{-# INLINE_NORMAL writeRevN #-}-writeRevN :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)-writeRevN = writeRevNWith newPinned---- | @writeNAligned align n@ folds a maximum of @n@ elements from the input--- stream to a 'MutArray' aligned to the given size.------ >>> writeNAligned align = MutArray.writeNWith (MutArray.newAlignedPinned align)--- >>> writeNAligned align n = MutArray.writeAppendN n (MutArray.newAlignedPinned align n)------ /Pre-release/----{-# INLINE_NORMAL writeNAligned #-}-writeNAligned :: forall m a. (MonadIO m, Unbox a)- => Int -> Int -> Fold m a (MutArray a)-writeNAligned align = writeNWith (newAlignedPinned align)---- XXX Buffer to a list instead?------ | Buffer a stream into a stream of arrays.------ >>> writeChunks n = Fold.many (MutArray.writeN n) Fold.toStreamK------ Breaking an array into an array stream can be useful to consume a large--- array sequentially such that memory of the array is released incrementatlly.------ See also: 'arrayStreamKFromStreamD'.------ /Unimplemented/----{-# INLINE_NORMAL writeChunks #-}-writeChunks :: (MonadIO m, Unbox a) =>- Int -> Fold m a (StreamK n (MutArray a))-writeChunks n = FL.many (writeN n) FL.toStreamK---- XXX Compare writeWith with fromStreamD which uses an array of streams--- implementation. We can write this using writeChunks above if that is faster.--- If writeWith is faster then we should use that to implement--- fromStreamD.------ XXX The realloc based implementation needs to make one extra copy if we use--- shrinkToFit. On the other hand, the stream of arrays implementation may--- buffer the array chunk pointers in memory but it does not have to shrink as--- we know the exact size in the end. However, memory copying does not seem to--- be as expensive as the allocations. Therefore, we need to reduce the number--- of allocations instead. Also, the size of allocations matters, right sizing--- an allocation even at the cost of copying sems to help. Should be measured--- on a big stream with heavy calls to toArray to see the effect.------ XXX check if GHC's memory allocator is efficient enough. We can try the C--- malloc to compare against.---- | @writeWith minCount@ folds the whole input to a single array. The array--- starts at a size big enough to hold minCount elements, the size is doubled--- every time the array needs to be grown.------ /Caution! Do not use this on infinite streams./------ >>> f n = MutArray.writeAppendWith (* 2) (MutArray.newPinned n)--- >>> writeWith n = Fold.rmapM MutArray.rightSize (f n)--- >>> writeWith n = Fold.rmapM MutArray.fromArrayStreamK (MutArray.writeChunks n)------ /Pre-release/-{-# INLINE_NORMAL writeWith #-}-writeWith :: forall m a. (MonadIO m, Unbox a)- => Int -> Fold m a (MutArray a)--- writeWith n = FL.rmapM rightSize $ writeAppendWith (* 2) (newPinned n)-writeWith elemCount =- FL.rmapM extract $ FL.foldlM' step initial-- where-- initial = do- when (elemCount < 0) $ error "writeWith: elemCount is negative"- liftIO $ newPinned elemCount-- step arr@(MutArray _ start end bound) x- | INDEX_NEXT(end,a) > bound = do- let oldSize = end - start- newSize = max (oldSize * 2) 1- arr1 <- liftIO $ reallocExplicit (SIZE_OF(a)) newSize arr- snocUnsafe arr1 x- step arr x = snocUnsafe arr x-- extract = liftIO . rightSize---- | Fold the whole input to a single array.------ Same as 'writeWith' using an initial array size of 'arrayChunkBytes' bytes--- rounded up to the element size.------ /Caution! Do not use this on infinite streams./----{-# INLINE write #-}-write :: forall m a. (MonadIO m, Unbox a) => Fold m a (MutArray a)-write = writeWith (allocBytesToElemCount (undefined :: a) arrayChunkBytes)------------------------------------------------------------------------------------ construct from streams, known size------------------------------------------------------------------------------------ | Use the 'writeN' fold instead.------ >>> fromStreamDN n = Stream.fold (MutArray.writeN n)----{-# INLINE_NORMAL fromStreamDN #-}-fromStreamDN :: forall m a. (MonadIO m, Unbox a)- => Int -> D.Stream m a -> m (MutArray a)--- fromStreamDN n = D.fold (writeN n)-fromStreamDN limit str = do- (arr :: MutArray a) <- liftIO $ newPinned limit- end <- D.foldlM' (fwrite (arrContents arr)) (return $ arrEnd arr) $ D.take limit str- return $ arr {arrEnd = end}-- where-- fwrite arrContents ptr x = do- liftIO $ pokeWith arrContents ptr x- return $ INDEX_NEXT(ptr,a)---- | Create a 'MutArray' from the first N elements of a list. The array is--- allocated to size N, if the list terminates before N elements then the--- array may hold less than N elements.----{-# INLINABLE fromListN #-}-fromListN :: (MonadIO m, Unbox a) => Int -> [a] -> m (MutArray a)-fromListN n xs = fromStreamDN n $ D.fromList xs---- | Like fromListN but writes the array in reverse order.------ /Pre-release/-{-# INLINE fromListRevN #-}-fromListRevN :: (MonadIO m, Unbox a) => Int -> [a] -> m (MutArray a)-fromListRevN n xs = D.fold (writeRevN n) $ D.fromList xs------------------------------------------------------------------------------------ convert stream to a single array----------------------------------------------------------------------------------{-# INLINE arrayStreamKLength #-}-arrayStreamKLength :: (Monad m, Unbox a) => StreamK m (MutArray a) -> m Int-arrayStreamKLength as = K.foldl' (+) 0 (K.map length as)---- | Convert an array stream to an array. Note that this requires peak memory--- that is double the size of the array stream.----{-# INLINE fromArrayStreamK #-}-fromArrayStreamK :: (Unbox a, MonadIO m) =>- StreamK m (MutArray a) -> m (MutArray a)-fromArrayStreamK as = do- len <- arrayStreamKLength as- fromStreamDN len $ D.unfoldMany reader $ D.fromStreamK as---- CAUTION: a very large number (millions) of arrays can degrade performance--- due to GC overhead because we need to buffer the arrays before we flatten--- all the arrays.------ XXX Compare if this is faster or "fold write".------ | We could take the approach of doubling the memory allocation on each--- overflow. This would result in more or less the same amount of copying as in--- the chunking approach. However, if we have to shrink in the end then it may--- result in an extra copy of the entire data.------ >>> fromStreamD = StreamD.fold MutArray.write----{-# INLINE fromStreamD #-}-fromStreamD :: (MonadIO m, Unbox a) => D.Stream m a -> m (MutArray a)-fromStreamD m = arrayStreamKFromStreamD m >>= fromArrayStreamK---- | Create a 'MutArray' from a list. The list must be of finite size.----{-# INLINE fromList #-}-fromList :: (MonadIO m, Unbox a) => [a] -> m (MutArray a)-fromList xs = fromStreamD $ D.fromList xs---- XXX We are materializing the whole list first for getting the length. Check--- if the 'fromList' like chunked implementation would fare better.---- | Like 'fromList' but writes the contents of the list in reverse order.-{-# INLINE fromListRev #-}-fromListRev :: (MonadIO m, Unbox a) => [a] -> m (MutArray a)-fromListRev xs = fromListRevN (Prelude.length xs) xs------------------------------------------------------------------------------------ Combining------------------------------------------------------------------------------------ | Put a sub range of a source array into a subrange of a destination array.--- This is not safe as it does not check the bounds.-{-# INLINE putSliceUnsafe #-}-putSliceUnsafe :: MonadIO m => MutArray a -> Int -> MutArray a -> Int -> Int -> m ()-putSliceUnsafe src srcStartBytes dst dstStartBytes lenBytes = liftIO $ do- assertM(lenBytes <= arrBound dst - dstStartBytes)- assertM(lenBytes <= arrEnd src - srcStartBytes)- let !(I# srcStartBytes#) = srcStartBytes- !(I# dstStartBytes#) = dstStartBytes- !(I# lenBytes#) = lenBytes- let arrS# = getMutableByteArray# (arrContents src)- arrD# = getMutableByteArray# (arrContents dst)- IO $ \s# -> (# copyMutableByteArray#- arrS# srcStartBytes# arrD# dstStartBytes# lenBytes# s#- , () #)---- | Copy two arrays into a newly allocated array.-{-# INLINE spliceCopy #-}-spliceCopy :: forall m a. MonadIO m =>-#ifdef DEVBUILD- Unbox a =>-#endif- MutArray a -> MutArray a -> m (MutArray a)-spliceCopy arr1 arr2 = liftIO $ do- let start1 = arrStart arr1- start2 = arrStart arr2- len1 = arrEnd arr1 - start1- len2 = arrEnd arr2 - start2- newArrContents <- liftIO $ Unboxed.newPinnedBytes (len1 + len2)- let len = len1 + len2- newArr = MutArray newArrContents 0 len len- putSliceUnsafe arr1 start1 newArr 0 len1- putSliceUnsafe arr2 start2 newArr len1 len2- return newArr---- | Really really unsafe, appends the second array into the first array. If--- the first array does not have enough space it may cause silent data--- corruption or if you are lucky a segfault.-{-# INLINE spliceUnsafe #-}-spliceUnsafe :: MonadIO m =>- MutArray a -> MutArray a -> m (MutArray a)-spliceUnsafe dst src =- liftIO $ do- let startSrc = arrStart src- srcLen = arrEnd src - startSrc- endDst = arrEnd dst- assertM(endDst + srcLen <= arrBound dst)- putSliceUnsafe src startSrc dst endDst srcLen- return $ dst {arrEnd = endDst + srcLen}---- | @spliceWith sizer dst src@ mutates @dst@ to append @src@. If there is no--- reserved space available in @dst@ it is reallocated to a size determined by--- the @sizer dstBytes srcBytes@ function, where @dstBytes@ is the size of the--- first array and @srcBytes@ is the size of the second array, in bytes.------ Note that the returned array may be a mutated version of first array.------ /Pre-release/-{-# INLINE spliceWith #-}-spliceWith :: forall m a. (MonadIO m, Unbox a) =>- (Int -> Int -> Int) -> MutArray a -> MutArray a -> m (MutArray a)-spliceWith sizer dst@(MutArray _ start end bound) src = do-{-- let f = writeAppendWith (`sizer` byteLength src) (return dst)- in D.fold f (toStreamD src)--}- assert (end <= bound) (return ())- let srcBytes = arrEnd src - arrStart src-- dst1 <-- if end + srcBytes >= bound- then do- let dstBytes = end - start- newSizeInBytes = sizer dstBytes srcBytes- when (newSizeInBytes < dstBytes + srcBytes)- $ error- $ "splice: newSize is less than the total size "- ++ "of arrays being appended. Please check the "- ++ "sizer function passed."- liftIO $ realloc newSizeInBytes dst- else return dst- spliceUnsafe dst1 src---- | The first array is mutated to append the second array. If there is no--- reserved space available in the first array a new allocation of exact--- required size is done.------ Note that the returned array may be a mutated version of first array.------ >>> splice = MutArray.spliceWith (+)------ /Pre-release/-{-# INLINE splice #-}-splice :: (MonadIO m, Unbox a) => MutArray a -> MutArray a -> m (MutArray a)-splice = spliceWith (+)---- | Like 'append' but the growth of the array is exponential. Whenever a new--- allocation is required the previous array size is at least doubled.------ This is useful to reduce allocations when folding many arrays together.------ Note that the returned array may be a mutated version of first array.------ >>> spliceExp = MutArray.spliceWith (\l1 l2 -> max (l1 * 2) (l1 + l2))------ /Pre-release/-{-# INLINE spliceExp #-}-spliceExp :: (MonadIO m, Unbox a) => MutArray a -> MutArray a -> m (MutArray a)-spliceExp = spliceWith (\l1 l2 -> max (l1 * 2) (l1 + l2))------------------------------------------------------------------------------------ Splitting------------------------------------------------------------------------------------ | Drops the separator byte-{-# INLINE breakOn #-}-breakOn :: MonadIO m- => Word8 -> MutArray Word8 -> m (MutArray Word8, Maybe (MutArray Word8))-breakOn sep arr@MutArray{..} = asPtrUnsafe arr $ \p -> liftIO $ do- -- XXX Instead of using asPtrUnsafe (pinning memory) we can pass unlifted- -- Addr# to memchr and it should be safe (from ghc 8.4).- -- XXX We do not need memchr here, we can use a Haskell equivalent.- loc <- c_memchr p sep (fromIntegral $ byteLength arr)- let sepIndex = loc `minusPtr` p- return $- if loc == nullPtr- then (arr, Nothing)- else- ( MutArray- { arrContents = arrContents- , arrStart = arrStart- , arrEnd = arrStart + sepIndex -- exclude the separator- , arrBound = arrStart + sepIndex- }- , Just $ MutArray- { arrContents = arrContents- , arrStart = arrStart + (sepIndex + 1)- , arrEnd = arrEnd- , arrBound = arrBound- }- )---- | Create two slices of an array without copying the original array. The--- specified index @i@ is the first index of the second slice.----splitAt :: forall a. Unbox a => Int -> MutArray a -> (MutArray a, MutArray a)-splitAt i arr@MutArray{..} =- let maxIndex = length arr - 1- in if i < 0- then error "sliceAt: negative array index"- else if i > maxIndex- then error $ "sliceAt: specified array index " ++ show i- ++ " is beyond the maximum index " ++ show maxIndex- else let off = i * SIZE_OF(a)- p = arrStart + off- in ( MutArray- { arrContents = arrContents- , arrStart = arrStart- , arrEnd = p- , arrBound = p- }- , MutArray- { arrContents = arrContents- , arrStart = p- , arrEnd = arrEnd- , arrBound = arrBound- }- )------------------------------------------------------------------------------------ Casting------------------------------------------------------------------------------------ | Cast an array having elements of type @a@ into an array having elements of--- type @b@. The array size must be a multiple of the size of type @b@--- otherwise accessing the last element of the array may result into a crash or--- a random value.------ /Pre-release/----castUnsafe ::-#ifdef DEVBUILD- Unbox b =>-#endif- MutArray a -> MutArray b-castUnsafe (MutArray contents start end bound) =- MutArray contents start end bound---- | Cast an @MutArray a@ into an @MutArray Word8@.----asBytes :: MutArray a -> MutArray Word8-asBytes = castUnsafe---- | Cast an array having elements of type @a@ into an array having elements of--- type @b@. The length of the array should be a multiple of the size of the--- target element otherwise 'Nothing' is returned.----cast :: forall a b. Unbox b => MutArray a -> Maybe (MutArray b)-cast arr =- let len = byteLength arr- r = len `mod` SIZE_OF(b)- in if r /= 0- then Nothing- else Just $ castUnsafe arr---- XXX We can provide another API for "unsafe" FFI calls passing an unlifted--- pointer to the FFI call. For unsafe calls we do not need to pin the array.--- We can pass an unlifted pointer to the FFI routine to avoid GC kicking in--- before the pointer is wrapped.------ From the GHC manual:------ GHC, since version 8.4, guarantees that garbage collection will never occur--- during an unsafe call, even in the bytecode interpreter, and further--- guarantees that unsafe calls will be performed in the calling thread. Making--- it safe to pass heap-allocated objects to unsafe functions.---- Unsafe because of direct pointer operations. The user must ensure that they--- are writing within the legal bounds of the array. Should we just name it--- asPtr, the unsafety is implicit for any pointer operations. And we are safe--- from Haskell perspective because we will be pinning the memory.---- | Use an @MutArray a@ as @Ptr a@. This is useful when we want to pass an array--- as a pointer to some operating system call or to a "safe" FFI call.------ If the array is not pinned it is copied to pinned memory before passing it--- to the monadic action.------ /Performance Notes:/ Forces a copy if the array is not pinned. It is advised--- that the programmer keeps this in mind and creates a pinned array--- opportunistically before this operation occurs, to avoid the cost of a copy--- if possible.------ /Unsafe/------ /Pre-release/----asPtrUnsafe :: MonadIO m => MutArray a -> (Ptr a -> m b) -> m b-asPtrUnsafe arr f = do- let contents = arrContents arr- !ptr = Ptr (byteArrayContents#- (unsafeCoerce# (getMutableByteArray# contents)))- -- XXX Check if the array is pinned, if not, copy it to a pinned array- -- XXX We should probably pass to the IO action the byte length of the array- -- as well so that bounds can be checked.- r <- f (ptr `plusPtr` arrStart arr)- liftIO $ touch contents- return r------------------------------------------------------------------------------------ Equality------------------------------------------------------------------------------------ | Compare the length of the arrays. If the length is equal, compare the--- lexicographical ordering of two underlying byte arrays otherwise return the--- result of length comparison.------ /Pre-release/-{-# INLINE cmp #-}-cmp :: MonadIO m => MutArray a -> MutArray a -> m Ordering-cmp arr1 arr2 =- liftIO- $ do- let marr1 = getMutableByteArray# (arrContents arr1)- marr2 = getMutableByteArray# (arrContents arr2)- !(I# st1#) = arrStart arr1- !(I# st2#) = arrStart arr2- !(I# len#) = byteLength arr1- case compare (byteLength arr1) (byteLength arr2) of- EQ -> do- r <- liftIO $ IO $ \s# ->- let res =- I#- (compareByteArrays#- (unsafeCoerce# marr1)- st1#- (unsafeCoerce# marr2)- st2#- len#)- in (# s#, res #)- return $ compare r 0- x -> return x------------------------------------------------------------------------------------ NFData------------------------------------------------------------------------------------ | Strip elements which match with predicate from both ends.------ /Pre-release/-{-# INLINE strip #-}-strip :: forall a m. (Unbox a, MonadIO m) =>- (a -> Bool) -> MutArray a -> m (MutArray a)-strip eq arr@MutArray{..} = liftIO $ do- st <- getStart arrStart- end <- getLast arrEnd st- return arr {arrStart = st, arrEnd = end, arrBound = end}-- where-- {-- -- XXX This should have the same perf but it does not, investigate.- getStart = do- r <- liftIO $ D.head $ D.findIndices (not . eq) $ toStreamD arr- pure $- case r of- Nothing -> arrEnd- Just i -> PTR_INDEX(arrStart,i,a)- -}-- getStart cur = do- if cur < arrEnd- then do- r <- peekWith arrContents cur- if eq r- then getStart (INDEX_NEXT(cur,a))- else return cur- else return cur-- getLast cur low = do- if cur > low- then do- let prev = INDEX_PREV(cur,a)- r <- peekWith arrContents prev- if eq r- then getLast prev low- else return cur- else return cur---- | Given an array sorted in ascending order except the last element being out--- of order, use bubble sort to place the last element at the right place such--- that the array remains sorted in ascending order.------ /Pre-release/-{-# INLINE bubble #-}-bubble :: (MonadIO m, Unbox a) => (a -> a -> Ordering) -> MutArray a -> m ()-bubble cmp0 arr =- when (l > 1) $ do- x <- getIndexUnsafe (l - 1) arr- go x (l - 2)-- where-- l = length arr-- go x i =- if i >= 0- then do- x1 <- getIndexUnsafe i arr- case x `cmp0` x1 of- LT -> do- putIndexUnsafe (i + 1) arr x1- go x (i - 1)- _ -> putIndexUnsafe (i + 1) arr x- else putIndexUnsafe (i + 1) arr x
+ src/Streamly/Internal/Data/Array/Stream.hs view
@@ -0,0 +1,723 @@+{-# OPTIONS_GHC -Wno-deprecations #-}+{-# OPTIONS_GHC -Wno-incomplete-patterns #-}+-- |+-- Module : Streamly.Internal.Data.Array.Stream+-- Copyright : (c) 2019 Composewell Technologies+-- License : BSD3-3-Clause+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--+-- Combinators to efficiently manipulate streams of immutable arrays.+--+-- We can either push these in the MutArray module with a "chunks" prefix or+-- keep this as a separate module and release it.+--+module Streamly.Internal.Data.Array.Stream+{-# DEPRECATED "Please use \"Streamly.Internal.Data.Array\" instead." #-}+ (+ -- * Creation+ Array.chunksOf+ , Array.pinnedChunksOf+ , Array.bufferChunks++ -- * Flattening to elements+ , Array.concat+ , Array.flattenArrays+ , Array.concatRev+ , Array.flattenArraysRev+ , Array.interpose+ , Array.interposeSuffix+ , Array.intercalateSuffix+ , unlines++ -- * Elimination+ -- ** Element Folds+ -- The byte level foldBreak can work as efficiently as the chunk level. We+ -- can flatten the stream to byte stream and use that. But if we want the+ -- remaining stream to be a chunk stream then this could be handy. But it+ -- could also be implemented using parseBreak.+ , foldBreak+ , foldBreakD+ -- This is chunked parseBreak. A byte level parseBreak cannot work+ -- efficiently. Because the stream will have to be a StreamK for+ -- backtracking, StreamK at byte level would not be efficient.+ -- parseBreak p = K.parseBreakChunks (ParserK.adaptC p)+ , parseBreak+ -- , parseBreakD+ -- , foldManyChunks+ -- , parseManyChunks+ , K.parseBreakChunks+ , K.parseChunks++ -- ** Array Folds+ -- XXX Use parseBreakChunks/parseChunks instead+ -- foldBreak can be implemented using parseBreak. Use StreamK.+ , runArrayFold+ , runArrayFoldBreak+ -- , parseArr+ , runArrayParserDBreak -- StreamK.parseBreakChunks+ , runArrayFoldMany++ , toArray++ -- * Compaction+ -- We can use something like foldManyChunks, parseManyChunks with a take+ -- fold.+ , lpackArraysChunksOf -- Fold.compactChunks+ , compact -- rechunk, compactChunks++ -- * Splitting+ -- We can use something like foldManyChunks, parseManyChunks with an+ -- appropriate splitting fold.+ , splitOn -- Stream.rechunkOn+ , splitOnSuffix -- Stream.rechunkOnSuffix+ )+where++#include "ArrayMacros.h"+#include "inline.hs"++import Data.Bifunctor (second)+import Control.Exception (assert)+import Control.Monad.IO.Class (MonadIO(..))+-- import Data.Bifunctor (first)+-- import Data.Either (fromRight)+import Data.Proxy (Proxy(..))+import Data.Word (Word8)+import Streamly.Internal.Data.Unbox (Unbox(..))+import Fusion.Plugin.Types (Fuse(..))+import GHC.Exts (SpecConstrAnnotation(..))+import GHC.Types (SPEC(..))+import Prelude hiding (null, last, (!!), read, concat, unlines)++import Streamly.Data.Fold (Fold)+import Streamly.Internal.Data.Array.Type (Array(..))+import Streamly.Internal.Data.Fold.Chunked (ChunkFold(..))+import Streamly.Internal.Data.Parser (ParseError(..))+import Streamly.Internal.Data.Stream (Stream)+import Streamly.Internal.Data.StreamK (StreamK, fromStream, toStream)+import Streamly.Internal.Data.SVar.Type (adaptState, defState)++import qualified Streamly.Internal.Data.Array as A+import qualified Streamly.Internal.Data.Array as Array+import qualified Streamly.Internal.Data.Parser as PR+import qualified Streamly.Internal.Data.Parser as PRD+ (Parser(..), Initial(..))+-- import qualified Streamly.Internal.Data.ParserK as ParserK+import qualified Streamly.Internal.Data.Stream as D+import qualified Streamly.Internal.Data.StreamK as K++-- XXX Since these are immutable arrays MonadIO constraint can be removed from+-- most places.++-------------------------------------------------------------------------------+-- Intersperse and append+-------------------------------------------------------------------------------++{-# INLINE_NORMAL unlines #-}+unlines :: forall m a. (MonadIO m, Unbox a)+ => a -> D.Stream m (Array a) -> D.Stream m a+unlines = Array.interposeSuffix++-------------------------------------------------------------------------------+-- Compact+-------------------------------------------------------------------------------++-- XXX instead of writing two different versions of this operation, we should+-- write it as a pipe.+--+-- XXX Confirm that immutable arrays won't be modified.+{-# INLINE_NORMAL lpackArraysChunksOf #-}+lpackArraysChunksOf :: (MonadIO m, Unbox a)+ => Int -> Fold m (Array a) () -> Fold m (Array a) ()+lpackArraysChunksOf = Array.lCompactGE++-- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a+-- maximum specified size in bytes.+--+-- @since 0.7.0+{-# INLINE compact #-}+compact :: (MonadIO m, Unbox a)+ => Int -> Stream m (Array a) -> Stream m (Array a)+compact = Array.compactLE++-- | Given a stream of arrays, splice them all together to generate a single+-- array. The stream must be /finite/.+--+-- @since 0.7.0+{-# INLINE toArray #-}+toArray :: (MonadIO m, Unbox a) => Stream m (Array a) -> m (Array a)+toArray = Array.fromChunks++-------------------------------------------------------------------------------+-- Split+-------------------------------------------------------------------------------++-- XXX Remove MonadIO constraint.+-- | Split a stream of arrays on a given separator byte, dropping the separator+-- and coalescing all the arrays between two separators into a single array.+--+-- @since 0.7.0+{-# INLINE splitOn #-}+splitOn+ :: (MonadIO m)+ => Word8+ -> Stream m (Array Word8)+ -> Stream m (Array Word8)+splitOn = Array.compactOnByte++{-# INLINE splitOnSuffix #-}+splitOnSuffix+ :: (MonadIO m)+ => Word8+ -> Stream m (Array Word8)+ -> Stream m (Array Word8)+splitOnSuffix = Array.compactOnByteSuffix++-------------------------------------------------------------------------------+-- Elimination - Running folds+-------------------------------------------------------------------------------++{-# INLINE_NORMAL foldBreakD #-}+foldBreakD :: forall m a b. (MonadIO m, Unbox a) =>+ Fold m a b -> D.Stream m (Array a) -> m (b, D.Stream m (Array a))+foldBreakD = Array.foldBreakChunks++-- | Fold an array stream using the supplied 'Fold'. Returns the fold result+-- and the unconsumed stream.+--+-- > foldBreak f = runArrayFoldBreak (ChunkFold.fromFold f)+--+-- Instead of using this we can adapt the fold to ParserK and use+-- parseBreakChunks instead. ParserK allows composing using Monad as well.+--+-- @+-- foldBreak f s =+-- fmap (first (fromRight undefined))+-- $ K.parseBreakChunks (ParserK.adaptC (PR.fromFold f)) s+-- @+--+-- We can compare perf and remove this one or define it in terms of that.+--+-- /Internal/+--+{-# INLINE_NORMAL foldBreak #-}+foldBreak ::+ (MonadIO m, Unbox a)+ => Fold m a b+ -> StreamK m (A.Array a)+ -> m (b, StreamK m (A.Array a))+foldBreak = Array.foldBreakChunksK+--+-- foldBreak f s = fmap fromStreamD <$> foldBreakD f (toStreamD s)+--+-- foldBreak f s =+-- fmap (first (fromRight undefined))+-- $ K.parseBreakChunks (ParserK.adaptC (PR.fromFold f)) s+--+-- If foldBreak performs better than runArrayFoldBreak we can use a rewrite+-- rule to rewrite runArrayFoldBreak to fold.+-- foldBreak f = runArrayFoldBreak (ChunkFold.fromFold f)++-------------------------------------------------------------------------------+-- Elimination - running element parsers+-------------------------------------------------------------------------------++-- When we have to take an array partially, take the last part of the array.+{-# INLINE takeArrayListRev #-}+takeArrayListRev :: forall a. Unbox a => Int -> [Array a] -> [Array a]+takeArrayListRev = go++ where++ go _ [] = []+ go n _ | n <= 0 = []+ go n (x:xs) =+ let len = Array.length x+ in if n > len+ then x : go (n - len) xs+ else if n == len+ then [x]+ else let !(Array contents _ end) = x+ !start = end - (n * SIZE_OF(a))+ in [Array contents start end]++-- When we have to take an array partially, take the last part of the array in+-- the first split.+{-# INLINE splitAtArrayListRev #-}+splitAtArrayListRev ::+ forall a. Unbox a => Int -> [Array a] -> ([Array a],[Array a])+splitAtArrayListRev n ls+ | n <= 0 = ([], ls)+ | otherwise = go n ls+ where+ go :: Int -> [Array a] -> ([Array a], [Array a])+ go _ [] = ([], [])+ go m (x:xs) =+ let len = Array.length x+ (xs', xs'') = go (m - len) xs+ in if m > len+ then (x:xs', xs'')+ else if m == len+ then ([x],xs)+ else let !(Array contents start end) = x+ end1 = end - (m * SIZE_OF(a))+ arr2 = Array contents start end1+ arr1 = Array contents end1 end+ in ([arr1], arr2:xs)++-- GHC parser does not accept {-# ANN type [] NoSpecConstr #-}, so we need+-- to make a newtype.+{-# ANN type List NoSpecConstr #-}+newtype List a = List {getList :: [a]}++-- | Parse an array stream using the supplied 'Parser'. Returns the parse+-- result and the unconsumed stream. Throws 'ParseError' if the parse fails.+--+-- >> parseBreak p = K.parseBreakChunks (ParserK.adaptC p)+--+-- This is redundant and we can just use parseBreakChunks, as ParserK can be+-- composed using Monad. The only advantage of this is that we do not need to+-- adapt.+--+-- We can compare perf and remove this one or define it in terms of that.+--+-- /Internal/+--+{-# INLINE_NORMAL parseBreak #-}+parseBreak ::+ (MonadIO m, Unbox a)+ => PR.Parser a m b+ -> StreamK m (A.Array a)+ -> m (Either ParseError b, StreamK m (A.Array a))+{-+parseBreak p s =+ fmap fromStreamD <$> parseBreakD (PRD.fromParserK p) (toStreamD s)+-}+parseBreak p = Array.parseBreak (Array.toParserK p)++-------------------------------------------------------------------------------+-- Elimination - Running Array Folds and parsers+-------------------------------------------------------------------------------++-- | Note that this is not the same as using a @Parser (Array a) m b@ with the+-- regular "Streamly.Internal.Data.IsStream.parse" function. The regular parse+-- would consume the input arrays as single unit. This parser parses in the way+-- as described in the ChunkFold module. The input arrays are treated as @n@+-- element units and can be consumed partially. The remaining elements are+-- inserted in the source stream as an array.+--+{-# INLINE_NORMAL runArrayParserDBreak #-}+runArrayParserDBreak ::+ forall m a b. (MonadIO m, Unbox a)+ => PRD.Parser (Array a) m b+ -> D.Stream m (Array.Array a)+ -> m (Either ParseError b, D.Stream m (Array.Array a))+runArrayParserDBreak+ (PRD.Parser pstep initial extract)+ stream@(D.Stream step state) = do++ res <- initial+ case res of+ PRD.IPartial s -> go SPEC state (List []) s+ PRD.IDone b -> return (Right b, stream)+ PRD.IError err -> return (Left (ParseError err), stream)++ where++ -- "backBuf" contains last few items in the stream that we may have to+ -- backtrack to.+ --+ -- XXX currently we are using a dumb list based approach for backtracking+ -- buffer. This can be replaced by a sliding/ring buffer using Data.Array.+ -- That will allow us more efficient random back and forth movement.+ go _ st backBuf !pst = do+ r <- step defState st+ case r of+ D.Yield x s -> gobuf SPEC [x] s backBuf pst+ D.Skip s -> go SPEC s backBuf pst+ D.Stop -> goStop backBuf pst++ gobuf !_ [] s backBuf !pst = go SPEC s backBuf pst+ gobuf !_ (x:xs) s backBuf !pst = do+ pRes <- pstep pst x+ case pRes of+ PR.Partial 0 pst1 ->+ gobuf SPEC xs s (List []) pst1+ PR.Partial n pst1 -> do+ assert+ (n <= sum (map Array.length (x:getList backBuf)))+ (return ())+ let src0 = takeArrayListRev n (x:getList backBuf)+ src = Prelude.reverse src0 ++ xs+ gobuf SPEC src s (List []) pst1+ PR.Continue 0 pst1 ->+ gobuf SPEC xs s (List (x:getList backBuf)) pst1+ PR.Continue n pst1 -> do+ assert+ (n <= sum (map Array.length (x:getList backBuf)))+ (return ())+ let (src0, buf1) = splitAtArrayListRev n (x:getList backBuf)+ src = Prelude.reverse src0 ++ xs+ gobuf SPEC src s (List buf1) pst1+ PR.Done 0 b -> do+ let str = D.append (D.fromList xs) (D.Stream step s)+ return (Right b, str)+ PR.Done n b -> do+ assert+ (n <= sum (map Array.length (x:getList backBuf)))+ (return ())+ let src0 = takeArrayListRev n (x:getList backBuf)+ src = Prelude.reverse src0 ++ xs+ return (Right b, D.append (D.fromList src) (D.Stream step s))+ PR.SError err -> do+ let src0 = x:getList backBuf+ src = Prelude.reverse src0 ++ x:xs+ strm = D.append (D.fromList src) (D.Stream step s)+ return (Left (ParseError err), strm)++ -- This is a simplified gobuf+ goExtract _ [] backBuf !pst = goStop backBuf pst+ goExtract _ (x:xs) backBuf !pst = do+ pRes <- pstep pst x+ case pRes of+ PR.Partial 0 pst1 ->+ goExtract SPEC xs (List []) pst1+ PR.Partial n pst1 -> do+ assert+ (n <= sum (map Array.length (x:getList backBuf)))+ (return ())+ let src0 = takeArrayListRev n (x:getList backBuf)+ src = Prelude.reverse src0 ++ xs+ goExtract SPEC src (List []) pst1+ PR.Continue 0 pst1 ->+ goExtract SPEC xs (List (x:getList backBuf)) pst1+ PR.Continue n pst1 -> do+ assert+ (n <= sum (map Array.length (x:getList backBuf)))+ (return ())+ let (src0, buf1) = splitAtArrayListRev n (x:getList backBuf)+ src = Prelude.reverse src0 ++ xs+ goExtract SPEC src (List buf1) pst1+ PR.Done 0 b ->+ return (Right b, D.fromList xs)+ PR.Done n b -> do+ assert+ (n <= sum (map Array.length (x:getList backBuf)))+ (return ())+ let src0 = takeArrayListRev n (x:getList backBuf)+ src = Prelude.reverse src0 ++ xs+ return (Right b, D.fromList src)+ PR.SError err -> do+ let src0 = getList backBuf+ src = Prelude.reverse src0 ++ x:xs+ return (Left (ParseError err), D.fromList src)++ -- This is a simplified goExtract+ {-# INLINE goStop #-}+ goStop backBuf pst = do+ pRes <- extract pst+ case pRes of+ PR.FContinue 0 pst1 ->+ goStop backBuf pst1+ PR.FContinue n pst1 -> do+ assert+ (n <= sum (map Array.length (getList backBuf)))+ (return ())+ let (src0, buf1) = splitAtArrayListRev n (getList backBuf)+ src = Prelude.reverse src0+ goExtract SPEC src (List buf1) pst1+ PR.FDone 0 b -> return (Right b, D.nil)+ PR.FDone n b -> do+ assert+ (n <= sum (map Array.length (getList backBuf)))+ (return ())+ let src0 = takeArrayListRev n (getList backBuf)+ src = Prelude.reverse src0+ return (Right b, D.fromList src)+ PR.FError err -> do+ let src0 = getList backBuf+ src = Prelude.reverse src0+ return (Left (ParseError err), D.fromList src)++{-+-- | Parse an array stream using the supplied 'Parser'. Returns the parse+-- result and the unconsumed stream. Throws 'ParseError' if the parse fails.+--+-- /Internal/+--+{-# INLINE parseArr #-}+parseArr ::+ (MonadIO m, MonadThrow m, Unbox a)+ => ASF.Parser a m b+ -> Stream m (A.Array a)+ -> m (b, Stream m (A.Array a))+parseArr p s = fmap fromStreamD <$> parseBreakD p (toStreamD s)+-}++-- | Fold an array stream using the supplied array stream 'Fold'.+--+-- /Pre-release/+--+{-# INLINE runArrayFold #-}+runArrayFold :: (MonadIO m, Unbox a) =>+ ChunkFold m a b -> StreamK m (A.Array a) -> m (Either ParseError b)+runArrayFold (ChunkFold p) s = fst <$> runArrayParserDBreak p (toStream s)++-- | Like 'fold' but also returns the remaining stream.+--+-- /Pre-release/+--+{-# INLINE runArrayFoldBreak #-}+runArrayFoldBreak :: (MonadIO m, Unbox a) =>+ ChunkFold m a b -> StreamK m (A.Array a) -> m (Either ParseError b, StreamK m (A.Array a))+runArrayFoldBreak (ChunkFold p) s =+ second fromStream <$> runArrayParserDBreak p (toStream s)++{-# ANN type ParseChunksState Fuse #-}+data ParseChunksState x inpBuf st pst =+ ParseChunksInit inpBuf st+ | ParseChunksInitBuf inpBuf+ | ParseChunksInitLeftOver inpBuf+ | ParseChunksStream st inpBuf !pst+ | ParseChunksStop inpBuf !pst+ | ParseChunksBuf inpBuf st inpBuf !pst+ | ParseChunksExtract inpBuf inpBuf !pst+ | ParseChunksYield x (ParseChunksState x inpBuf st pst)++{-# INLINE_NORMAL runArrayFoldManyD #-}+runArrayFoldManyD+ :: (Monad m, Unbox a)+ => ChunkFold m a b+ -> D.Stream m (Array a)+ -> D.Stream m (Either ParseError b)+runArrayFoldManyD+ (ChunkFold (PRD.Parser pstep initial extract)) (D.Stream step state) =++ D.Stream stepOuter (ParseChunksInit [] state)++ where++ {-# INLINE_LATE stepOuter #-}+ -- Buffer is empty, get the first element from the stream, initialize the+ -- fold and then go to stream processing loop.+ stepOuter gst (ParseChunksInit [] st) = do+ r <- step (adaptState gst) st+ case r of+ D.Yield x s -> do+ res <- initial+ case res of+ PRD.IPartial ps ->+ return $ D.Skip $ ParseChunksBuf [x] s [] ps+ PRD.IDone pb -> do+ let next = ParseChunksInit [x] s+ return $ D.Skip $ ParseChunksYield (Right pb) next+ PRD.IError err -> do+ let next = ParseChunksInitLeftOver []+ return+ $ D.Skip+ $ ParseChunksYield (Left (ParseError err)) next+ D.Skip s -> return $ D.Skip $ ParseChunksInit [] s+ D.Stop -> return D.Stop++ -- Buffer is not empty, go to buffered processing loop+ stepOuter _ (ParseChunksInit src st) = do+ res <- initial+ case res of+ PRD.IPartial ps ->+ return $ D.Skip $ ParseChunksBuf src st [] ps+ PRD.IDone pb ->+ let next = ParseChunksInit src st+ in return $ D.Skip $ ParseChunksYield (Right pb) next+ PRD.IError err -> do+ let next = ParseChunksInitLeftOver []+ return+ $ D.Skip+ $ ParseChunksYield (Left (ParseError err)) next++ -- This is a simplified ParseChunksInit+ stepOuter _ (ParseChunksInitBuf src) = do+ res <- initial+ case res of+ PRD.IPartial ps ->+ return $ D.Skip $ ParseChunksExtract src [] ps+ PRD.IDone pb ->+ let next = ParseChunksInitBuf src+ in return $ D.Skip $ ParseChunksYield (Right pb) next+ PRD.IError err -> do+ let next = ParseChunksInitLeftOver []+ return+ $ D.Skip+ $ ParseChunksYield (Left (ParseError err)) next++ -- XXX we just discard any leftover input at the end+ stepOuter _ (ParseChunksInitLeftOver _) = return D.Stop++ -- Buffer is empty, process elements from the stream+ stepOuter gst (ParseChunksStream st backBuf pst) = do+ r <- step (adaptState gst) st+ case r of+ D.Yield x s -> do+ pRes <- pstep pst x+ case pRes of+ PR.Partial 0 pst1 ->+ return $ D.Skip $ ParseChunksStream s [] pst1+ PR.Partial n pst1 -> do+ assert+ (n <= sum (map Array.length (x:backBuf)))+ (return ())+ let src0 = takeArrayListRev n (x:backBuf)+ src = Prelude.reverse src0+ return $ D.Skip $ ParseChunksBuf src s [] pst1+ PR.Continue 0 pst1 ->+ return $ D.Skip $ ParseChunksStream s (x:backBuf) pst1+ PR.Continue n pst1 -> do+ assert+ (n <= sum (map Array.length (x:backBuf)))+ (return ())+ let (src0, buf1) = splitAtArrayListRev n (x:backBuf)+ src = Prelude.reverse src0+ return $ D.Skip $ ParseChunksBuf src s buf1 pst1+ PR.Done 0 b -> do+ return $ D.Skip $+ ParseChunksYield (Right b) (ParseChunksInit [] s)+ PR.Done n b -> do+ assert+ (n <= sum (map Array.length (x:backBuf)))+ (return ())+ let src0 = takeArrayListRev n (x:backBuf)+ src = Prelude.reverse src0+ next = ParseChunksInit src s+ return+ $ D.Skip+ $ ParseChunksYield (Right b) next+ PR.SError err -> do+ let next = ParseChunksInitLeftOver []+ return+ $ D.Skip+ $ ParseChunksYield (Left (ParseError err)) next++ D.Skip s -> return $ D.Skip $ ParseChunksStream s backBuf pst+ D.Stop -> return $ D.Skip $ ParseChunksStop backBuf pst++ -- go back to stream processing mode+ stepOuter _ (ParseChunksBuf [] s buf pst) =+ return $ D.Skip $ ParseChunksStream s buf pst++ -- buffered processing loop+ stepOuter _ (ParseChunksBuf (x:xs) s backBuf pst) = do+ pRes <- pstep pst x+ case pRes of+ PR.Partial 0 pst1 ->+ return $ D.Skip $ ParseChunksBuf xs s [] pst1+ PR.Partial n pst1 -> do+ assert (n <= sum (map Array.length (x:backBuf))) (return ())+ let src0 = takeArrayListRev n (x:backBuf)+ src = Prelude.reverse src0 ++ xs+ return $ D.Skip $ ParseChunksBuf src s [] pst1+ PR.Continue 0 pst1 ->+ return $ D.Skip $ ParseChunksBuf xs s (x:backBuf) pst1+ PR.Continue n pst1 -> do+ assert (n <= sum (map Array.length (x:backBuf))) (return ())+ let (src0, buf1) = splitAtArrayListRev n (x:backBuf)+ src = Prelude.reverse src0 ++ xs+ return $ D.Skip $ ParseChunksBuf src s buf1 pst1+ PR.Done 0 b ->+ return+ $ D.Skip+ $ ParseChunksYield (Right b) (ParseChunksInit xs s)+ PR.Done n b -> do+ assert (n <= sum (map Array.length (x:backBuf))) (return ())+ let src0 = takeArrayListRev n (x:backBuf)+ src = Prelude.reverse src0 ++ xs+ return+ $ D.Skip+ $ ParseChunksYield (Right b) (ParseChunksInit src s)+ PR.SError err -> do+ let next = ParseChunksInitLeftOver []+ return+ $ D.Skip+ $ ParseChunksYield (Left (ParseError err)) next++ -- This is a simplified ParseChunksBuf+ stepOuter _ (ParseChunksExtract [] buf pst) =+ return $ D.Skip $ ParseChunksStop buf pst++ stepOuter _ (ParseChunksExtract (x:xs) backBuf pst) = do+ pRes <- pstep pst x+ case pRes of+ PR.Partial 0 pst1 ->+ return $ D.Skip $ ParseChunksExtract xs [] pst1+ PR.Partial n pst1 -> do+ assert (n <= sum (map Array.length (x:backBuf))) (return ())+ let src0 = takeArrayListRev n (x:backBuf)+ src = Prelude.reverse src0 ++ xs+ return $ D.Skip $ ParseChunksExtract src [] pst1+ PR.Continue 0 pst1 ->+ return $ D.Skip $ ParseChunksExtract xs (x:backBuf) pst1+ PR.Continue n pst1 -> do+ assert (n <= sum (map Array.length (x:backBuf))) (return ())+ let (src0, buf1) = splitAtArrayListRev n (x:backBuf)+ src = Prelude.reverse src0 ++ xs+ return $ D.Skip $ ParseChunksExtract src buf1 pst1+ PR.Done 0 b ->+ return+ $ D.Skip+ $ ParseChunksYield (Right b) (ParseChunksInitBuf xs)+ PR.Done n b -> do+ assert (n <= sum (map Array.length (x:backBuf))) (return ())+ let src0 = takeArrayListRev n (x:backBuf)+ src = Prelude.reverse src0 ++ xs+ return+ $ D.Skip+ $ ParseChunksYield (Right b) (ParseChunksInitBuf src)+ PR.SError err -> do+ let next = ParseChunksInitLeftOver []+ return+ $ D.Skip+ $ ParseChunksYield (Left (ParseError err)) next+++ -- This is a simplified ParseChunksExtract+ stepOuter _ (ParseChunksStop backBuf pst) = do+ pRes <- extract pst+ case pRes of+ PR.FContinue 0 pst1 ->+ return $ D.Skip $ ParseChunksStop backBuf pst1+ PR.FContinue n pst1 -> do+ assert (n <= sum (map Array.length backBuf)) (return ())+ let (src0, buf1) = splitAtArrayListRev n backBuf+ src = Prelude.reverse src0+ return $ D.Skip $ ParseChunksExtract src buf1 pst1+ PR.FDone 0 b ->+ return+ $ D.Skip+ $ ParseChunksYield (Right b) (ParseChunksInitLeftOver [])+ PR.FDone n b -> do+ assert (n <= sum (map Array.length backBuf)) (return ())+ let src0 = takeArrayListRev n backBuf+ src = Prelude.reverse src0+ return+ $ D.Skip+ $ ParseChunksYield (Right b) (ParseChunksInitBuf src)+ PR.FError err -> do+ let next = ParseChunksInitLeftOver []+ return+ $ D.Skip+ $ ParseChunksYield (Left (ParseError err)) next++ stepOuter _ (ParseChunksYield a next) = return $ D.Yield a next++-- | Apply an 'ChunkFold' repeatedly on an array stream and emit the+-- fold outputs in the output stream.+--+-- See "Streamly.Data.Stream.foldMany" for more details.+--+-- /Pre-release/+{-# INLINE runArrayFoldMany #-}+runArrayFoldMany+ :: (Monad m, Unbox a)+ => ChunkFold m a b+ -> StreamK m (Array a)+ -> StreamK m (Either ParseError b)+runArrayFoldMany p m = fromStream $ runArrayFoldManyD p (toStream m)
src/Streamly/Internal/Data/Array/Type.hs view
@@ -1,592 +1,1557 @@ {-# LANGUAGE CPP #-}--- |--- Module : Streamly.Internal.Data.Array.Type--- Copyright : (c) 2020 Composewell Technologies------ License : BSD3-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ See notes in "Streamly.Internal.Data.Array.Mut.Type"----module Streamly.Internal.Data.Array.Type- (- -- $arrayNotes- Array (..)- , asPtrUnsafe-- -- * Freezing and Thawing- , unsafeFreeze- , unsafeFreezeWithShrink- , unsafeThaw-- -- * Pinning and Unpinning- , pin- , unpin-- -- * Construction- , splice-- , fromList- , fromListN- , fromListRev- , fromListRevN- , fromStreamDN- , fromStreamD-- -- * Split- , breakOn-- -- * Elimination- , unsafeIndexIO- , unsafeIndex -- getIndexUnsafe- , byteLength- , length-- , foldl'- , foldr- , splitAt-- , toStreamD- , toStreamDRev- , toStreamK- , toStreamKRev- , toStream- , toStreamRev- , read- , readRev- , readerRev- , toList-- -- * Folds- , writeWith- , writeN- , writeNUnsafe- , MA.ArrayUnsafe (..)- , writeNAligned- , write-- -- * Streams of arrays- , chunksOf- , bufferChunks- , flattenArrays- , flattenArraysRev- )-where--#include "ArrayMacros.h"-#include "inline.hs"--import Control.Exception (assert)-import Control.Monad (replicateM)-import Control.Monad.IO.Class (MonadIO(..))-import Data.Functor.Identity (Identity(..))-import Data.Proxy (Proxy(..))-import Data.Word (Word8)-import GHC.Base (build)-import GHC.Exts (IsList, IsString(..))--import GHC.IO (unsafePerformIO)-import GHC.Ptr (Ptr(..))-import Streamly.Internal.Data.Array.Mut.Type (MutArray(..), MutableByteArray)-import Streamly.Internal.Data.Fold.Type (Fold(..))-import Streamly.Internal.Data.Stream.StreamD.Type (Stream)-import Streamly.Internal.Data.Unboxed (Unbox, peekWith, sizeOf)-import Streamly.Internal.Data.Unfold.Type (Unfold(..))-import Text.Read (readPrec)--import Prelude hiding (length, foldr, read, unlines, splitAt)--import qualified GHC.Exts as Exts-import qualified Streamly.Internal.Data.Array.Mut.Type as MA-import qualified Streamly.Internal.Data.Stream.StreamD.Type as D-import qualified Streamly.Internal.Data.Stream.StreamK.Type as K-import qualified Streamly.Internal.Data.Unboxed as Unboxed-import qualified Streamly.Internal.Data.Unfold.Type as Unfold-import qualified Text.ParserCombinators.ReadPrec as ReadPrec--import Streamly.Internal.System.IO (unsafeInlineIO, defaultChunkSize)--#include "DocTestDataArray.hs"------------------------------------------------------------------------------------ Array Data Type------------------------------------------------------------------------------------ $arrayNotes------ We can use an 'Unbox' constraint in the Array type and the constraint can--- be automatically provided to a function that pattern matches on the Array--- type. However, it has huge performance cost, so we do not use it.--- Investigate a GHC improvement possiblity.----data Array a =-#ifdef DEVBUILD- Unbox a =>-#endif- -- All offsets are in terms of bytes from the start of arraycontents- Array- { arrContents :: {-# UNPACK #-} !MutableByteArray- , arrStart :: {-# UNPACK #-} !Int -- offset- , arrEnd :: {-# UNPACK #-} !Int -- offset + len- }------------------------------------------------------------------------------------ Utility functions------------------------------------------------------------------------------------ | Use an @Array a@ as @Ptr a@.------ See 'MA.asPtrUnsafe' in the Mutable array module for more details.------ /Unsafe/------ /Pre-release/----asPtrUnsafe :: MonadIO m => Array a -> (Ptr a -> m b) -> m b-asPtrUnsafe arr = MA.asPtrUnsafe (unsafeThaw arr)------------------------------------------------------------------------------------ Freezing and Thawing------------------------------------------------------------------------------------ XXX For debugging we can track slices/references through a weak IORef. Then--- trigger a GC after freeze/thaw and assert that there are no references--- remaining.---- | Makes an immutable array using the underlying memory of the mutable--- array.------ Please make sure that there are no other references to the mutable array--- lying around, so that it is never used after freezing it using--- /unsafeFreeze/. If the underlying array is mutated, the immutable promise--- is lost.------ /Pre-release/-{-# INLINE unsafeFreeze #-}-unsafeFreeze :: MutArray a -> Array a-unsafeFreeze (MutArray ac as ae _) = Array ac as ae---- | Similar to 'unsafeFreeze' but uses 'MA.rightSize' on the mutable array--- first.-{-# INLINE unsafeFreezeWithShrink #-}-unsafeFreezeWithShrink :: Unbox a => MutArray a -> Array a-unsafeFreezeWithShrink arr = unsafePerformIO $ do- MutArray ac as ae _ <- MA.rightSize arr- return $ Array ac as ae---- | Makes a mutable array using the underlying memory of the immutable array.------ Please make sure that there are no other references to the immutable array--- lying around, so that it is never used after thawing it using /unsafeThaw/.--- If the resulting array is mutated, any references to the older immutable--- array are mutated as well.------ /Pre-release/-{-# INLINE unsafeThaw #-}-unsafeThaw :: Array a -> MutArray a-unsafeThaw (Array ac as ae) = MutArray ac as ae ae------------------------------------------------------------------------------------ Pinning & Unpinning----------------------------------------------------------------------------------{-# INLINE pin #-}-pin :: Array a -> IO (Array a)-pin = fmap unsafeFreeze . MA.pin . unsafeThaw--{-# INLINE unpin #-}-unpin :: Array a -> IO (Array a)-unpin = fmap unsafeFreeze . MA.unpin . unsafeThaw------------------------------------------------------------------------------------ Construction------------------------------------------------------------------------------------ Splice two immutable arrays creating a new array.-{-# INLINE splice #-}-splice :: (MonadIO m, Unbox a) => Array a -> Array a -> m (Array a)-splice arr1 arr2 =- unsafeFreeze <$> MA.splice (unsafeThaw arr1) (unsafeThaw arr2)---- | Create an 'Array' from the first N elements of a list. The array is--- allocated to size N, if the list terminates before N elements then the--- array may hold less than N elements.----{-# INLINABLE fromListN #-}-fromListN :: Unbox a => Int -> [a] -> Array a-fromListN n xs = unsafePerformIO $ unsafeFreeze <$> MA.fromListN n xs---- | Create an 'Array' from the first N elements of a list in reverse order.--- The array is allocated to size N, if the list terminates before N elements--- then the array may hold less than N elements.------ /Pre-release/-{-# INLINABLE fromListRevN #-}-fromListRevN :: Unbox a => Int -> [a] -> Array a-fromListRevN n xs = unsafePerformIO $ unsafeFreeze <$> MA.fromListRevN n xs---- | Create an 'Array' from a list. The list must be of finite size.----{-# INLINE fromList #-}-fromList :: Unbox a => [a] -> Array a-fromList xs = unsafePerformIO $ unsafeFreeze <$> MA.fromList xs---- | Create an 'Array' from a list in reverse order. The list must be of finite--- size.------ /Pre-release/-{-# INLINABLE fromListRev #-}-fromListRev :: Unbox a => [a] -> Array a-fromListRev xs = unsafePerformIO $ unsafeFreeze <$> MA.fromListRev xs--{-# INLINE_NORMAL fromStreamDN #-}-fromStreamDN :: forall m a. (MonadIO m, Unbox a)- => Int -> D.Stream m a -> m (Array a)-fromStreamDN limit str = unsafeFreeze <$> MA.fromStreamDN limit str--{-# INLINE_NORMAL fromStreamD #-}-fromStreamD :: forall m a. (MonadIO m, Unbox a)- => D.Stream m a -> m (Array a)-fromStreamD str = unsafeFreeze <$> MA.fromStreamD str------------------------------------------------------------------------------------ Streams of arrays----------------------------------------------------------------------------------{-# INLINE bufferChunks #-}-bufferChunks :: (MonadIO m, Unbox a) =>- D.Stream m a -> m (K.StreamK m (Array a))-bufferChunks m = D.foldr K.cons K.nil $ chunksOf defaultChunkSize m---- | @chunksOf n stream@ groups the elements in the input stream into arrays of--- @n@ elements each.------ Same as the following but may be more efficient:------ >>> chunksOf n = Stream.foldMany (Array.writeN n)------ /Pre-release/-{-# INLINE_NORMAL chunksOf #-}-chunksOf :: forall m a. (MonadIO m, Unbox a)- => Int -> D.Stream m a -> D.Stream m (Array a)-chunksOf n str = D.map unsafeFreeze $ MA.chunksOf n str---- | Use the "read" unfold instead.------ @flattenArrays = unfoldMany read@------ We can try this if there are any fusion issues in the unfold.----{-# INLINE_NORMAL flattenArrays #-}-flattenArrays :: forall m a. (MonadIO m, Unbox a)- => D.Stream m (Array a) -> D.Stream m a-flattenArrays = MA.flattenArrays . D.map unsafeThaw---- | Use the "readRev" unfold instead.------ @flattenArrays = unfoldMany readRev@------ We can try this if there are any fusion issues in the unfold.----{-# INLINE_NORMAL flattenArraysRev #-}-flattenArraysRev :: forall m a. (MonadIO m, Unbox a)- => D.Stream m (Array a) -> D.Stream m a-flattenArraysRev = MA.flattenArraysRev . D.map unsafeThaw---- Drops the separator byte-{-# INLINE breakOn #-}-breakOn :: MonadIO m- => Word8 -> Array Word8 -> m (Array Word8, Maybe (Array Word8))-breakOn sep arr = do- (a, b) <- MA.breakOn sep (unsafeThaw arr)- return (unsafeFreeze a, unsafeFreeze <$> b)------------------------------------------------------------------------------------ Elimination------------------------------------------------------------------------------------ | Return element at the specified index without checking the bounds.------ Unsafe because it does not check the bounds of the array.-{-# INLINE_NORMAL unsafeIndexIO #-}-unsafeIndexIO :: forall a. Unbox a => Int -> Array a -> IO a-unsafeIndexIO i arr = MA.getIndexUnsafe i (unsafeThaw arr)---- | Return element at the specified index without checking the bounds.-{-# INLINE_NORMAL unsafeIndex #-}-unsafeIndex :: forall a. Unbox a => Int -> Array a -> a-unsafeIndex i arr = let !r = unsafeInlineIO $ unsafeIndexIO i arr in r---- | /O(1)/ Get the byte length of the array.----{-# INLINE byteLength #-}-byteLength :: Array a -> Int-byteLength = MA.byteLength . unsafeThaw---- | /O(1)/ Get the length of the array i.e. the number of elements in the--- array.----{-# INLINE length #-}-length :: Unbox a => Array a -> Int-length arr = MA.length (unsafeThaw arr)---- | Unfold an array into a stream in reverse order.----{-# INLINE_NORMAL readerRev #-}-readerRev :: forall m a. (Monad m, Unbox a) => Unfold m (Array a) a-readerRev = Unfold.lmap unsafeThaw $ MA.readerRevWith (return . unsafeInlineIO)--{-# INLINE_NORMAL toStreamD #-}-toStreamD :: forall m a. (Monad m, Unbox a) => Array a -> D.Stream m a-toStreamD arr = MA.toStreamDWith (return . unsafeInlineIO) (unsafeThaw arr)--{-# INLINE toStreamK #-}-toStreamK :: forall m a. (Monad m, Unbox a) => Array a -> K.StreamK m a-toStreamK arr = MA.toStreamKWith (return . unsafeInlineIO) (unsafeThaw arr)--{-# INLINE_NORMAL toStreamDRev #-}-toStreamDRev :: forall m a. (Monad m, Unbox a) => Array a -> D.Stream m a-toStreamDRev arr =- MA.toStreamDRevWith (return . unsafeInlineIO) (unsafeThaw arr)--{-# INLINE toStreamKRev #-}-toStreamKRev :: forall m a. (Monad m, Unbox a) => Array a -> K.StreamK m a-toStreamKRev arr =- MA.toStreamKRevWith (return . unsafeInlineIO) (unsafeThaw arr)---- | Convert an 'Array' into a stream.------ /Pre-release/-{-# INLINE_EARLY read #-}-read :: (Monad m, Unbox a) => Array a -> Stream m a-read = toStreamD---- | Same as 'read'----{-# DEPRECATED toStream "Please use 'read' instead." #-}-{-# INLINE_EARLY toStream #-}-toStream :: (Monad m, Unbox a) => Array a -> Stream m a-toStream = read--- XXX add fallback to StreamK rule--- {-# RULES "Streamly.Array.read fallback to StreamK" [1]--- forall a. S.readK (read a) = K.fromArray a #-}---- | Convert an 'Array' into a stream in reverse order.------ /Pre-release/-{-# INLINE_EARLY readRev #-}-readRev :: (Monad m, Unbox a) => Array a -> Stream m a-readRev = toStreamDRev---- | Same as 'readRev'----{-# DEPRECATED toStreamRev "Please use 'readRev' instead." #-}-{-# INLINE_EARLY toStreamRev #-}-toStreamRev :: (Monad m, Unbox a) => Array a -> Stream m a-toStreamRev = readRev---- XXX add fallback to StreamK rule--- {-# RULES "Streamly.Array.readRev fallback to StreamK" [1]--- forall a. S.toStreamK (readRev a) = K.revFromArray a #-}--{-# INLINE_NORMAL foldl' #-}-foldl' :: forall a b. Unbox a => (b -> a -> b) -> b -> Array a -> b-foldl' f z arr = runIdentity $ D.foldl' f z $ toStreamD arr--{-# INLINE_NORMAL foldr #-}-foldr :: Unbox a => (a -> b -> b) -> b -> Array a -> b-foldr f z arr = runIdentity $ D.foldr f z $ toStreamD arr---- | Create two slices of an array without copying the original array. The--- specified index @i@ is the first index of the second slice.----splitAt :: Unbox a => Int -> Array a -> (Array a, Array a)-splitAt i arr = (unsafeFreeze a, unsafeFreeze b)- where- (a, b) = MA.splitAt i (unsafeThaw arr)---- Use foldr/build fusion to fuse with list consumers--- This can be useful when using the IsList instance-{-# INLINE_LATE toListFB #-}-toListFB :: forall a b. Unbox a => (a -> b -> b) -> b -> Array a -> b-toListFB c n Array{..} = go arrStart- where-- go p | assert (p <= arrEnd) (p == arrEnd) = n- go p =- -- 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 peekWith at them.- let !x = unsafeInlineIO $ peekWith arrContents p- in c x (go (INDEX_NEXT(p,a)))---- | Convert an 'Array' into a list.----{-# INLINE toList #-}-toList :: Unbox a => Array a -> [a]-toList s = build (\c n -> toListFB c n s)------------------------------------------------------------------------------------ Folds------------------------------------------------------------------------------------ | @writeN n@ folds a maximum of @n@ elements from the input stream to an--- 'Array'.----{-# INLINE_NORMAL writeN #-}-writeN :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)-writeN = fmap unsafeFreeze . MA.writeN---- | @writeNAligned alignment n@ folds a maximum of @n@ elements from the input--- stream to an 'Array' aligned to the given size.------ /Pre-release/----{-# INLINE_NORMAL writeNAligned #-}-writeNAligned :: forall m a. (MonadIO m, Unbox a)- => Int -> Int -> Fold m a (Array a)-writeNAligned alignSize = fmap unsafeFreeze . MA.writeNAligned alignSize---- | Like 'writeN' but does not check the array bounds when writing. The fold--- driver must not call the step function more than 'n' times otherwise it will--- corrupt the memory and crash. This function exists mainly because any--- conditional in the step function blocks fusion causing 10x performance--- slowdown.----{-# INLINE_NORMAL writeNUnsafe #-}-writeNUnsafe :: forall m a. (MonadIO m, Unbox a)- => Int -> Fold m a (Array a)-writeNUnsafe n = unsafeFreeze <$> MA.writeNUnsafe n--{-# INLINE_NORMAL writeWith #-}-writeWith :: forall m a. (MonadIO m, Unbox a)- => Int -> Fold m a (Array a)--- writeWith n = FL.rmapM spliceArrays $ toArraysOf n-writeWith elemCount = unsafeFreeze <$> MA.writeWith elemCount---- | Fold the whole input to a single array.------ /Caution! Do not use this on infinite streams./----{-# INLINE write #-}-write :: forall m a. (MonadIO m, Unbox a) => Fold m a (Array a)-write = fmap unsafeFreeze MA.write------------------------------------------------------------------------------------ Instances----------------------------------------------------------------------------------instance (Show a, Unbox a) => Show (Array a) where- {-# INLINE show #-}- show arr = "fromList " ++ show (toList arr)--instance (Unbox a, Read a, Show a) => Read (Array a) where- {-# INLINE readPrec #-}- readPrec = do- fromListWord <- replicateM 9 ReadPrec.get- if fromListWord == "fromList "- then fromList <$> readPrec- else ReadPrec.pfail--instance (a ~ Char) => IsString (Array a) where- {-# INLINE fromString #-}- fromString = fromList---- GHC versions 8.0 and below cannot derive IsList-instance Unbox a => IsList (Array a) where- type (Item (Array a)) = a- {-# INLINE fromList #-}- fromList = fromList- {-# INLINE fromListN #-}- fromListN = fromListN- {-# INLINE toList #-}- toList = toList---- XXX we are assuming that Unboxed equality means element equality. This may--- or may not be correct? arrcmp is 40% faster compared to stream equality.-instance (Unbox a, Eq a) => Eq (Array a) where- {-# INLINE (==) #-}- arr1 == arr2 =- (==) EQ $ unsafeInlineIO $! unsafeThaw arr1 `MA.cmp` unsafeThaw arr2--instance (Unbox a, Ord a) => Ord (Array a) where- {-# INLINE compare #-}- compare arr1 arr2 = runIdentity $- D.cmpBy compare (toStreamD arr1) (toStreamD arr2)-- -- Default definitions defined in base do not have an INLINE on them, so we- -- replicate them here with an INLINE.- {-# 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 }-- -- These two default methods use '<=' rather than 'compare'- -- because the latter is often more expensive- {-# INLINE max #-}- max x y = if x <= y then y else x-- {-# INLINE min #-}- min x y = if x <= y then x else y--#ifdef DEVBUILD--- Definitions using the Unboxed constraint from the Array type. These are to--- make the Foldable instance possible though it is much slower (7x slower).----{-# INLINE_NORMAL _toStreamD_ #-}-_toStreamD_ :: forall m a. MonadIO m => Int -> Array a -> D.Stream m a-_toStreamD_ size Array{..} = D.Stream step arrStart-- where-- {-# INLINE_LATE step #-}- step _ p | p == arrEnd = return D.Stop- step _ p = liftIO $ do- x <- peekWith arrContents p- return $ D.Yield x (p + size)--{--XXX Why isn't Unboxed implicit? This does not compile unless I use the Unboxed-contraint.-{-# INLINE_NORMAL _foldr #-}-_foldr :: forall a b. (a -> b -> b) -> b -> Array a -> b-_foldr f z arr =- let !n = SIZE_OF(a)- in unsafePerformIO $ D.foldr f z $ toStreamD_ n arr--- | Note that the 'Foldable' instance is 7x slower than the direct--- operations.-instance Foldable Array where- foldr = _foldr--}--#endif------------------------------------------------------------------------------------ Semigroup and Monoid----------------------------------------------------------------------------------instance Unbox a => Semigroup (Array a) where- arr1 <> arr2 = unsafePerformIO $ splice arr1 arr2--nil ::-#ifdef DEVBUILD- Unbox a =>-#endif- Array a-nil = Array Unboxed.nil 0 0--instance Unbox a => Monoid (Array a) where- mempty = nil- mappend = (<>)+{-# LANGUAGE TypeFamilies #-}+-- Must come after TypeFamilies, otherwise it is re-enabled.+-- MonoLocalBinds enabled by TypeFamilies causes perf regressions in general.+{-# LANGUAGE NoMonoLocalBinds #-}+{-# OPTIONS_GHC -Wno-deprecations #-}+-- |+-- Module : Streamly.Internal.Data.Array.Type+-- Copyright : (c) 2020 Composewell Technologies+-- License : BSD3-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- See notes in "Streamly.Internal.Data.MutArray.Type"+--++module Streamly.Internal.Data.Array.Type+ (+ -- ** Type+ -- $arrayNotes+ Array (..)++ -- ** Conversion+ -- *** Mutable and Immutable+ , unsafeFreeze+ , unsafeFreezeWithShrink+ , unsafeThaw++ -- *** Pinned and Unpinned+ , pin+ , unpin+ , isPinned++ -- *** Casting+ , unsafePinnedAsPtr+ , unsafeAsForeignPtr++ -- * Subarrays+ , unsafeSliceOffLen++ -- ** Construction+ , empty++ -- * Random Access+ -- , (!!)+ , getIndex+ , getIndexRev+ , head+ , last+ , init+ , tail+ , uncons+ , unsnoc++ -- *** Slicing+ -- | Get a subarray without copying+ , unsafeBreakAt+ , breakAt+ , breakEndByWord8_+ , breakEndBy+ , breakEndBy_+ , revBreakEndBy+ , revBreakEndBy_+ -- drop+ -- dropRev/dropEnd+ , dropAround+ , dropWhile+ , revDropWhile++ -- *** Stream Folds+ , unsafeMakePure+ , createOf+ , createOf'+ , unsafeCreateOf+ , unsafeCreateOf'+ , create+ , create'+ , createWith++ -- *** From containers+ , fromListN+ , fromListN'+ , fromList+ , fromList'+ , fromListRevN+ , fromListRev+ , fromStreamN+ , fromStream+ , fromPureStreamN+ , fromPureStream+ , fromCString#+ , fromCString+ , fromW16CString#+ , fromW16CString+ , fromPtrN+ , fromChunks+ , fromChunksK+ , unsafeFromForeignPtr++ -- ** Reading++ -- *** Indexing+ , unsafeGetIndexIO+ , unsafeGetIndexRevIO+ , unsafeGetIndex+ , unsafeGetIndexRev++ -- *** To Streams+ , read+ , readRev+ , toStreamK+ , toStreamKRev++ -- *** To Containers+ , toList++ -- *** Unfolds+ , producer -- experimental+ , unsafeReader+ , reader+ , readerRev++ -- *** Size+ , null+ , length+ , byteLength++ -- ** Folding+ , foldl'+ , foldr+ , byteCmp+ , byteEq+ , listCmp+ , listEq++ -- ** Appending+ , splice -- XXX requires MonadIO+ -- appendString+ -- appendCString/CString#++ -- ** Streams of arrays+ -- *** Chunk+ -- | Group a stream into arrays.+ , chunksOf+ , chunksOf'+ , buildChunks+ , chunksEndBy+ , chunksEndBy'+ , chunksEndByLn+ , chunksEndByLn'++ -- *** Split+ -- | Split an array into slices.+ , splitEndBy+ , splitEndBy_++ -- *** Concat+ -- | Append the arrays in a stream to form a stream of elements.+ , concat+ , concatRev++ -- *** Compact+ -- | Append the arrays in a stream to form a stream of larger arrays.+ , createCompactMin+ , createCompactMin'+ , scanCompactMin+ , scanCompactMin'+ , compactMin++ -- ** Deprecated+ , breakOn+ , splitAt+ , asPtrUnsafe+ , unsafeIndex+ , bufferChunks+ , flattenArrays+ , flattenArraysRev+ , fromArrayStreamK+ , fromStreamDN+ , fromStreamD+ , toStreamD+ , toStreamDRev+ , toStream+ , toStreamRev+ , nil+ , writeWith+ , writeN+ , pinnedWriteN+ , writeNUnsafe+ , pinnedWriteNUnsafe+ , pinnedWriteNAligned+ , write+ , pinnedWrite+ , fromByteStr#+ , fromByteStr+ , fCompactGE+ , fPinnedCompactGE+ , lCompactGE+ , lPinnedCompactGE+ , compactGE+ , pinnedCreateOf+ , unsafePinnedCreateOf+ , pinnedCreate+ , pinnedFromListN+ , pinnedFromList+ , pinnedChunksOf+ , unsafeIndexIO+ , getIndexUnsafe+ , readerUnsafe+ )+where++#include "ArrayMacros.h"+#include "deprecation.h"+#include "inline.hs"++import Control.Exception (assert)+import Control.Monad (replicateM, when)+import Control.Monad.IO.Class (MonadIO(..))+import Data.Char (ord)+import Data.Functor.Identity (Identity(..))+import Data.Int (Int8, Int16, Int32, Int64)+import Data.Proxy (Proxy(..))+import Data.Word (Word8, Word16, Word32, Word64)+import GHC.Base (build)+import GHC.Exts (IsList, IsString(..), Addr#, minusAddr#)+import GHC.Int (Int(..))+import GHC.ForeignPtr (ForeignPtr(..), ForeignPtrContents(..))++import GHC.IO (unsafePerformIO)+import GHC.Ptr (Ptr(..), nullPtr)+import Streamly.Internal.Data.Producer.Type (Producer(..))+import Streamly.Internal.Data.MutArray.Type (MutArray)+import Streamly.Internal.Data.MutByteArray.Type (MutByteArray)+import Streamly.Internal.Data.Fold.Type (Fold(..))+import Streamly.Internal.Data.Scanl.Type (Scanl (..))+import Streamly.Internal.Data.Stream.Type (Stream)+import Streamly.Internal.Data.StreamK.Type (StreamK)+import Streamly.Internal.Data.Unbox (Unbox(..))+import Streamly.Internal.Data.Unfold.Type (Unfold(..))+import Text.Read (readPrec)++import Prelude hiding+ ( Foldable(..), concat, head, init, last, read, tail, unlines, splitAt+ , dropWhile)++import qualified GHC.Exts as Exts+import qualified Streamly.Internal.Data.Fold.Type as Fold+import qualified Streamly.Internal.Data.MutArray.Type as MA+import qualified Streamly.Internal.Data.Stream.Type as D+import qualified Streamly.Internal.Data.StreamK.Type as K+import qualified Streamly.Internal.Data.MutByteArray.Type as Unboxed+import qualified Streamly.Internal.Data.Producer as Producer+import qualified Streamly.Internal.Data.Scanl.Type as Scanl+import qualified Streamly.Internal.Data.Unfold.Type as Unfold+import qualified Text.ParserCombinators.ReadPrec as ReadPrec++import Streamly.Internal.System.IO (unsafeInlineIO, defaultChunkSize)++#include "DocTestDataArray.hs"++-------------------------------------------------------------------------------+-- Notes+-------------------------------------------------------------------------------++-- IMPORTANT:++-- We need to be careful while using unsafePerformIO when array creation is+-- involved.+--+-- * We need to make sure the unsafe IO line does not float out of the binding.+-- * The order of the IO actions should be sane. For example, `touch` after `f`.+--+-- Assume the unsafe IO action floats up. If it makes sense given this+-- assumption, it's probably OK to use usafe IO.+--+-- A general approach should be never to use unsafe IO where Array creation is+-- involved or touch is involved.++-------------------------------------------------------------------------------+-- Array Data Type+-------------------------------------------------------------------------------++-- $arrayNotes+--+-- We can use an 'Unbox' constraint in the Array type and the constraint can+-- be automatically provided to a function that pattern matches on the Array+-- type. However, it has huge performance cost, so we do not use it.+-- Investigate a GHC improvement possiblity.+--+data Array a =+#ifdef DEVBUILD+ Unbox a =>+#endif+ -- All offsets are in terms of bytes from the start of arraycontents+ Array+ { arrContents :: {-# UNPACK #-} !MutByteArray+ , arrStart :: {-# UNPACK #-} !Int -- offset+ , arrEnd :: {-# UNPACK #-} !Int -- offset + len+ }++-------------------------------------------------------------------------------+-- Utility functions+-------------------------------------------------------------------------------++-- XXX Rename this to "unsafeAsPtr"?+-- | Use an @Array a@ as @Ptr a@.+--+-- See 'MA.unsafePinnedAsPtr' in the Mutable array module for more details.+--+-- /Unsafe/+--+-- 1. The accessor must not access the array beyond the specified length.+-- 2. The accessor must not mutate the array.+--+-- /Pre-release/+--+{-# INLINE unsafePinnedAsPtr #-}+unsafePinnedAsPtr :: MonadIO m => Array a -> (Ptr a -> Int -> IO b) -> m b+unsafePinnedAsPtr arr f = do+ let marr = unsafeThaw arr+ pinned <- liftIO $ MA.pin marr+ MA.unsafeAsPtr pinned f++-- | Use an @Array a@ as @ForeignPtr a@.+--+-- /Unsafe/ because of direct pointer operations. The user must ensure that they+-- are writing within the legal bounds of the array.+--+-- /Pre-release/+--+{-# INLINE unsafeAsForeignPtr #-}+unsafeAsForeignPtr+ :: MonadIO m => Array a -> (ForeignPtr a -> Int -> IO b) -> m b+unsafeAsForeignPtr arr0 f = do+ let marr = unsafeThaw arr0+ pinned <- liftIO $ MA.pin marr+ MA.unsafeAsPtr pinned (finner (MA.arrContents pinned))+ where+ finner arrContents_ (Ptr addr#) i =+ let fptrContents =+ PlainPtr (Unboxed.getMutByteArray# arrContents_)+ fptr = ForeignPtr addr# fptrContents+ in f fptr i++{-# INLINE mutableByteArrayContents# #-}+mutableByteArrayContents# :: Exts.MutableByteArray# s -> Addr#+#if __GLASGOW_HASKELL__ >= 902+mutableByteArrayContents# = Exts.mutableByteArrayContents#+#else+mutableByteArrayContents# x = Exts.byteArrayContents# (Exts.unsafeCoerce# x)+#endif++-- | @unsafeFromForeignPtr fptr len@ converts the "ForeignPtr" to an "Array".+--+unsafeFromForeignPtr+ :: MonadIO m => ForeignPtr Word8 -> Int -> m (Array Word8)+unsafeFromForeignPtr (ForeignPtr addr# _) i+ | Ptr addr# == nullPtr || i == 0 = pure empty+unsafeFromForeignPtr (ForeignPtr addr# (PlainPtr marr#)) len =+ let off = I# (addr# `minusAddr#` mutableByteArrayContents# marr#)+ in pure (Array (Unboxed.MutByteArray marr#) off (off + len))+unsafeFromForeignPtr (ForeignPtr addr# _) len =+ fromPtrN len (Ptr addr#)++{-# DEPRECATED asPtrUnsafe "Please use unsafePinnedAsPtr instead." #-}+{-# INLINE asPtrUnsafe #-}+asPtrUnsafe :: MonadIO m => Array a -> (Ptr a -> m b) -> m b+asPtrUnsafe arr f = MA.unsafePinnedAsPtr (unsafeThaw arr) (\p _ -> f p)++-------------------------------------------------------------------------------+-- Freezing and Thawing+-------------------------------------------------------------------------------++-- XXX For debugging we can track slices/references through a weak IORef. Then+-- trigger a GC after freeze/thaw and assert that there are no references+-- remaining.++-- | Makes an immutable array using the underlying memory of the mutable+-- array.+--+-- Please make sure that there are no other references to the mutable array+-- lying around, so that it is never used after freezing it using+-- /unsafeFreeze/. If the underlying array is mutated, the immutable promise+-- is lost.+--+-- /Pre-release/+{-# INLINE unsafeFreeze #-}+unsafeFreeze :: MutArray a -> Array a+unsafeFreeze (MA.MutArray ac as ae _) = Array ac as ae++-- | Similar to 'unsafeFreeze' but uses 'MA.rightSize' on the mutable array+-- first.+{-# INLINE unsafeFreezeWithShrink #-}+unsafeFreezeWithShrink :: Unbox a => MutArray a -> Array a+unsafeFreezeWithShrink arr = unsafePerformIO $ do+ MA.MutArray ac as ae _ <- MA.rightSize arr+ return $ Array ac as ae++-- | Makes a mutable array using the underlying memory of the immutable array.+--+-- Please make sure that there are no other references to the immutable array+-- lying around, so that it is never used after thawing it using /unsafeThaw/.+-- If the resulting array is mutated, any references to the older immutable+-- array are mutated as well.+--+-- /Pre-release/+{-# INLINE unsafeThaw #-}+unsafeThaw :: Array a -> MutArray a+unsafeThaw (Array ac as ae) = MA.MutArray ac as ae ae++-------------------------------------------------------------------------------+-- Pinning & Unpinning+-------------------------------------------------------------------------------++-- | Return a copy of the 'Array' in pinned memory if unpinned, else return the+-- original array.+{-# INLINE pin #-}+pin :: Array a -> IO (Array a)+pin = fmap unsafeFreeze . MA.pin . unsafeThaw++-- | Return a copy of the 'Array' in unpinned memory if pinned, else return the+-- original array.+{-# INLINE unpin #-}+unpin :: Array a -> IO (Array a)+unpin = fmap unsafeFreeze . MA.unpin . unsafeThaw++-- | Return 'True' if the array is allocated in pinned memory.+{-# INLINE isPinned #-}+isPinned :: Array a -> Bool+isPinned = MA.isPinned . unsafeThaw++-------------------------------------------------------------------------------+-- Construction+-------------------------------------------------------------------------------++-- | Copy two immutable arrays into a new array. If you want to splice more+-- than two arrays then this operation would be highly inefficient because it+-- would make a copy on every splice operation, instead use the+-- 'fromChunksK' operation to combine n immutable arrays.+{-# INLINE splice #-}+splice :: (MonadIO m+#ifdef DEVBUILD+ , Unbox a+#endif+ )+ => Array a -> Array a -> m (Array a)+splice arr1 arr2 =+ unsafeFreeze <$> MA.spliceCopy (unsafeThaw arr1) (unsafeThaw arr2)++-- | Create an 'Array' from the first N elements of a list. The array is+-- allocated to size N, if the list terminates before N elements then the+-- array may hold less than N elements.+--+{-# INLINABLE fromListN #-}+fromListN :: Unbox a => Int -> [a] -> Array a+fromListN n xs = unsafePerformIO $ unsafeFreeze <$> MA.fromListN n xs++-- | Like 'fromListN' but creates a pinned array.+{-# INLINABLE fromListN' #-}+pinnedFromListN, fromListN' :: Unbox a => Int -> [a] -> Array a+fromListN' n xs =+ unsafePerformIO $ unsafeFreeze <$> MA.fromListN' n xs+RENAME_PRIME(pinnedFromListN,fromListN)++-- | Create an 'Array' from the first N elements of a list in reverse order.+-- The array is allocated to size N, if the list terminates before N elements+-- then the array may hold less than N elements.+--+-- /Pre-release/+{-# INLINABLE fromListRevN #-}+fromListRevN :: Unbox a => Int -> [a] -> Array a+fromListRevN n xs = unsafePerformIO $ unsafeFreeze <$> MA.fromListRevN n xs++-- | Create an 'Array' from a list. The list must be of finite size.+--+{-# INLINE fromList #-}+fromList :: Unbox a => [a] -> Array a+fromList xs = unsafePerformIO $ unsafeFreeze <$> MA.fromList xs++-- | Like 'fromList' but creates a pinned array.+{-# INLINE fromList' #-}+pinnedFromList, fromList' :: Unbox a => [a] -> Array a+fromList' xs = unsafePerformIO $ unsafeFreeze <$> MA.fromList' xs+RENAME_PRIME(pinnedFromList,fromList)++-- | Create an 'Array' from a list in reverse order. The list must be of finite+-- size.+--+-- /Pre-release/+{-# INLINABLE fromListRev #-}+fromListRev :: Unbox a => [a] -> Array a+fromListRev xs = unsafePerformIO $ unsafeFreeze <$> MA.fromListRev xs++-- | Create an 'Array' 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.+--+-- >>> fromStreamN n = Stream.fold (Array.createOf n)+--+-- /Pre-release/+{-# INLINE_NORMAL fromStreamN #-}+fromStreamN :: (MonadIO m, Unbox a) => Int -> Stream m a -> m (Array a)+fromStreamN n m = do+ when (n < 0) $ error "writeN: negative write count specified"+ unsafeFreeze <$> MA.fromStreamN n m+-- fromStreamN n = D.fold (writeN n)++{-# DEPRECATED fromStreamDN "Please use fromStreamN instead." #-}+fromStreamDN :: forall m a. (MonadIO m, Unbox a)+ => Int -> D.Stream m a -> m (Array a)+fromStreamDN = fromStreamN++-- | Create an 'Array' from a stream. This is useful when we want to create a+-- single array from a stream of unknown size. 'writeN' is at least twice+-- as efficient when the size is already known.+--+-- >>> fromStream = Stream.fold Array.create+--+-- Note that if the input stream is too large memory allocation for the array+-- may fail. When the stream size is not known, `chunksOf` followed by+-- processing of indvidual arrays in the resulting stream should be preferred.+--+-- /Pre-release/+{-# INLINE_NORMAL fromStreamD #-}+fromStream :: (MonadIO m, Unbox a) => Stream m a -> m (Array a)+fromStream = D.fold write+-- fromStreamD str = unsafeFreeze <$> MA.fromStream str++{-# DEPRECATED fromStreamD "Please use fromStream instead." #-}+fromStreamD :: forall m a. (MonadIO m, Unbox a)+ => D.Stream m a -> m (Array a)+fromStreamD = fromStream++-------------------------------------------------------------------------------+-- Slice+-------------------------------------------------------------------------------++-- | /O(1)/ Slice an array in constant time.+--+-- Caution: The bounds of the slice are not checked.+--+-- /Unsafe/+--+-- /Pre-release/+{-# INLINE unsafeSliceOffLen #-}+unsafeSliceOffLen ::+ forall a. Unbox a+ => Int -- ^ starting index+ -> Int -- ^ length of the slice+ -> Array a+ -> Array a+unsafeSliceOffLen index len (Array contents start e) =+ let size = SIZE_OF(a)+ start1 = start + (index * size)+ end1 = start1 + (len * size)+ in assert (end1 <= e) (Array contents start1 end1)++-------------------------------------------------------------------------------+-- Elimination+-------------------------------------------------------------------------------++-- |+--+-- >>> null arr = Array.byteLength arr == 0+--+-- Note that this may be faster than checking Array.length as length+-- calculation involves a division operation.+--+-- /Pre-release/+{-# INLINE null #-}+null :: Array a -> Bool+null arr = byteLength arr == 0++-- XXX Change this to a partial function instead of a Maybe type? And use+-- MA.getIndex instead.++-- | /O(1)/ Lookup the element at the given index. Index starts from 0.+--+{-# INLINE getIndex #-}+getIndex :: forall a. Unbox a => Int -> Array a -> Maybe a+getIndex i arr =+ unsafeInlineIO $ do+ let elemPtr = INDEX_OF(arrStart arr, i, a)+ if i >= 0 && INDEX_VALID(elemPtr, arrEnd arr, a)+ then Just <$> peekAt elemPtr (arrContents arr)+ else return Nothing++-- | Like 'getIndex' but indexes the array in reverse from the end.+--+-- /Pre-release/+{-# INLINE getIndexRev #-}+getIndexRev :: forall a. Unbox a => Int -> Array a -> Maybe a+getIndexRev i arr =+ unsafeInlineIO $ do+ let elemPtr = RINDEX_OF(arrEnd arr, i, a)+ if i >= 0 && elemPtr >= arrStart arr+ then Just <$> peekAt elemPtr (arrContents arr)+ else return Nothing++{-# INLINE head #-}+head :: Unbox a => Array a -> Maybe a+head = getIndex 0++{-# INLINE last #-}+last :: Unbox a => Array a -> Maybe a+last = getIndexRev 0++{-# INLINE unsafeTail #-}+unsafeTail :: forall a. Unbox a => Array a -> Array a+unsafeTail Array{..} = Array arrContents (arrStart + SIZE_OF(a)) arrEnd++{-# INLINE tail #-}+tail :: Unbox a => Array a -> Array a+tail arr@Array{..} =+ if arrEnd > arrStart+ then unsafeTail arr+ else arr++{-# INLINE uncons #-}+uncons :: Unbox a => Array a -> Maybe (a, Array a)+uncons arr =+ if null arr+ then Nothing+ else Just (unsafeGetIndex 0 arr, unsafeTail arr)++{-# INLINE unsafeInit #-}+unsafeInit :: forall a. Unbox a => Array a -> Array a+unsafeInit Array{..} = Array arrContents arrStart (arrEnd - SIZE_OF(a))++{-# INLINE init #-}+init :: Unbox a => Array a -> Array a+init arr@Array{..} =+ if arrEnd > arrStart+ then unsafeInit arr+ else arr++{-# INLINE unsnoc #-}+unsnoc :: Unbox a => Array a -> Maybe (Array a, a)+unsnoc arr =+ if null arr+ then Nothing+ else Just (unsafeTail arr, unsafeGetIndexRev 0 arr)++-------------------------------------------------------------------------------+-- Streams of arrays+-------------------------------------------------------------------------------++{-# INLINE buildChunks #-}+buildChunks :: (MonadIO m, Unbox a) =>+ D.Stream m a -> m (K.StreamK m (Array a))+buildChunks m = D.foldr K.cons K.nil $ chunksOf defaultChunkSize m++{-# DEPRECATED bufferChunks "Please use buildChunks instead." #-}+bufferChunks :: (MonadIO m, Unbox a) =>+ D.Stream m a -> m (K.StreamK m (Array a))+bufferChunks = buildChunks++-- | @chunksOf n stream@ groups the elements in the input stream into arrays of+-- @n@ elements each.+--+-- Same as the following but may be more efficient:+--+-- >>> chunksOf n = Stream.foldMany (Array.createOf n)+--+-- /Pre-release/+{-# INLINE_NORMAL chunksOf #-}+chunksOf :: forall m a. (MonadIO m, Unbox a)+ => Int -> D.Stream m a -> D.Stream m (Array a)+chunksOf n str = D.map unsafeFreeze $ MA.chunksOf n str++-- | Like 'chunksOf' but creates pinned arrays.+{-# INLINE_NORMAL chunksOf' #-}+pinnedChunksOf, chunksOf' :: forall m a. (MonadIO m, Unbox a)+ => Int -> D.Stream m a -> D.Stream m (Array a)+chunksOf' n str = D.map unsafeFreeze $ MA.chunksOf' n str+RENAME_PRIME(pinnedChunksOf,chunksOf)++-- | Create arrays from the input stream using a predicate to find the end of+-- the chunk. When the predicate matches, the chunk ends, the matching element+-- is included in the chunk.+--+-- Definition:+--+-- >>> chunksEndBy p = Stream.foldMany (Fold.takeEndBy p Array.create)+--+{-# INLINE chunksEndBy #-}+chunksEndBy :: forall m a. (MonadIO m, Unbox a)+ => (a -> Bool) -> D.Stream m a -> D.Stream m (Array a)+chunksEndBy p = D.foldMany (Fold.takeEndBy p create)++-- | Like 'chunksEndBy' but creates pinned arrays.+--+{-# INLINE chunksEndBy' #-}+chunksEndBy' :: forall m a. (MonadIO m, Unbox a)+ => (a -> Bool) -> D.Stream m a -> D.Stream m (Array a)+chunksEndBy' p = D.foldMany (Fold.takeEndBy p create')++-- | Create chunks using newline as the separator, including it.+{-# INLINE chunksEndByLn #-}+chunksEndByLn :: (MonadIO m)+ => D.Stream m Word8 -> D.Stream m (Array Word8)+chunksEndByLn = chunksEndBy (== fromIntegral (ord '\n'))++-- | Like 'chunksEndByLn' but creates pinned arrays.+{-# INLINE chunksEndByLn' #-}+chunksEndByLn' :: (MonadIO m)+ => D.Stream m Word8 -> D.Stream m (Array Word8)+chunksEndByLn' = chunksEndBy' (== fromIntegral (ord '\n'))++-- XXX Remove MonadIO++{-# INLINE splitEndBy #-}+splitEndBy :: (MonadIO m, Unbox a) =>+ (a -> Bool) -> Array a -> Stream m (Array a)+splitEndBy p arr = D.map unsafeFreeze $ MA.splitEndBy p (unsafeThaw arr)++-- | Split the array into a stream of slices using a predicate. The element+-- matching the predicate is dropped.+--+-- /Pre-release/+{-# INLINE splitEndBy_ #-}+splitEndBy_ :: (Monad m, Unbox a) =>+ (a -> Bool) -> Array a -> Stream m (Array a)+splitEndBy_ predicate arr =+ fmap (\(i, len) -> unsafeSliceOffLen i len arr)+ $ D.indexEndBy_ predicate (read arr)++-- | Convert a stream of arrays into a stream of their elements.+--+-- >>> concat = Stream.unfoldEach Array.reader+--+{-# INLINE_NORMAL concat #-}+concat :: (Monad m, Unbox a) => Stream m (Array a) -> Stream m a+concat = MA.concatWith (pure . unsafeInlineIO) . D.map unsafeThaw+-- concat = D.unfoldMany reader++{-# DEPRECATED flattenArrays "Please use \"unfoldMany reader\" instead." #-}+{-# INLINE flattenArrays #-}+flattenArrays :: forall m a. (MonadIO m, Unbox a)+ => D.Stream m (Array a) -> D.Stream m a+flattenArrays = concat++-- | Convert a stream of arrays into a stream of their elements reversing the+-- contents of each array before flattening.+--+-- >>> concatRev = Stream.unfoldEach Array.readerRev+--+{-# INLINE_NORMAL concatRev #-}+concatRev :: forall m a. (Monad m, Unbox a)+ => D.Stream m (Array a) -> D.Stream m a+-- XXX this requires MonadIO whereas the unfoldMany version does not+concatRev = MA.concatRevWith (pure . unsafeInlineIO) . D.map unsafeThaw+-- concatRev = D.unfoldMany readerRev++{-# DEPRECATED flattenArraysRev "Please use \"unfoldMany readerRev\" instead." #-}+{-# INLINE flattenArraysRev #-}+flattenArraysRev :: forall m a. (MonadIO m, Unbox a)+ => D.Stream m (Array a) -> D.Stream m a+flattenArraysRev = concatRev++-------------------------------------------------------------------------------+-- Compact+-------------------------------------------------------------------------------++-- XXX Note that this thaws immutable arrays for appending, that may be+-- problematic if multiple users do the same thing, however, thawed immutable+-- arrays would have no capacity to append, therefore, a copy will be forced+-- anyway.++-- | Fold @createCompactMin n@ coalesces adjacent arrays in the input+-- stream until the size becomes greater than or equal to n.+--+-- Generates unpinned arrays irrespective of the pinning status of input+-- arrays.+{-# INLINE_NORMAL createCompactMin #-}+createCompactMin, fCompactGE :: (MonadIO m, Unbox a) =>+ Int -> Fold m (Array a) (Array a)+createCompactMin n =+ fmap unsafeFreeze $ Fold.lmap unsafeThaw $ MA.createCompactMin n++RENAME(fCompactGE,createCompactMin)++-- | Pinned version of 'createCompactMin'.+{-# INLINE_NORMAL createCompactMin' #-}+createCompactMin', fPinnedCompactGE :: (MonadIO m, Unbox a) =>+ Int -> Fold m (Array a) (Array a)+createCompactMin' n =+ fmap unsafeFreeze $ Fold.lmap unsafeThaw $ MA.createCompactMin' n++{-# DEPRECATED fPinnedCompactGE "Please use createCompactMin' instead." #-}+{-# INLINE fPinnedCompactGE #-}+fPinnedCompactGE = createCompactMin++-- | @compactBySize n stream@ coalesces adjacent arrays in the @stream@ until+-- the size becomes greater than or equal to @n@.+--+-- >>> compactBySize n = Stream.foldMany (Array.createCompactMin n)+--+-- Generates unpinned arrays irrespective of the pinning status of input+-- arrays.+{-# INLINE compactMin #-}+compactMin, compactGE ::+ (MonadIO m, Unbox a)+ => Int -> Stream m (Array a) -> Stream m (Array a)+compactMin n stream =+ D.map unsafeFreeze $ MA.compactMin n $ D.map unsafeThaw stream++RENAME(compactGE,compactMin)++-- | Like 'compactBySizeGE' but for transforming folds instead of stream.+--+-- >>> lCompactBySizeGE n = Fold.many (Array.createCompactMin n)+--+-- Generates unpinned arrays irrespective of the pinning status of input+-- arrays.+{-# DEPRECATED lCompactGE "Please use scanCompactMin instead." #-}+{-# INLINE_NORMAL lCompactGE #-}+lCompactGE :: (MonadIO m, Unbox a)+ => Int -> Fold m (Array a) () -> Fold m (Array a) ()+lCompactGE n fld =+ Fold.lmap unsafeThaw $ MA.lCompactGE n (Fold.lmap unsafeFreeze fld)++-- | Pinned version of 'lCompactGE'.+{-# DEPRECATED lPinnedCompactGE "Please use scanCompactMin' instead." #-}+{-# INLINE_NORMAL lPinnedCompactGE #-}+lPinnedCompactGE :: (MonadIO m, Unbox a)+ => Int -> Fold m (Array a) () -> Fold m (Array a) ()+lPinnedCompactGE n fld =+ Fold.lmap unsafeThaw $ MA.lPinnedCompactGE n (Fold.lmap unsafeFreeze fld)++{-# INLINE scanCompactMin #-}+scanCompactMin :: forall m a. (MonadIO m, Unbox a)+ => Int -> Scanl m (Array a) (Maybe (Array a))+scanCompactMin n =+ Scanl.lmap unsafeThaw+ $ fmap (fmap unsafeFreeze)+ $ MA.scanCompactMin n++{-# INLINE scanCompactMin' #-}+scanCompactMin' :: forall m a. (MonadIO m, Unbox a)+ => Int -> Scanl m (Array a) (Maybe (Array a))+scanCompactMin' n =+ Scanl.lmap unsafeThaw+ $ fmap (fmap unsafeFreeze)+ $ MA.scanCompactMin' n++-------------------------------------------------------------------------------+-- Splitting+-------------------------------------------------------------------------------++-- Drops the separator byte+{-# INLINE breakEndByWord8_ #-}+breakEndByWord8_, breakOn :: MonadIO m+ => Word8 -> Array Word8 -> m (Array Word8, Maybe (Array Word8))+breakEndByWord8_ sep arr = do+ (a, b) <- MA.breakOn sep (unsafeThaw arr)+ return (unsafeFreeze a, unsafeFreeze <$> b)+RENAME(breakOn,breakEndByWord8_)++-------------------------------------------------------------------------------+-- Elimination+-------------------------------------------------------------------------------++-- | Return element at the specified index without checking the bounds.+--+-- Unsafe because it does not check the bounds of the array.+{-# INLINE_NORMAL unsafeGetIndexIO #-}+unsafeGetIndexIO, unsafeIndexIO :: forall a. Unbox a => Int -> Array a -> IO a+unsafeGetIndexIO i arr = MA.unsafeGetIndex i (unsafeThaw arr)++-- | Return element at the specified index without checking the bounds.+{-# INLINE_NORMAL unsafeGetIndex #-}+unsafeGetIndex, getIndexUnsafe :: forall a. Unbox a => Int -> Array a -> a+unsafeGetIndex i arr = let !r = unsafeInlineIO $ unsafeGetIndexIO i arr in r++{-# DEPRECATED unsafeIndex "Please use 'unsafeGetIndex' instead" #-}+{-# INLINE_NORMAL unsafeIndex #-}+unsafeIndex :: forall a. Unbox a => Int -> Array a -> a+unsafeIndex = unsafeGetIndex++{-# INLINE_NORMAL unsafeGetIndexRevIO #-}+unsafeGetIndexRevIO :: forall a. Unbox a => Int -> Array a -> IO a+unsafeGetIndexRevIO i arr = MA.unsafeGetIndexRev i (unsafeThaw arr)++{-# INLINE_NORMAL unsafeGetIndexRev #-}+unsafeGetIndexRev :: forall a. Unbox a => Int -> Array a -> a+unsafeGetIndexRev i arr =+ let !r = unsafeInlineIO $ unsafeGetIndexRevIO i arr in r++-- | /O(1)/ Get the byte length of the array.+--+{-# INLINE byteLength #-}+byteLength :: Array a -> Int+byteLength = MA.byteLength . unsafeThaw++-- | /O(1)/ Get the length of the array i.e. the number of elements in the+-- array.+--+{-# INLINE length #-}+length :: Unbox a => Array a -> Int+length arr = MA.length (unsafeThaw arr)++{-# INLINE_NORMAL producer #-}+producer :: forall m a. (Monad m, Unbox a) => Producer m (Array a) a+producer =+ Producer.translate unsafeThaw unsafeFreeze+ $ MA.producerWith (return . unsafeInlineIO)++-- | Unfold an array into a stream.+--+{-# INLINE_NORMAL reader #-}+reader :: forall m a. (Monad m, Unbox a) => Unfold m (Array a) a+reader = Producer.simplify producer++-- | 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.+--+-- /Pre-release/+--+{-# INLINE_NORMAL unsafeReader #-}+unsafeReader, readerUnsafe :: forall m a. (Monad m, Unbox a) => Unfold m (Array a) a+unsafeReader = Unfold step inject+ where++ inject (Array contents start end) =+ return (MA.ArrayUnsafe contents end start)++ {-# INLINE_LATE step #-}+ step (MA.ArrayUnsafe contents end p) = 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 = unsafeInlineIO $ peekAt p contents+ let !p1 = INDEX_NEXT(p,a)+ return $ D.Yield x (MA.ArrayUnsafe contents end p1)++-- | Unfold an array into a stream in reverse order.+--+{-# INLINE_NORMAL readerRev #-}+readerRev :: forall m a. (Monad m, Unbox a) => Unfold m (Array a) a+readerRev = Unfold.lmap unsafeThaw $ MA.readerRevWith (return . unsafeInlineIO)++{-# DEPRECATED toStreamD "Please use 'read' instead." #-}+{-# INLINE_NORMAL toStreamD #-}+toStreamD :: forall m a. (Monad m, Unbox a) => Array a -> D.Stream m a+toStreamD = read++{-# INLINE toStreamK #-}+toStreamK :: forall m a. (Monad m, Unbox a) => Array a -> K.StreamK m a+toStreamK arr = MA.toStreamKWith (return . unsafeInlineIO) (unsafeThaw arr)++{-# DEPRECATED toStreamDRev "Please use 'readRev' instead." #-}+{-# INLINE_NORMAL toStreamDRev #-}+toStreamDRev :: forall m a. (Monad m, Unbox a) => Array a -> D.Stream m a+toStreamDRev = readRev++{-# INLINE toStreamKRev #-}+toStreamKRev :: forall m a. (Monad m, Unbox a) => Array a -> K.StreamK m a+toStreamKRev arr =+ MA.toStreamKRevWith (return . unsafeInlineIO) (unsafeThaw arr)++-- | Convert an 'Array' into a stream.+--+-- /Pre-release/+{-# INLINE_EARLY read #-}+read :: (Monad m, Unbox a) => Array a -> Stream m a+read arr = MA.toStreamWith (return . unsafeInlineIO) (unsafeThaw arr)++-- | Same as 'read'+--+{-# DEPRECATED toStream "Please use 'read' instead." #-}+{-# INLINE_EARLY toStream #-}+toStream :: (Monad m, Unbox a) => Array a -> Stream m a+toStream = read+-- XXX add fallback to StreamK rule+-- {-# RULES "Streamly.Array.read fallback to StreamK" [1]+-- forall a. S.readK (read a) = K.fromArray a #-}++-- | Convert an 'Array' into a stream in reverse order.+--+-- /Pre-release/+{-# INLINE_EARLY readRev #-}+readRev :: (Monad m, Unbox a) => Array a -> Stream m a+readRev arr = MA.toStreamRevWith (return . unsafeInlineIO) (unsafeThaw arr)++-- | Same as 'readRev'+--+{-# DEPRECATED toStreamRev "Please use 'readRev' instead." #-}+{-# INLINE_EARLY toStreamRev #-}+toStreamRev :: (Monad m, Unbox a) => Array a -> Stream m a+toStreamRev = readRev++-- XXX add fallback to StreamK rule+-- {-# RULES "Streamly.Array.readRev fallback to StreamK" [1]+-- forall a. S.toStreamK (readRev a) = K.revFromArray a #-}++{-# INLINE_NORMAL foldl' #-}+foldl' :: forall a b. Unbox a => (b -> a -> b) -> b -> Array a -> b+foldl' f z arr = runIdentity $ D.foldl' f z $ toStreamD arr++{-# INLINE_NORMAL foldr #-}+foldr :: Unbox a => (a -> b -> b) -> b -> Array a -> b+foldr f z arr = runIdentity $ D.foldr f z $ toStreamD arr++-- | Like 'breakAt' but does not check whether the index is valid.+--+{-# INLINE unsafeBreakAt #-}+unsafeBreakAt :: Unbox a =>+ Int -> Array a -> (Array a, Array a)+unsafeBreakAt i arr = (unsafeFreeze a, unsafeFreeze b)++ where++ (a, b) = MA.unsafeBreakAt i (unsafeThaw arr)++-- | Create two slices of an array without copying the original array. The+-- specified index @i@ is the first index of the second slice.+--+{-# INLINE breakAt #-}+breakAt, splitAt :: Unbox a => Int -> Array a -> (Array a, Array a)+breakAt i arr = (unsafeFreeze a, unsafeFreeze b)++ where++ (a, b) = MA.breakAt i (unsafeThaw arr)+RENAME(splitAt,breakAt)++{-# INLINE breakEndBy #-}+breakEndBy :: Unbox a => (a -> Bool) -> Array a -> (Array a, Array a)+breakEndBy p arr = (unsafeFreeze a, unsafeFreeze b)++ where++ (a, b) = unsafePerformIO $ MA.breakEndBy p (unsafeThaw arr)++{-# INLINE breakEndBy_ #-}+breakEndBy_ :: Unbox a => (a -> Bool) -> Array a -> (Array a, Array a)+breakEndBy_ p arr = (unsafeFreeze a, unsafeFreeze b)++ where++ (a, b) = unsafePerformIO $ MA.breakEndBy_ p (unsafeThaw arr)++{-# INLINE revBreakEndBy #-}+revBreakEndBy :: Unbox a => (a -> Bool) -> Array a -> (Array a, Array a)+revBreakEndBy p arr = (unsafeFreeze a, unsafeFreeze b)++ where++ (a, b) = unsafePerformIO $ MA.revBreakEndBy p (unsafeThaw arr)++{-# INLINE revBreakEndBy_ #-}+revBreakEndBy_ :: Unbox a => (a -> Bool) -> Array a -> (Array a, Array a)+revBreakEndBy_ p arr = (unsafeFreeze a, unsafeFreeze b)++ where++ (a, b) = unsafePerformIO $ MA.revBreakEndBy_ p (unsafeThaw arr)++-- XXX Remove unsafePerformIO++-- | Strip elements which match the predicate, from both ends.+--+-- /Pre-release/+{-# INLINE dropAround #-}+dropAround :: Unbox a => (a -> Bool) -> Array a -> Array a+dropAround eq arr =+ unsafeFreeze $ unsafePerformIO $ MA.dropAround eq (unsafeThaw arr)++-- | Strip elements which match the predicate, from the start of the array.+--+-- /Pre-release/+{-# INLINE dropWhile #-}+dropWhile :: Unbox a => (a -> Bool) -> Array a -> Array a+dropWhile eq arr =+ unsafeFreeze $ unsafePerformIO $ MA.dropWhile eq (unsafeThaw arr)++-- | Strip elements which match the predicate, from the end of the array.+--+-- /Pre-release/+{-# INLINE revDropWhile #-}+revDropWhile :: Unbox a => (a -> Bool) -> Array a -> Array a+revDropWhile eq arr =+ unsafeFreeze $ unsafePerformIO $ MA.revDropWhile eq (unsafeThaw arr)++-- Use foldr/build fusion to fuse with list consumers+-- This can be useful when using the IsList instance+{-# INLINE_LATE toListFB #-}+toListFB :: forall a b. Unbox a => (a -> b -> b) -> b -> Array a -> b+toListFB c n Array{..} = go arrStart+ where++ go p | assert (p <= arrEnd) (p == arrEnd) = n+ go p =+ -- 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 peekAt at them.+ let !x = unsafeInlineIO $ peekAt p arrContents+ in c x (go (INDEX_NEXT(p,a)))++-- | Convert an 'Array' into a list.+--+{-# INLINE toList #-}+toList :: Unbox a => Array a -> [a]+toList s = build (\c n -> toListFB c n s)++-------------------------------------------------------------------------------+-- Folds+-------------------------------------------------------------------------------++-- | @createOf n@ folds a maximum of @n@ elements from the input stream to an+-- 'Array'.+--+{-# INLINE_NORMAL createOf #-}+createOf :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+createOf = fmap unsafeFreeze . MA.createOf++{-# DEPRECATED writeN "Please use createOf instead." #-}+{-# INLINE writeN #-}+writeN :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+writeN = createOf++-- | Like 'createOf' but creates a pinned array.+{-# INLINE_NORMAL createOf' #-}+pinnedCreateOf, createOf' :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+createOf' = fmap unsafeFreeze . MA.createOf'+RENAME_PRIME(pinnedCreateOf,createOf)++{-# DEPRECATED pinnedWriteN "Please use createOf' instead." #-}+{-# INLINE pinnedWriteN #-}+pinnedWriteN :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (Array a)+pinnedWriteN = createOf'++-- | @pinnedWriteNAligned alignment n@ folds a maximum of @n@ elements from the input+-- stream to an 'Array' aligned to the given size.+--+-- /Pre-release/+--+{-# INLINE_NORMAL pinnedWriteNAligned #-}+{-# DEPRECATED pinnedWriteNAligned "To be removed." #-}+pinnedWriteNAligned :: forall m a. (MonadIO m, Unbox a)+ => Int -> Int -> Fold m a (Array a)+pinnedWriteNAligned alignSize = fmap unsafeFreeze . MA.pinnedWriteNAligned alignSize++-- | Like 'createOf' but does not check the array bounds when writing. The fold+-- driver must not call the step function more than 'n' times otherwise it will+-- corrupt the memory and crash. This function exists mainly because any+-- conditional in the step function blocks fusion causing 10x performance+-- slowdown.+--+{-# INLINE_NORMAL unsafeCreateOf #-}+unsafeCreateOf :: forall m a. (MonadIO m, Unbox a)+ => Int -> Fold m a (Array a)+unsafeCreateOf n = unsafeFreeze <$> MA.unsafeCreateOf n++{-# DEPRECATED writeNUnsafe "Please use unsafeCreateOf instead." #-}+{-# INLINE writeNUnsafe #-}+writeNUnsafe :: forall m a. (MonadIO m, Unbox a)+ => Int -> Fold m a (Array a)+writeNUnsafe = unsafeCreateOf++{-# INLINE_NORMAL unsafeCreateOf' #-}+unsafePinnedCreateOf, unsafeCreateOf' :: forall m a. (MonadIO m, Unbox a)+ => Int -> Fold m a (Array a)+unsafeCreateOf' n = unsafeFreeze <$> MA.unsafeCreateOf' n+RENAME_PRIME(unsafePinnedCreateOf,unsafeCreateOf)++{-# DEPRECATED pinnedWriteNUnsafe "Please use unsafeCreateOf' instead." #-}+{-# INLINE pinnedWriteNUnsafe #-}+pinnedWriteNUnsafe :: forall m a. (MonadIO m, Unbox a)+ => Int -> Fold m a (Array a)+pinnedWriteNUnsafe = unsafeCreateOf'++-- | A version of "create" that let's you pass in the initial capacity of the+-- array in terms of the number of elements.+--+-- Semantically @createWith 10@ and @createWith 100@ will behave in the same+-- way. @createWith 100@ will be more performant though.+--+-- > create = createWith elementCount+--+-- /Pre-release/+{-# INLINE_NORMAL createWith #-}+createWith :: forall m a. (MonadIO m, Unbox a)+ => Int -> Fold m a (Array a)+-- createWith n = FL.rmapM spliceArrays $ toArraysOf n+createWith elemCount = unsafeFreeze <$> MA.createWith elemCount++{-# DEPRECATED writeWith "Please use createWith instead." #-}+{-# INLINE writeWith #-}+writeWith :: forall m a. (MonadIO m, Unbox a)+ => Int -> Fold m a (Array a)+writeWith = createWith++-- | Fold the whole input to a single array.+--+-- /Caution! Do not use this on infinite streams./+--+{-# INLINE create #-}+create :: forall m a. (MonadIO m, Unbox a) => Fold m a (Array a)+create = fmap unsafeFreeze MA.create++{-# DEPRECATED write "Please use create instead." #-}+{-# INLINE write #-}+write :: forall m a. (MonadIO m, Unbox a) => Fold m a (Array a)+write = create++-- | Like 'create' but creates a pinned array.+{-# INLINE create' #-}+pinnedCreate, create' :: forall m a. (MonadIO m, Unbox a) => Fold m a (Array a)+create' = fmap unsafeFreeze MA.create'+RENAME_PRIME(pinnedCreate,create)++{-# DEPRECATED pinnedWrite "Please use create' instead." #-}+{-# INLINE pinnedWrite #-}+pinnedWrite :: forall m a. (MonadIO m, Unbox a) => Fold m a (Array a)+pinnedWrite = create'++-- | Fold "step" has a dependency on "initial", and each step is dependent on+-- the previous invocation of step due to state passing, finally extract+-- depends on the result of step, therefore, as long as the fold is driven in+-- the correct order the operations would be correctly ordered. We need to+-- ensure that we strictly evaluate the previous step completely before the+-- next step.+--+-- To not share the same array we need to make sure that the result of+-- "initial" is not shared. Existential type ensures that it does not get+-- shared across different folds. However, if we invoke "initial" multiple+-- times for the same fold, there is a possiblity of sharing the two because+-- the compiler would consider it as a pure value. One such example is the+-- chunksOf combinator, or using an array creation fold with foldMany+-- combinator. Is there a proper way in GHC to tell it to not share a pure+-- expression in a particular case?+--+-- For this reason array creation folds have a MonadIO constraint. Pure folds+-- could be unsafe and dangerous. This is dangerous especially when used with+-- foldMany like operations.+--+-- >>> unsafePureWrite = Array.unsafeMakePure Array.create+--+{-# INLINE unsafeMakePure #-}+unsafeMakePure :: Monad m => Fold IO a b -> Fold m a b+unsafeMakePure (Fold step initial extract final) =+ Fold (\x a -> return $! unsafeInlineIO (step x a))+ (return $! unsafePerformIO initial)+ (\s -> return $! unsafeInlineIO $ extract s)+ (\s -> return $! unsafeInlineIO $ final s)++{-# INLINE fromPureStreamN #-}+fromPureStreamN :: Unbox a => Int -> Stream Identity a -> Array a+fromPureStreamN n x =+ unsafePerformIO $ fmap unsafeFreeze (MA.fromPureStreamN n x)++-- | Convert a pure stream in Identity monad to an immutable array.+--+-- Same as the following but with better performance:+--+-- >>> fromPureStream = Array.fromList . runIdentity . Stream.toList+--+fromPureStream :: Unbox a => Stream Identity a -> Array a+fromPureStream x = unsafePerformIO $ fmap unsafeFreeze (MA.fromPureStream x)+-- fromPureStream = runIdentity . D.fold (unsafeMakePure write)+-- fromPureStream = fromList . runIdentity . D.toList++-- | @fromPtrN len addr@ copies @len@ bytes from @addr@ into an array. The+-- memory pointed by @addr@ must be pinned or static.+--+-- /Unsafe:/ The caller is responsible to ensure that the pointer passed is+-- valid up to the given length.+--+fromPtrN :: MonadIO m => Int -> Ptr Word8 -> m (Array Word8)+fromPtrN n addr = fmap unsafeFreeze (MA.fromPtrN n addr)++-- | Copy a null terminated immutable 'Addr#' Word8 sequence into an array.+--+-- /Unsafe:/ The caller is responsible for safe addressing.+--+-- Note that this is completely safe when reading from Haskell string+-- literals because they are guaranteed to be NULL terminated:+--+-- Note, you can use lazy unsafePerformIO _only if_ the pointer is immutable.+--+-- >>> Array.toList $ unsafePerformIO $ Array.fromCString# "\1\2\3\0"#+-- [1,2,3]+--+fromCString# :: MonadIO m => Addr# -> m (Array Word8)+fromCString# addr = fmap unsafeFreeze (MA.fromCString# addr)++{-# DEPRECATED fromByteStr# "Please use fromCString# instead." #-}+fromByteStr# :: Addr# -> Array Word8+fromByteStr# addr = unsafePerformIO $ fromCString# addr++-- | Copy a C string consisting of 16-bit wide chars and terminated by a 16-bit+-- null char, into a Word16 array. The null character is not copied.+--+-- Useful for copying UTF16 strings on Windows.+--+fromW16CString# :: MonadIO m => Addr# -> m (Array Word16)+fromW16CString# addr = fmap unsafeFreeze (MA.fromW16CString# addr)++fromCString :: MonadIO m => Ptr Word8 -> m (Array Word8)+fromCString (Ptr addr#) = fromCString# addr#++{-# DEPRECATED fromByteStr "Please use fromCString instead." #-}+fromByteStr :: Ptr Word8 -> Array Word8+fromByteStr = unsafePerformIO . fromCString++-- | Copy a C string consisting of 16-bit wide chars and terminated by a 16-bit+-- null char, into a Word16 array. The null character is not copied.+--+-- Useful for copying UTF16 strings on Windows.+--+fromW16CString :: MonadIO m => Ptr Word8 -> m (Array Word16)+fromW16CString (Ptr addr#) = fromW16CString# addr#++-- XXX implement fromChunks/fromChunkList instead?++-- | Convert an array stream to an array. Note that this requires peak memory+-- that is double the size of the array stream.+--+{-# INLINE fromChunksK #-}+fromChunksK :: (MonadIO m, Unbox a) => StreamK m (Array a) -> m (Array a)+fromChunksK stream =+ -- We buffer the entire stream and then allocate the target array of the+ -- same size, thus requiring double the memory.+ fmap unsafeFreeze $ MA.fromChunksK $ fmap unsafeThaw stream++{-# DEPRECATED fromArrayStreamK "Please use fromChunksK instead." #-}+fromArrayStreamK :: (Unbox a, MonadIO m) => StreamK m (Array a) -> m (Array a)+fromArrayStreamK = fromChunksK++-- | Given a stream of arrays, splice them all together to generate a single+-- array. The stream must be /finite/.+--+{-# INLINE fromChunks #-}+fromChunks :: (MonadIO m, Unbox a) => Stream m (Array a) -> m (Array a)+fromChunks s =+ -- XXX Check which implementation is better+ -- This may also require double the memory as we double the space every+ -- time, when copying the last array we may have reallocated almost double+ -- the space required before we right size it.+ fmap unsafeFreeze $ MA.fromChunksRealloced (fmap unsafeThaw s)+ -- fromChunkStreamK $ D.toStreamK s++-------------------------------------------------------------------------------+-- Instances+-------------------------------------------------------------------------------++instance (Show a, Unbox a) => Show (Array a) where+ {-# INLINE show #-}+ show arr = "fromList " ++ show (toList arr)++instance (Unbox a, Read a, Show a) => Read (Array a) where+ {-# INLINE readPrec #-}+ readPrec = do+ fromListWord <- replicateM 9 ReadPrec.get+ if fromListWord == "fromList "+ then fromList <$> readPrec+ else ReadPrec.pfail++instance (a ~ Char) => IsString (Array a) where+ {-# INLINE fromString #-}+ fromString = fromList++-- GHC versions 8.0 and below cannot derive IsList+instance Unbox a => IsList (Array a) where+ type (Item (Array a)) = a+ {-# INLINE fromList #-}+ fromList = fromList+ {-# INLINE fromListN #-}+ fromListN = fromListN+ {-# INLINE toList #-}+ toList = toList++-- | Compare an array with a list.+{-# INLINE listCmp #-}+listCmp :: (Unbox a, Ord a) => [a] -> Array a -> Ordering+listCmp xs arr = runIdentity $ D.cmpBy compare (D.fromList xs) (toStream arr)++-- | Check equality of an array with a list.+{-# INLINE listEq #-}+listEq :: (Unbox a, Ord a) => [a] -> Array a -> Bool+listEq xs arr = runIdentity $ D.eqBy (==) (D.fromList xs) (toStream arr)++-- | Byte compare two arrays. Compare the length of the arrays. If the length+-- is equal, compare the lexicographical ordering of two underlying byte arrays+-- otherwise return the result of length comparison.+--+-- /Unsafe/: Note that the 'Unbox' instance of sum types with constructors of+-- different sizes may leave some memory uninitialized which can make byte+-- comparison unreliable.+--+-- /Pre-release/+{-# INLINE byteCmp #-}+byteCmp :: Array a -> Array a -> Ordering+byteCmp arr1 arr2 =+ -- unsafePerformIO?+ unsafeInlineIO $! unsafeThaw arr1 `MA.byteCmp` unsafeThaw arr2++-- | Byte equality of two arrays.+--+-- >>> byteEq arr1 arr2 = (==) EQ $ Array.byteCmp arr1 arr2+--+-- /Unsafe/: See 'byteCmp'.+{-# INLINE byteEq #-}+byteEq :: Array a -> Array a -> Bool+byteEq arr1 arr2 = (==) EQ $ byteCmp arr1 arr2++#define MK_EQ_INSTANCE(typ) \+instance {-# OVERLAPPING #-} Eq (Array typ) where { \+; {-# INLINE (==) #-} \+; (==) = byteEq \+}++MK_EQ_INSTANCE(Char)+MK_EQ_INSTANCE(Word8)+MK_EQ_INSTANCE(Word16)+MK_EQ_INSTANCE(Word32)++-- XXX The Word64 default instance should be as fast because we are comparing+-- 64-bit at a time.+MK_EQ_INSTANCE(Word64)+MK_EQ_INSTANCE(Int)+MK_EQ_INSTANCE(Int8)+MK_EQ_INSTANCE(Int16)+MK_EQ_INSTANCE(Int32)++-- XXX The Int64 default instance should be as fast.+MK_EQ_INSTANCE(Int64)++-- | If the type allows a byte-by-byte comparison this instance can be+-- overlapped by a more specific instance that uses 'byteCmp'. Byte comparison+-- can be significantly faster.+--+instance {-# OVERLAPPABLE #-} (Unbox a, Eq a) => Eq (Array a) where+ {-# INLINE (==) #-}+ arr1 == arr2 =+ -- Does unboxed byte equality mean element equality?+ -- XXX This is incorrect for sum types, as we may have some+ -- uninitialized memory in that case. If we always initialize the+ -- unused memory to zero we can use this.+ -- Byte comparison is 40% faster compared to stream equality.+ -- (==) EQ $ unsafeInlineIO $! unsafeThaw arr1 `MA.cmp` unsafeThaw arr2+ (toStreamD arr1 :: Stream Identity a) == toStreamD arr2++instance (Unbox a, Ord a) => Ord (Array a) where+ {-# INLINE compare #-}+ compare arr1 arr2 = runIdentity $+ D.cmpBy compare (toStreamD arr1) (toStreamD arr2)++ -- Default definitions defined in base do not have an INLINE on them, so we+ -- replicate them here with an INLINE.+ {-# 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 }++ -- These two default methods use '<=' rather than 'compare'+ -- because the latter is often more expensive+ {-# INLINE max #-}+ max x y = if x <= y then y else x++ {-# INLINE min #-}+ min x y = if x <= y then x else y++#ifdef DEVBUILD+-- Definitions using the Unboxed constraint from the Array type. These are to+-- make the Foldable instance possible though it is much slower (7x slower).+--+{-# INLINE_NORMAL _toStreamD_ #-}+_toStreamD_ :: forall m a. MonadIO m => Int -> Array a -> D.Stream m a+_toStreamD_ size Array{..} = D.Stream step arrStart++ where++ {-# INLINE_LATE step #-}+ step _ p | p == arrEnd = return D.Stop+ step _ p = liftIO $ do+ x <- peekAt p arrContents+ return $ D.Yield x (p + size)++{-+XXX Why isn't Unboxed implicit? This does not compile unless I use the Unboxed+contraint.+{-# INLINE_NORMAL _foldr #-}+_foldr :: forall a b. (a -> b -> b) -> b -> Array a -> b+_foldr f z arr =+ let !n = SIZE_OF(a)+ in unsafePerformIO $ D.foldr f z $ toStreamD_ n arr+-- | Note that the 'Foldable' instance is 7x slower than the direct+-- operations.+instance Foldable Array where+ foldr = _foldr+-}++#endif++-------------------------------------------------------------------------------+-- Semigroup and Monoid+-------------------------------------------------------------------------------++-- XXX Deprecate and remove the Semigroup and Monoid instances because of+-- potential misuse chances.++-- | This should not be used for combining many or N arrays as it would copy+-- the two arrays everytime to a new array. For coalescing multiple arrays use+-- 'fromChunksK' instead.+instance Unbox a => Semigroup (Array a) where+ arr1 <> arr2 = unsafePerformIO $ splice arr1 arr2++empty ::+#ifdef DEVBUILD+ Unbox a =>+#endif+ Array a+empty = Array Unboxed.empty 0 0++{-# DEPRECATED nil "Please use empty instead." #-}+nil ::+#ifdef DEVBUILD+ Unbox a =>+#endif+ Array a+nil = empty++instance Unbox a => Monoid (Array a) where+ mempty = nil+ mappend = (<>)++-------------------------------------------------------------------------------+-- Backward Compatibility+-------------------------------------------------------------------------------++RENAME(unsafeIndexIO,unsafeGetIndexIO)+RENAME(getIndexUnsafe,unsafeGetIndex)+RENAME(readerUnsafe,unsafeReader)
+ src/Streamly/Internal/Data/Binary/Parser.hs view
@@ -0,0 +1,399 @@+-- |+-- Module : Streamly.Internal.Data.Binary.Parser+-- Copyright : (c) 2020 Composewell Technologies+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--+-- Decode Haskell data types from byte streams.+--+-- It would be inefficient to use this to compose parsers for general algebraic+-- data types. For general deserialization of ADTs please use the Serialize+-- type class instances. The fastest way to deserialize byte streams+-- representing Haskell data types is to write them to arrays and deserialize+-- the array using the Serialize type class.++module Streamly.Internal.Data.Binary.Parser+ (+ -- * Type class+ FromBytes (..)++ -- * Decoders+ , unit+ , bool+ , ordering+ , eqWord8 -- XXX rename to word8Eq+ , word8+ , word16be+ , word16le+ , word32be+ , word32le+ , word64be+ , word64le+ , word64host+ , int8+ , int16be+ , int16le+ , int32be+ , int32le+ , int64be+ , int64le+ , float32be+ , float32le+ , double64be+ , double64le+ , charLatin1+ )+where++import Control.Monad.IO.Class (MonadIO)+import Data.Bits ((.|.), unsafeShiftL)+import Data.Char (chr)+import Data.Int (Int8, Int16, Int32, Int64)+import GHC.Float (castWord32ToFloat, castWord64ToDouble)+import Data.Word (Word8, Word16, Word32, Word64)+import Streamly.Internal.Data.Parser (Parser)+import Streamly.Internal.Data.Maybe.Strict (Maybe'(..))+import Streamly.Internal.Data.Tuple.Strict (Tuple' (..))+import qualified Streamly.Data.Array as A+import qualified Streamly.Internal.Data.Array as A+ (unsafeGetIndex, unsafeCast)+import qualified Streamly.Internal.Data.Parser as PR+ (fromPure, either, satisfy, takeEQ)+import qualified Streamly.Internal.Data.Parser as PRD+ (Parser(..), Initial(..), Step(..), Final(..))++-- Note: The () type does not need to have an on-disk representation in theory.+-- But we use a concrete representation for it so that we count how many ()+-- types we have. Or when we have an array of units the array a concrete+-- length.++-- | A value of type '()' is encoded as @0@ in binary encoding.+--+-- @+-- 0 ==> ()+-- @+--+-- /Pre-release/+--+{-# INLINE unit #-}+unit :: Monad m => Parser Word8 m ()+unit = eqWord8 0 *> PR.fromPure ()++{-# INLINE word8ToBool #-}+word8ToBool :: Word8 -> Either String Bool+word8ToBool 0 = Right False+word8ToBool 1 = Right True+word8ToBool w = Left ("Invalid Bool encoding " ++ Prelude.show w)++-- | A value of type 'Bool' is encoded as follows in binary encoding.+--+-- @+-- 0 ==> False+-- 1 ==> True+-- @+--+-- /Pre-release/+--+{-# INLINE bool #-}+bool :: Monad m => Parser Word8 m Bool+bool = PR.either word8ToBool++{-# INLINE word8ToOrdering #-}+word8ToOrdering :: Word8 -> Either String Ordering+word8ToOrdering 0 = Right LT+word8ToOrdering 1 = Right EQ+word8ToOrdering 2 = Right GT+word8ToOrdering w = Left ("Invalid Ordering encoding " ++ Prelude.show w)++-- | A value of type 'Ordering' is encoded as follows in binary encoding.+--+-- @+-- 0 ==> LT+-- 1 ==> EQ+-- 2 ==> GT+-- @+--+-- /Pre-release/+--+{-# INLINE ordering #-}+ordering :: Monad m => Parser Word8 m Ordering+ordering = PR.either word8ToOrdering++-- XXX should go in a Word8 parser module?+-- | Accept the input byte only if it is equal to the specified value.+--+-- /Pre-release/+--+{-# INLINE eqWord8 #-}+eqWord8 :: Monad m => Word8 -> Parser Word8 m Word8+eqWord8 b = PR.satisfy (== b)++-- | Accept any byte.+--+-- /Pre-release/+--+{-# INLINE word8 #-}+word8 :: Monad m => Parser Word8 m Word8+word8 = PR.satisfy (const True)++-- | Big endian (MSB first) Word16+{-# INLINE word16beD #-}+word16beD :: Monad m => PRD.Parser Word8 m Word16+word16beD = PRD.Parser step initial extract++ where++ initial = return $ PRD.IPartial Nothing'++ step Nothing' a =+ -- XXX We can use a non-failing parser or a fold so that we do not+ -- have to buffer for backtracking which is inefficient.+ return $ PRD.SContinue 1 (Just' (fromIntegral a `unsafeShiftL` 8))+ step (Just' w) a =+ return $ PRD.SDone 1 (w .|. fromIntegral a)++ extract _ = return $ PRD.FError "word16be: end of input"++-- | Parse two bytes as a 'Word16', the first byte is the MSB of the Word16 and+-- second byte is the LSB (big endian representation).+--+-- /Pre-release/+--+{-# INLINE word16be #-}+word16be :: Monad m => Parser Word8 m Word16+word16be = word16beD++-- | Little endian (LSB first) Word16+{-# INLINE word16leD #-}+word16leD :: Monad m => PRD.Parser Word8 m Word16+word16leD = PRD.Parser step initial extract++ where++ initial = return $ PRD.IPartial Nothing'++ step Nothing' a =+ return $ PRD.SContinue 1 (Just' (fromIntegral a))+ step (Just' w) a =+ return $ PRD.SDone 1 (w .|. fromIntegral a `unsafeShiftL` 8)++ extract _ = return $ PRD.FError "word16le: end of input"++-- | Parse two bytes as a 'Word16', the first byte is the LSB of the Word16 and+-- second byte is the MSB (little endian representation).+--+-- /Pre-release/+--+{-# INLINE word16le #-}+word16le :: Monad m => Parser Word8 m Word16+word16le = word16leD++-- | Big endian (MSB first) Word32+{-# INLINE word32beD #-}+word32beD :: Monad m => PRD.Parser Word8 m Word32+word32beD = PRD.Parser step initial extract++ where++ initial = return $ PRD.IPartial $ Tuple' 0 24++ step (Tuple' w sh) a = return $+ if sh /= 0+ then+ let w1 = w .|. (fromIntegral a `unsafeShiftL` sh)+ in PRD.SContinue 1 (Tuple' w1 (sh - 8))+ else PRD.SDone 1 (w .|. fromIntegral a)++ extract _ = return $ PRD.FError "word32beD: end of input"++-- | Parse four bytes as a 'Word32', the first byte is the MSB of the Word32+-- and last byte is the LSB (big endian representation).+--+-- /Pre-release/+--+{-# INLINE word32be #-}+word32be :: Monad m => Parser Word8 m Word32+word32be = word32beD++-- | Little endian (LSB first) Word32+{-# INLINE word32leD #-}+word32leD :: Monad m => PRD.Parser Word8 m Word32+word32leD = PRD.Parser step initial extract++ where++ initial = return $ PRD.IPartial $ Tuple' 0 0++ step (Tuple' w sh) a = return $+ let w1 = w .|. (fromIntegral a `unsafeShiftL` sh)+ in if sh /= 24+ then PRD.SContinue 1 (Tuple' w1 (sh + 8))+ else PRD.SDone 1 w1++ extract _ = return $ PRD.FError "word32leD: end of input"++-- | Parse four bytes as a 'Word32', the first byte is the MSB of the Word32+-- and last byte is the LSB (big endian representation).+--+-- /Pre-release/+--+{-# INLINE word32le #-}+word32le :: Monad m => Parser Word8 m Word32+word32le = word32leD++-- | Big endian (MSB first) Word64+{-# INLINE word64beD #-}+word64beD :: Monad m => PRD.Parser Word8 m Word64+word64beD = PRD.Parser step initial extract++ where++ initial = return $ PRD.IPartial $ Tuple' 0 56++ step (Tuple' w sh) a = return $+ if sh /= 0+ then+ let w1 = w .|. (fromIntegral a `unsafeShiftL` sh)+ in PRD.SContinue 1 (Tuple' w1 (sh - 8))+ else PRD.SDone 1 (w .|. fromIntegral a)++ extract _ = return $ PRD.FError "word64beD: end of input"++-- | Parse eight bytes as a 'Word64', the first byte is the MSB of the Word64+-- and last byte is the LSB (big endian representation).+--+-- /Pre-release/+--+{-# INLINE word64be #-}+word64be :: Monad m => Parser Word8 m Word64+word64be = word64beD++-- | Little endian (LSB first) Word64+{-# INLINE word64leD #-}+word64leD :: Monad m => PRD.Parser Word8 m Word64+word64leD = PRD.Parser step initial extract++ where++ initial = return $ PRD.IPartial $ Tuple' 0 0++ step (Tuple' w sh) a = return $+ let w1 = w .|. (fromIntegral a `unsafeShiftL` sh)+ in if sh /= 56+ then PRD.SContinue 1 (Tuple' w1 (sh + 8))+ else PRD.SDone 1 w1++ extract _ = return $ PRD.FError "word64leD: end of input"++-- | Parse eight bytes as a 'Word64', the first byte is the MSB of the Word64+-- and last byte is the LSB (big endian representation).+--+-- /Pre-release/+--+{-# INLINE word64le #-}+word64le :: Monad m => Parser Word8 m Word64+word64le = word64leD++{-# INLINE int8 #-}+int8 :: Monad m => Parser Word8 m Int8+int8 = fromIntegral <$> word8++-- | Parse two bytes as a 'Int16', the first byte is the MSB of the Int16 and+-- second byte is the LSB (big endian representation).+--+-- /Pre-release/+--+{-# INLINE int16be #-}+int16be :: Monad m => Parser Word8 m Int16+int16be = fromIntegral <$> word16be++-- | Parse two bytes as a 'Int16', the first byte is the LSB of the Int16 and+-- second byte is the MSB (little endian representation).+--+-- /Pre-release/+--+{-# INLINE int16le #-}+int16le :: Monad m => Parser Word8 m Int16+int16le = fromIntegral <$> word16le++-- | Parse four bytes as a 'Int32', the first byte is the MSB of the Int32+-- and last byte is the LSB (big endian representation).+--+-- /Pre-release/+--+{-# INLINE int32be #-}+int32be :: Monad m => Parser Word8 m Int32+int32be = fromIntegral <$> word32be++-- | Parse four bytes as a 'Int32', the first byte is the MSB of the Int32+-- and last byte is the LSB (big endian representation).+--+-- /Pre-release/+--+{-# INLINE int32le #-}+int32le :: Monad m => Parser Word8 m Int32+int32le = fromIntegral <$> word32le++-- | Parse eight bytes as a 'Int64', the first byte is the MSB of the Int64+-- and last byte is the LSB (big endian representation).+--+-- /Pre-release/+--+{-# INLINE int64be #-}+int64be :: Monad m => Parser Word8 m Int64+int64be = fromIntegral <$> word64be++-- | Parse eight bytes as a 'Int64', the first byte is the MSB of the Int64+-- and last byte is the LSB (big endian representation).+--+-- /Pre-release/+--+{-# INLINE int64le #-}+int64le :: Monad m => Parser Word8 m Int64+int64le = fromIntegral <$> word64le++{-# INLINE float32be #-}+float32be :: MonadIO m => Parser Word8 m Float+float32be = castWord32ToFloat <$> word32be++{-# INLINE float32le #-}+float32le :: MonadIO m => Parser Word8 m Float+float32le = castWord32ToFloat <$> word32le++{-# INLINE double64be #-}+double64be :: MonadIO m => Parser Word8 m Double+double64be = castWord64ToDouble <$> word64be++{-# INLINE double64le #-}+double64le :: MonadIO m => Parser Word8 m Double+double64le = castWord64ToDouble <$> word64le++-- | Accept any byte.+--+-- /Pre-release/+--+{-# INLINE charLatin1 #-}+charLatin1 :: Monad m => Parser Word8 m Char+charLatin1 = fmap (chr . fromIntegral) word8++-------------------------------------------------------------------------------+-- Host byte order+-------------------------------------------------------------------------------++-- | Parse eight bytes as a 'Word64' in the host byte order.+--+-- /Pre-release/+--+{-# INLINE word64host #-}+word64host :: MonadIO m => Parser Word8 m Word64+word64host =+ fmap (A.unsafeGetIndex 0 . A.unsafeCast) $ PR.takeEQ 8 (A.createOf 8)++-------------------------------------------------------------------------------+-- Type class+-------------------------------------------------------------------------------++class FromBytes a where+ -- | Decode a byte stream to a Haskell type.+ fromBytes :: Parser Word8 m a
+ src/Streamly/Internal/Data/Binary/Stream.hs view
@@ -0,0 +1,383 @@+-- |+-- Module : Streamly.Internal.Data.Binary.Stream+-- Copyright : (c) 2022 Composewell Technologies+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--+-- Encode Haskell data types to byte streams.+--+-- The primary purpose of this module is to serialize primitive Haskell types+-- to streams for convenient byte by byte processing when such a need arises.+--+-- It would be inefficient to use this to build byte streams from algebraic+-- data types. For general serialization of ADTs please use the Serialize type+-- class instances. The fastest way to convert general Haskell types to byte+-- streams is to serialize them to an array and then stream the array.++-- XXX remove unit, bool, ordering, and the type class as well++module Streamly.Internal.Data.Binary.Stream+ (+ -- * Type class+ ToBytes (..)++ -- * Encoders+ , unit+ , bool+ , ordering+ , word8+ , word16be+ , word16le+ , word32be+ , word32le+ , word64be+ , word64le+ , word64host+ , int8+ , int16be+ , int16le+ , int32be+ , int32le+ , int64be+ , int64le+ , float32be+ , float32le+ , double64be+ , double64le+ , charLatin1+ , charUtf8+ )+where++#include "MachDeps.h"++import Data.Bits (shiftR)+import Data.Char (ord)+import Data.Int (Int8, Int16, Int32, Int64)+import Data.Word (Word8, Word16, Word32, Word64)+import GHC.Float (castDoubleToWord64, castFloatToWord32)+import Streamly.Internal.Data.Stream (Stream)+import Streamly.Internal.Data.Stream (Step(..))+import Streamly.Internal.Unicode.Stream (readCharUtf8)++import qualified Streamly.Internal.Data.Stream as Stream+import qualified Streamly.Internal.Data.Stream as D++-- XXX Use StreamD directly?++-- | A value of type '()' is encoded as @0@ in binary encoding.+--+-- @+-- 0 ==> ()+-- @+--+-- /Pre-release/+--+{-# INLINE unit #-}+unit :: Applicative m => Stream m Word8+unit = Stream.fromPure 0++{-# INLINE boolToWord8 #-}+boolToWord8 :: Bool -> Word8+boolToWord8 False = 0+boolToWord8 True = 1++-- | A value of type 'Bool' is encoded as follows in binary encoding.+--+-- @+-- 0 ==> False+-- 1 ==> True+-- @+--+-- /Pre-release/+--+{-# INLINE bool #-}+bool :: Applicative m => Bool -> Stream m Word8+bool = Stream.fromPure . boolToWord8++{-# INLINE orderingToWord8 #-}+orderingToWord8 :: Ordering -> Word8+orderingToWord8 LT = 0+orderingToWord8 EQ = 1+orderingToWord8 GT = 2++-- | A value of type 'Ordering' is encoded as follows in binary encoding.+--+-- @+-- 0 ==> LT+-- 1 ==> EQ+-- 2 ==> GT+-- @+--+-- /Pre-release/+--+{-# INLINE ordering #-}+ordering :: Applicative m => Ordering -> Stream m Word8+ordering = Stream.fromPure . orderingToWord8++-- | Stream a 'Word8'.+--+-- /Pre-release/+--+{-# INLINE word8 #-}+word8 :: Applicative m => Word8 -> Stream m Word8+word8 = Stream.fromPure++data W16State = W16B1 | W16B2 | W16Done++{-# INLINE word16beD #-}+word16beD :: Applicative m => Word16 -> D.Stream m Word8+word16beD w = D.Stream step W16B1++ where++ step _ W16B1 = pure $ Yield (fromIntegral (shiftR w 8) :: Word8) W16B2+ step _ W16B2 = pure $ Yield (fromIntegral w :: Word8) W16Done+ step _ W16Done = pure Stop++-- | Stream a 'Word16' as two bytes, the first byte is the MSB of the Word16+-- and second byte is the LSB (big endian representation).+--+-- /Pre-release/+--+{-# INLINE word16be #-}+word16be :: Monad m => Word16 -> Stream m Word8+word16be = word16beD++-- | Little endian (LSB first) Word16+{-# INLINE word16leD #-}+word16leD :: Applicative m => Word16 -> D.Stream m Word8+word16leD w = D.Stream step W16B1++ where++ step _ W16B1 = pure $ Yield (fromIntegral w :: Word8) W16B2+ step _ W16B2 = pure $ Yield (fromIntegral (shiftR w 8) :: Word8) W16Done+ step _ W16Done = pure Stop++-- | Stream a 'Word16' as two bytes, the first byte is the LSB of the Word16+-- and second byte is the MSB (little endian representation).+--+-- /Pre-release/+--+{-# INLINE word16le #-}+word16le :: Monad m => Word16 -> Stream m Word8+word16le = word16leD++data W32State = W32B1 | W32B2 | W32B3 | W32B4 | W32Done++-- | Big endian (MSB first) Word32+{-# INLINE word32beD #-}+word32beD :: Applicative m => Word32 -> D.Stream m Word8+word32beD w = D.Stream step W32B1++ where++ yield n s = pure $ Yield (fromIntegral (shiftR w n) :: Word8) s++ step _ W32B1 = yield 24 W32B2+ step _ W32B2 = yield 16 W32B3+ step _ W32B3 = yield 8 W32B4+ step _ W32B4 = pure $ Yield (fromIntegral w :: Word8) W32Done+ step _ W32Done = pure Stop++-- | Stream a 'Word32' as four bytes, the first byte is the MSB of the Word32+-- and last byte is the LSB (big endian representation).+--+-- /Pre-release/+--+{-# INLINE word32be #-}+word32be :: Monad m => Word32 -> Stream m Word8+word32be = word32beD++-- | Little endian (LSB first) Word32+{-# INLINE word32leD #-}+word32leD :: Applicative m => Word32 -> D.Stream m Word8+word32leD w = D.Stream step W32B1++ where++ yield n s = pure $ Yield (fromIntegral (shiftR w n) :: Word8) s++ step _ W32B1 = pure $ Yield (fromIntegral w :: Word8) W32B2+ step _ W32B2 = yield 8 W32B3+ step _ W32B3 = yield 16 W32B4+ step _ W32B4 = yield 24 W32Done+ step _ W32Done = pure Stop++-- | Stream a 'Word32' as four bytes, the first byte is the MSB of the Word32+-- and last byte is the LSB (big endian representation).+--+-- /Pre-release/+--+{-# INLINE word32le #-}+word32le :: Monad m => Word32 -> Stream m Word8+word32le = word32leD++data W64State =+ W64B1 | W64B2 | W64B3 | W64B4 | W64B5 | W64B6 | W64B7 | W64B8 | W64Done++-- | Big endian (MSB first) Word64+{-# INLINE word64beD #-}+word64beD :: Applicative m => Word64 -> D.Stream m Word8+word64beD w = D.Stream step W64B1++ where++ yield n s = pure $ Yield (fromIntegral (shiftR w n) :: Word8) s++ step _ W64B1 = yield 56 W64B2+ step _ W64B2 = yield 48 W64B3+ step _ W64B3 = yield 40 W64B4+ step _ W64B4 = yield 32 W64B5+ step _ W64B5 = yield 24 W64B6+ step _ W64B6 = yield 16 W64B7+ step _ W64B7 = yield 8 W64B8+ step _ W64B8 = pure $ Yield (fromIntegral w :: Word8) W64Done+ step _ W64Done = pure Stop++-- | Stream a 'Word64' as eight bytes, the first byte is the MSB of the Word64+-- and last byte is the LSB (big endian representation).+--+-- /Pre-release/+--+{-# INLINE word64be #-}+word64be :: Monad m => Word64 -> Stream m Word8+word64be = word64beD++-- | Little endian (LSB first) Word64+{-# INLINE word64leD #-}+word64leD :: Applicative m => Word64 -> D.Stream m Word8+word64leD w = D.Stream step W64B1++ where++ yield n s = pure $ Yield (fromIntegral (shiftR w n) :: Word8) s++ step _ W64B1 = pure $ Yield (fromIntegral w :: Word8) W64B2+ step _ W64B2 = yield 8 W64B3+ step _ W64B3 = yield 16 W64B4+ step _ W64B4 = yield 24 W64B5+ step _ W64B5 = yield 32 W64B6+ step _ W64B6 = yield 40 W64B7+ step _ W64B7 = yield 48 W64B8+ step _ W64B8 = yield 56 W64Done+ step _ W64Done = pure Stop++-- | Stream a 'Word64' as eight bytes, the first byte is the MSB of the Word64+-- and last byte is the LSB (big endian representation).+--+-- /Pre-release/+--+{-# INLINE word64le #-}+word64le :: Monad m => Word64 -> Stream m Word8+word64le = word64leD++{-# INLINE int8 #-}+int8 :: Applicative m => Int8 -> Stream m Word8+int8 i = word8 (fromIntegral i :: Word8)++-- | Stream a 'Int16' as two bytes, the first byte is the MSB of the Int16+-- and second byte is the LSB (big endian representation).+--+-- /Pre-release/+--+{-# INLINE int16be #-}+int16be :: Monad m => Int16 -> Stream m Word8+int16be i = word16be (fromIntegral i :: Word16)++-- | Stream a 'Int16' as two bytes, the first byte is the LSB of the Int16+-- and second byte is the MSB (little endian representation).+--+-- /Pre-release/+--+{-# INLINE int16le #-}+int16le :: Monad m => Int16 -> Stream m Word8+int16le i = word16le (fromIntegral i :: Word16)++-- | Stream a 'Int32' as four bytes, the first byte is the MSB of the Int32+-- and last byte is the LSB (big endian representation).+--+-- /Pre-release/+--+{-# INLINE int32be #-}+int32be :: Monad m => Int32 -> Stream m Word8+int32be i = word32be (fromIntegral i :: Word32)++{-# INLINE int32le #-}+int32le :: Monad m => Int32 -> Stream m Word8+int32le i = word32le (fromIntegral i :: Word32)++-- | Stream a 'Int64' as eight bytes, the first byte is the MSB of the Int64+-- and last byte is the LSB (big endian representation).+--+-- /Pre-release/+--+{-# INLINE int64be #-}+int64be :: Monad m => Int64 -> Stream m Word8+int64be i = word64be (fromIntegral i :: Word64)++-- | Stream a 'Int64' as eight bytes, the first byte is the LSB of the Int64+-- and last byte is the MSB (little endian representation).+--+-- /Pre-release/+--+{-# INLINE int64le #-}+int64le :: Monad m => Int64 -> Stream m Word8+int64le i = word64le (fromIntegral i :: Word64)++-- | Big endian (MSB first) Float+{-# INLINE float32be #-}+float32be :: Monad m => Float -> Stream m Word8+float32be = word32beD . castFloatToWord32++-- | Little endian (LSB first) Float+{-# INLINE float32le #-}+float32le :: Monad m => Float -> Stream m Word8+float32le = word32leD . castFloatToWord32++-- | Big endian (MSB first) Double+{-# INLINE double64be #-}+double64be :: Monad m => Double -> Stream m Word8+double64be = word64beD . castDoubleToWord64++-- | Little endian (LSB first) Double+{-# INLINE double64le #-}+double64le :: Monad m => Double -> Stream m Word8+double64le = word64leD . castDoubleToWord64++-- | Encode a Unicode character to stream of bytes in 0-255 range.+--+{-# INLINE charLatin1 #-}+charLatin1 :: Applicative m => Char -> Stream m Word8+charLatin1 = Stream.fromPure . fromIntegral . ord++{-# INLINE charUtf8 #-}+charUtf8 :: Monad m => Char -> Stream m Word8+charUtf8 = Stream.unfold readCharUtf8++-------------------------------------------------------------------------------+-- Host byte order+-------------------------------------------------------------------------------++-- | Stream a 'Word64' as eight bytes in the host byte order.+--+-- /Pre-release/+--+{-# INLINE word64host #-}+word64host :: Monad m => Word64 -> Stream m Word8+word64host =+#ifdef WORDS_BIGENDIAN+ word64be+#else+ word64le+#endif++-------------------------------------------------------------------------------+-- Type class+-------------------------------------------------------------------------------++class ToBytes a where+ -- | Convert a Haskell type to a byte stream.+ toBytes :: a -> Stream m Word8
src/Streamly/Internal/Data/Builder.hs view
@@ -16,7 +16,10 @@ ) where +#if !MIN_VERSION_base(4,18,0) import Control.Applicative (liftA2)+#endif+import Data.Bifunctor (first) ------------------------------------------------------------------------------ -- The Builder type@@ -27,16 +30,16 @@ -- or even a Fold. Unlike fold the step function is one-shot and not called in -- a loop. newtype Builder s m a =- Builder (s -> m (s, a))+ Builder (s -> m (a, s)) -- | Maps a function on the output of the fold (the type @b@). instance Functor m => Functor (Builder s m) where {-# INLINE fmap #-}- fmap f (Builder step1) = Builder (fmap (fmap f) . step1)+ fmap f (Builder step1) = Builder (fmap (first f) . step1) {-# INLINE fromPure #-} fromPure :: Applicative m => b -> Builder s m b-fromPure b = Builder (\s -> pure (s, b))+fromPure b = Builder (\s -> pure (b, s)) -- | Chain the actions and zip the outputs. {-# INLINE sequenceWith #-}@@ -47,9 +50,9 @@ where step s = do- (s1, x) <- stepL s- (s2, y) <- stepR s1- pure (s2, func x y)+ (x, s1) <- stepL s+ (y, s2) <- stepR s1+ pure (func x y, s2) instance Monad m => Applicative (Builder a m) where {-# INLINE pure #-}@@ -74,7 +77,7 @@ where step s = do- (s1, x) <- stepL s+ (x, s1) <- stepL s let Builder stepR = f x- (s2, y) <- stepR s1- pure (s2, y)+ (y, s2) <- stepR s1+ pure (y, s2)
+ src/Streamly/Internal/Data/CString.hs view
@@ -0,0 +1,109 @@+{-# LANGUAGE UnliftedFFITypes #-}++-- |+-- Module : Streamly.Internal.Data.CString+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3-3-Clause+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--+-- MutByteArray representing null terminated c strings.+-- All APIs in this module are unsafe and caution must be used when using them.+-- Completely experimental. Everything is subject to change without notice.++module Streamly.Internal.Data.CString+ (+ splice+ , spliceCString+ , splicePtrN+ , putCString+ , length+ )++where++#ifdef DEBUG+#include "assert.hs"+#endif++import GHC.Ptr (Ptr(..), castPtr)+import Foreign.C (CString, CSize(..))+import GHC.Exts (MutableByteArray#, RealWorld)+import GHC.Word (Word8)++import Streamly.Internal.Data.MutByteArray.Type hiding (length)++import Prelude hiding (length)++-- XXX Use cstringLength# from GHC.CString in ghc-prim+foreign import ccall unsafe "string.h strlen" c_strlen+ :: MutableByteArray# RealWorld -> IO CSize++-- XXX Use cstringLength# from GHC.CString in ghc-prim+foreign import ccall unsafe "string.h strlen" c_strlen_pinned+ :: CString -> IO CSize++{-# INLINE length #-}+length :: MutByteArray -> IO Int+length (MutByteArray src#) = do+ fmap fromIntegral $ c_strlen src#++-- | Join two null terminated cstrings, the null byte of the first string is+-- overwritten. Does not check the destination length or source length.+-- Destination must have enough space to accomodate src.+--+-- Returns the offset of the null byte.+--+-- /Unsafe/+splice :: MutByteArray -> MutByteArray -> IO Int+splice dst@(MutByteArray dst#) src@(MutByteArray src#) = do+ srcLen <- fmap fromIntegral $ c_strlen src#+#ifdef DEBUG+ srcLen1 <- length src+ assertM(srcLen <= srcLen1)+#endif+ dstLen <- fmap fromIntegral $ c_strlen dst#+#ifdef DEBUG+ dstLen1 <- length dst+ assertM(dstLen <= dstLen1)+ assertM(dstLen + srcLen + 1 <= dstLen1)+#endif+ unsafePutSlice src 0 dst dstLen (srcLen + 1)+ return $ dstLen + srcLen++-- | Append specified number of bytes from a Ptr to a MutByteArray CString. The+-- null byte of CString is overwritten and the result is terminated with a null+-- byte.+{-# INLINE splicePtrN #-}+splicePtrN :: MutByteArray -> Ptr Word8 -> Int -> IO Int+splicePtrN dst@(MutByteArray dst#) src srcLen = do+ dstLen <- fmap fromIntegral $ c_strlen dst#+#ifdef DEBUG+ dstLen1 <- length dst+ assertM(dstLen <= dstLen1)+ assertM(dstLen + srcLen + 1 <= dstLen1)+#endif+ -- unsafePutSlice src 0 dst dstLen srcLen+ -- XXX unsafePutPtrN signature consistency with serialization routines+ -- XXX unsafePutSlice as well+ unsafePutPtrN src dst dstLen (srcLen + 1)+ return $ dstLen + srcLen++-- | Join a null terminated cstring MutByteByteArray with a null terminated+-- cstring Ptr.+{-# INLINE spliceCString #-}+spliceCString :: MutByteArray -> CString -> IO Int+spliceCString dst src = do+ srcLen <- fmap fromIntegral $ c_strlen_pinned src+ splicePtrN dst (castPtr src) srcLen++-- XXX this is CString serialization.++-- | @putCString dst dstOffset cstr@ writes the cstring cstr at dstOffset in+-- the dst MutByteArray. The result is terminated by a null byte.+{-# INLINE putCString #-}+putCString :: MutByteArray -> Int -> CString -> IO Int+putCString dst off src = do+ srcLen <- fmap fromIntegral $ c_strlen_pinned src+ unsafePutPtrN (castPtr src) dst off (srcLen + 1)+ return $ off + srcLen
src/Streamly/Internal/Data/Fold.hs view
@@ -1,2598 +1,36 @@ {-# LANGUAGE CPP #-}--- |--- Module : Streamly.Internal.Data.Fold--- Copyright : (c) 2019 Composewell Technologies--- (c) 2013 Gabriel Gonzalez--- License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ See "Streamly.Data.Fold" for an overview and--- "Streamly.Internal.Data.Fold.Type" for design notes.--module Streamly.Internal.Data.Fold- (- -- * Imports- -- $setup-- -- * Fold Type- Step (..)- , Fold (..)- , Tee (..)-- -- * Constructors- -- | Which constructor to use?- --- -- * @foldl*@: If the fold never terminates i.e. does not use the 'Done'- -- constructor otherwise use the @foldt*@ variants.- -- * @*M@: Use the @M@ suffix variants if any of the step, initial, or- -- extract function is monadic, otherwise use the pure variants.- --- , foldl'- , foldlM'- , foldl1'- , foldlM1'- , foldt'- , foldtM'- , foldr'- , foldrM'-- -- * Mappers- -- | Monadic functions useful with mapM/lmapM on folds or streams.- , tracing- , trace-- -- * Folds-- -- ** Accumulators- -- *** Semigroups and Monoids- , sconcat- , mconcat- , foldMap- , foldMapM-- -- *** Reducers- , drain- , drainMapM- , the- , length- , lengthGeneric- , mean- , rollingHash- , defaultSalt- , rollingHashWithSalt- , rollingHashFirstN- -- , rollingHashLastN-- -- *** Saturating Reducers- -- | 'product' terminates if it becomes 0. Other folds can theoretically- -- saturate on bounded types, and therefore terminate, however, they will- -- run forever on unbounded types like Integer/Double.- , sum- , product- , maximumBy- , maximum- , minimumBy- , minimum-- -- *** Collectors- -- | Avoid using these folds in scalable or performance critical- -- applications, they buffer all the input in GC memory which can be- -- detrimental to performance if the input is large.- , toList- , toListRev- -- $toListRev- , toStream- , toStreamRev- , toStreamK- , toStreamKRev- , topBy- , top- , bottomBy- , bottom-- -- *** Scanners- -- | Stateful transformation of the elements. Useful in combination with- -- the 'scanMaybe' combinator. For scanners the result of the fold is- -- usually a transformation of the current element rather than an- -- aggregation of all elements till now.- , latest- -- , nthLast -- using Ring array- , indexingWith- , indexing- , indexingRev- , rollingMapM-- -- *** Filters- -- | Useful in combination with the 'scanMaybe' combinator.- , filtering- , deleteBy- , uniqBy- , uniq- , repeated- , findIndices- , elemIndices-- -- ** Terminating Folds- -- *** Empty folds- -- | Folds that return a result without consuming any input.- , fromPure- , fromEffect- , fromRefold-- -- *** Singleton folds- -- | Folds that terminate after consuming exactly one input element. All- -- these can be implemented in terms of the 'maybe' fold.- , one- , null -- XXX not very useful and could be problematic, remove it?- , satisfy- , maybe-- -- *** Multi folds- -- | Terminate after consuming one or more elements.- , drainN- -- , lastN- -- , (!!)- , indexGeneric- , index- , findM- , find- , lookup- , findIndex- , elemIndex- , elem- , notElem- , all- , any- , and- , or-- -- ** Trimmers- -- | Useful in combination with the 'scanMaybe' combinator.- , taking- , dropping- , takingEndByM- , takingEndBy- , takingEndByM_- , takingEndBy_- , droppingWhileM- , droppingWhile- , prune-- -- * Running A Fold- , drive- -- , breakStream-- -- * Building Incrementally- , extractM- , reduce- , close- , isClosed- , snoc- , snocl- , snocM- , snoclM-- , addOne- , addStream-- -- * Combinators- -- ** Utilities- , with-- -- ** Transforming the Monad- , morphInner- , generalizeInner-- -- ** Mapping on output- , rmapM-- -- ** Mapping on Input- , transform- , lmap- --, lsequence- , lmapM-- -- ** Sliding Window- , slide2-- -- ** Scanning Input- , scan- , scanMany- , postscan- , indexed-- -- ** Zipping Input- , zipStreamWithM- , zipStream-- -- ** Filtering Input- , catMaybes- , mapMaybeM- , mapMaybe- , scanMaybe- , filter- , filterM- , sampleFromthen-- -- Either streams- , catLefts- , catRights- , catEithers-- {-- -- ** Insertion- -- | Insertion adds more elements to the stream.-- , insertBy- , intersperseM-- -- ** Reordering- , reverse- -}-- -- ** Trimming- , take-- -- By elements- , takeEndBy- , takeEndBy_- , takeEndBySeq- , takeEndBySeq_- {-- , drop- , dropWhile- , dropWhileM- -}-- -- ** Serial Append- , splitWith- , split_- -- , tail- -- , init- , splitAt -- spanN- -- , splitIn -- sessionN-- -- ** Parallel Distribution- , teeWith- , tee- , teeWithFst- , teeWithMin- , distribute- -- , distributeFst- -- , distributeMin-- -- ** Unzipping- , unzip- -- These two can be expressed using lmap/lmapM and unzip- , unzipWith- , unzipWithM- , unzipWithFstM- , unzipWithMinM-- -- ** Parallel Alternative- , shortest- , longest-- -- ** Partitioning- , partitionByM- , partitionByFstM- , partitionByMinM- , partitionBy- , partition-- -- ** Splitting- , many- , manyPost- , groupsOf- , chunksBetween- , refoldMany- , refoldMany1- , intersperseWithQuotes-- -- ** Nesting- , unfoldMany- , concatSequence- , concatMap- , duplicate- , refold-- -- * Deprecated- , foldr- , drainBy- , last- , head- , sequence- , mapM- , variance- , stdDev- , serialWith- )-where--#include "inline.hs"-#include "ArrayMacros.h"--import Control.Monad (void)-import Control.Monad.IO.Class (MonadIO(..))-import Data.Bifunctor (first)-import Data.Bits (shiftL, shiftR, (.|.), (.&.))-import Data.Either (isLeft, isRight, fromLeft, fromRight)-import Data.Int (Int64)-import Data.Proxy (Proxy(..))-import Data.Word (Word32)-import Foreign.Storable (Storable, peek)-import Streamly.Internal.Data.Array.Mut.Type (MutArray(..))-import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)-import Streamly.Internal.Data.Pipe.Type (Pipe (..), PipeState(..))-import Streamly.Internal.Data.Unboxed (Unbox, sizeOf)-import Streamly.Internal.Data.Unfold.Type (Unfold(..))-import Streamly.Internal.Data.Tuple.Strict (Tuple'(..), Tuple3'(..))-import Streamly.Internal.Data.Stream.StreamD.Type (Stream)--import qualified Prelude-import qualified Streamly.Internal.Data.Array.Mut.Type as MA-import qualified Streamly.Internal.Data.Array.Type as Array-import qualified Streamly.Internal.Data.Fold.Window as FoldW-import qualified Streamly.Internal.Data.Pipe.Type as Pipe-import qualified Streamly.Internal.Data.Ring.Unboxed as Ring-import qualified Streamly.Internal.Data.Stream.StreamD.Type as StreamD--import Prelude hiding- ( filter, foldl1, drop, dropWhile, take, takeWhile, zipWith- , foldl, foldr, map, mapM_, sequence, all, any, sum, product, elem- , notElem, maximum, minimum, head, last, tail, length, null- , reverse, iterate, init, and, or, lookup, (!!)- , scanl, scanl1, replicate, concatMap, mconcat, foldMap, unzip- , span, splitAt, break, mapM, zip, maybe)-import Streamly.Internal.Data.Fold.Type-import Streamly.Internal.Data.Fold.Tee--#include "DocTestDataFold.hs"----------------------------------------------------------------------------------- Running----------------------------------------------------------------------------------- | Drive a fold using the supplied 'Stream', reducing the resulting--- expression strictly at each step.------ Definition:------ >>> drive = flip Stream.fold------ Example:------ >>> Fold.drive (Stream.enumerateFromTo 1 100) Fold.sum--- 5050----{-# INLINE drive #-}-drive :: Monad m => Stream m a -> Fold m a b -> m b-drive = flip StreamD.fold--{---- | Like 'drive' but also returns the remaining stream. The resulting stream--- would be 'Stream.nil' if the stream finished before the fold.------ Definition:------ >>> breakStream = flip Stream.foldBreak------ /CPS/----{-# INLINE breakStreamK #-}-breakStreamK :: Monad m => StreamK m a -> Fold m a b -> m (b, StreamK m a)-breakStreamK strm fl = fmap f $ K.foldBreak fl (Stream.toStreamK strm)-- where-- f (b, str) = (b, Stream.fromStreamK str)--}---- | Append a stream to a fold to build the fold accumulator incrementally. We--- can repeatedly call 'addStream' on the same fold to continue building the--- fold and finally use 'drive' to finish the fold and extract the result. Also--- see the 'Streamly.Data.Fold.addOne' operation which is a singleton version--- of 'addStream'.------ Definitions:------ >>> addStream stream = Fold.drive stream . Fold.duplicate------ Example, build a list incrementally:------ >>> :{--- pure (Fold.toList :: Fold IO Int [Int])--- >>= Fold.addOne 1--- >>= Fold.addStream (Stream.enumerateFromTo 2 4)--- >>= Fold.drive Stream.nil--- >>= print--- :}--- [1,2,3,4]------ This can be used as an O(n) list append compared to the O(n^2) @++@ when--- used for incrementally building a list.------ Example, build a stream incrementally:------ >>> :{--- pure (Fold.toStream :: Fold IO Int (Stream Identity Int))--- >>= Fold.addOne 1--- >>= Fold.addStream (Stream.enumerateFromTo 2 4)--- >>= Fold.drive Stream.nil--- >>= print--- :}--- fromList [1,2,3,4]------ This can be used as an O(n) stream append compared to the O(n^2) @<>@ when--- used for incrementally building a stream.------ Example, build an array incrementally:------ >>> :{--- pure (Array.write :: Fold IO Int (Array Int))--- >>= Fold.addOne 1--- >>= Fold.addStream (Stream.enumerateFromTo 2 4)--- >>= Fold.drive Stream.nil--- >>= print--- :}--- fromList [1,2,3,4]------ Example, build an array stream incrementally:------ >>> :{--- let f :: Fold IO Int (Stream Identity (Array Int))--- f = Fold.groupsOf 2 (Array.writeN 3) Fold.toStream--- in pure f--- >>= Fold.addOne 1--- >>= Fold.addStream (Stream.enumerateFromTo 2 4)--- >>= Fold.drive Stream.nil--- >>= print--- :}--- fromList [fromList [1,2],fromList [3,4]]----addStream :: Monad m => Stream m a -> Fold m a b -> m (Fold m a b)-addStream stream = drive stream . duplicate----------------------------------------------------------------------------------- Transformations on fold inputs----------------------------------------------------------------------------------- | Flatten the monadic output of a fold to pure output.----{-# DEPRECATED sequence "Use \"rmapM id\" instead" #-}-{-# INLINE sequence #-}-sequence :: Monad m => Fold m a (m b) -> Fold m a b-sequence = rmapM id---- | Map a monadic function on the output of a fold.----{-# DEPRECATED mapM "Use rmapM instead" #-}-{-# INLINE mapM #-}-mapM :: Monad m => (b -> m c) -> Fold m a b -> Fold m a c-mapM = rmapM---- |--- >>> mapMaybeM f = Fold.lmapM f . Fold.catMaybes----{-# INLINE mapMaybeM #-}-mapMaybeM :: Monad m => (a -> m (Maybe b)) -> Fold m b r -> Fold m a r-mapMaybeM f = lmapM f . catMaybes---- | @mapMaybe f fold@ maps a 'Maybe' returning function @f@ on the input of--- the fold, filters out 'Nothing' elements, and return the values extracted--- from 'Just'.------ >>> mapMaybe f = Fold.lmap f . Fold.catMaybes--- >>> mapMaybe f = Fold.mapMaybeM (return . f)------ >>> f x = if even x then Just x else Nothing--- >>> fld = Fold.mapMaybe f Fold.toList--- >>> Stream.fold fld (Stream.enumerateFromTo 1 10)--- [2,4,6,8,10]----{-# INLINE mapMaybe #-}-mapMaybe :: Monad m => (a -> Maybe b) -> Fold m b r -> Fold m a r-mapMaybe f = lmap f . catMaybes----------------------------------------------------------------------------------- Transformations on fold inputs----------------------------------------------------------------------------------- | Apply a monadic function on the input and return the input.------ >>> Stream.fold (Fold.lmapM (Fold.tracing print) Fold.drain) $ (Stream.enumerateFromTo (1 :: Int) 2)--- 1--- 2------ /Pre-release/----{-# INLINE tracing #-}-tracing :: Monad m => (a -> m b) -> (a -> m a)-tracing f x = void (f x) >> return x---- | Apply a monadic function to each element flowing through and discard the--- results.------ >>> Stream.fold (Fold.trace print Fold.drain) $ (Stream.enumerateFromTo (1 :: Int) 2)--- 1--- 2------ >>> trace f = Fold.lmapM (Fold.tracing f)------ /Pre-release/-{-# INLINE trace #-}-trace :: Monad m => (a -> m b) -> Fold m a r -> Fold m a r-trace f = lmapM (tracing f)---- rename to lpipe?------ | Apply a transformation on a 'Fold' using a 'Pipe'.------ /Pre-release/-{-# INLINE transform #-}-transform :: Monad m => Pipe m a b -> Fold m b c -> Fold m a c-transform (Pipe pstep1 pstep2 pinitial) (Fold fstep finitial fextract) =- Fold step initial extract-- where-- initial = first (Tuple' pinitial) <$> finitial-- step (Tuple' ps fs) x = do- r <- pstep1 ps x- go fs r-- where-- -- XXX use SPEC?- go acc (Pipe.Yield b (Consume ps')) = do- acc' <- fstep acc b- return- $ case acc' of- Partial s -> Partial $ Tuple' ps' s- Done b2 -> Done b2- go acc (Pipe.Yield b (Produce ps')) = do- acc' <- fstep acc b- r <- pstep2 ps'- case acc' of- Partial s -> go s r- Done b2 -> return $ Done b2- go acc (Pipe.Continue (Consume ps')) =- return $ Partial $ Tuple' ps' acc- go acc (Pipe.Continue (Produce ps')) = do- r <- pstep2 ps'- go acc r-- extract (Tuple' _ fs) = fextract fs--{-# INLINE scanWith #-}-scanWith :: Monad m => Bool -> Fold m a b -> Fold m b c -> Fold m a c-scanWith isMany (Fold stepL initialL extractL) (Fold stepR initialR extractR) =- Fold step initial extract-- where-- {-# INLINE runStep #-}- runStep actionL sR = do- rL <- actionL- case rL of- Done bL -> do- rR <- stepR sR bL- case rR of- Partial sR1 ->- if isMany- then runStep initialL sR1- else Done <$> extractR sR1- Done bR -> return $ Done bR- Partial sL -> do- !b <- extractL sL- rR <- stepR sR b- return- $ case rR of- Partial sR1 -> Partial (sL, sR1)- Done bR -> Done bR-- initial = do- r <- initialR- case r of- Partial sR -> runStep initialL sR- Done b -> return $ Done b-- step (sL, sR) x = runStep (stepL sL x) sR-- extract = extractR . snd---- | Scan the input of a 'Fold' to change it in a stateful manner using another--- 'Fold'. The scan stops as soon as the fold terminates.------ /Pre-release/-{-# INLINE scan #-}-scan :: Monad m => Fold m a b -> Fold m b c -> Fold m a c-scan = scanWith False---- XXX This does not fuse beacuse of the recursive step. Need to investigate.------ | Scan the input of a 'Fold' to change it in a stateful manner using another--- 'Fold'. The scan restarts with a fresh state if the fold terminates.------ /Pre-release/-{-# INLINE scanMany #-}-scanMany :: Monad m => Fold m a b -> Fold m b c -> Fold m a c-scanMany = scanWith True----------------------------------------------------------------------------------- Filters----------------------------------------------------------------------------------- | Returns the latest element omitting the first occurrence that satisfies--- the given equality predicate.------ Example:------ >>> input = Stream.fromList [1,3,3,5]--- >>> Stream.fold Fold.toList $ Stream.scanMaybe (Fold.deleteBy (==) 3) input--- [1,3,5]----{-# INLINE_NORMAL deleteBy #-}-deleteBy :: Monad m => (a -> a -> Bool) -> a -> Fold m a (Maybe a)-deleteBy eq x0 = fmap extract $ foldl' step (Tuple' False Nothing)-- where-- step (Tuple' False _) x =- if eq x x0- then Tuple' True Nothing- else Tuple' False (Just x)- step (Tuple' True _) x = Tuple' True (Just x)-- extract (Tuple' _ x) = x---- | Provide a sliding window of length 2 elements.------ See "Streamly.Internal.Data.Fold.Window".----{-# INLINE slide2 #-}-slide2 :: Monad m => Fold m (a, Maybe a) b -> Fold m a b-slide2 (Fold step1 initial1 extract1) = Fold step initial extract-- where-- initial =- first (Tuple' Nothing) <$> initial1-- step (Tuple' prev s) cur =- first (Tuple' (Just cur)) <$> step1 s (cur, prev)-- extract (Tuple' _ s) = extract1 s---- | Return the latest unique element using the supplied comparison function.--- Returns 'Nothing' if the current element is same as the last element--- otherwise returns 'Just'.------ Example, strip duplicate path separators:------ >>> input = Stream.fromList "//a//b"--- >>> f x y = x == '/' && y == '/'--- >>> Stream.fold Fold.toList $ Stream.scanMaybe (Fold.uniqBy f) input--- "/a/b"------ Space: @O(1)@------ /Pre-release/----{-# INLINE uniqBy #-}-uniqBy :: Monad m => (a -> a -> Bool) -> Fold m a (Maybe a)-uniqBy eq = rollingMap f-- where-- f pre curr =- case pre of- Nothing -> Just curr- Just x -> if x `eq` curr then Nothing else Just curr---- | See 'uniqBy'.------ Definition:------ >>> uniq = Fold.uniqBy (==)----{-# INLINE uniq #-}-uniq :: (Monad m, Eq a) => Fold m a (Maybe a)-uniq = uniqBy (==)---- | Strip all leading and trailing occurrences of an element passing a--- predicate and make all other consecutive occurrences uniq.------ >> prune p = Stream.dropWhileAround p $ Stream.uniqBy (x y -> p x && p y)------ @--- > Stream.prune isSpace (Stream.fromList " hello world! ")--- "hello world!"------ @------ Space: @O(1)@------ /Unimplemented/-{-# INLINE prune #-}-prune ::- -- (Monad m, Eq a) =>- (a -> Bool) -> Fold m a (Maybe a)-prune = error "Not implemented yet!"---- | Emit only repeated elements, once.------ /Unimplemented/-repeated :: -- (Monad m, Eq a) =>- Fold m a (Maybe a)-repeated = error "Not implemented yet!"----------------------------------------------------------------------------------- Left folds------------------------------------------------------------------------------------------------------------------------------------------------------------------ Run Effects----------------------------------------------------------------------------------- |--- Definitions:------ >>> drainMapM f = Fold.lmapM f Fold.drain--- >>> drainMapM f = Fold.foldMapM (void . f)------ Drain all input after passing it through a monadic function. This is the--- dual of mapM_ on stream producers.----{-# INLINE drainMapM #-}-drainMapM :: Monad m => (a -> m b) -> Fold m a ()-drainMapM f = lmapM f drain--{-# DEPRECATED drainBy "Please use 'drainMapM' instead." #-}-{-# INLINE drainBy #-}-drainBy :: Monad m => (a -> m b) -> Fold m a ()-drainBy = drainMapM---- | Returns the latest element of the input stream, if any.------ >>> latest = Fold.foldl1' (\_ x -> x)--- >>> latest = fmap getLast $ Fold.foldMap (Last . Just)----{-# INLINE latest #-}-latest :: Monad m => Fold m a (Maybe a)-latest = foldl1' (\_ x -> x)--{-# DEPRECATED last "Please use 'latest' instead." #-}-{-# INLINE last #-}-last :: Monad m => Fold m a (Maybe a)-last = latest---- | Terminates with 'Nothing' as soon as it finds an element different than--- the previous one, returns 'the' element if the entire input consists of the--- same element.----{-# INLINE the #-}-the :: (Monad m, Eq a) => Fold m a (Maybe a)-the = foldt' step initial id-- where-- initial = Partial Nothing-- step Nothing x = Partial (Just x)- step old@(Just x0) x =- if x0 == x- then Partial old- else Done Nothing----------------------------------------------------------------------------------- To Summary----------------------------------------------------------------------------------- | Like 'length', except with a more general 'Num' return value------ Definition:------ >>> lengthGeneric = fmap getSum $ Fold.foldMap (Sum . const 1)--- >>> lengthGeneric = Fold.foldl' (\n _ -> n + 1) 0------ /Pre-release/-{-# INLINE lengthGeneric #-}-lengthGeneric :: (Monad m, Num b) => Fold m a b-lengthGeneric = foldl' (\n _ -> n + 1) 0---- | Determine the length of the input stream.------ Definition:------ >>> length = Fold.lengthGeneric--- >>> length = fmap getSum $ Fold.foldMap (Sum . const 1)----{-# INLINE length #-}-length :: Monad m => Fold m a Int-length = lengthGeneric----- | Determine the sum of all elements of a stream of numbers. Returns additive--- identity (@0@) when the stream is empty. Note that this is not numerically--- stable for floating point numbers.------ >>> sum = FoldW.cumulative FoldW.sum------ Same as following but numerically stable:------ >>> sum = Fold.foldl' (+) 0--- >>> sum = fmap Data.Monoid.getSum $ Fold.foldMap Data.Monoid.Sum----{-# INLINE sum #-}-sum :: (Monad m, Num a) => Fold m a a-sum = FoldW.cumulative FoldW.sum---- | Determine the product of all elements of a stream of numbers. Returns--- multiplicative identity (@1@) when the stream is empty. The fold terminates--- when it encounters (@0@) in its input.------ Same as the following but terminates on multiplication by @0@:------ >>> product = fmap Data.Monoid.getProduct $ Fold.foldMap Data.Monoid.Product----{-# INLINE product #-}-product :: (Monad m, Num a, Eq a) => Fold m a a-product = foldt' step (Partial 1) id-- where-- step x a =- if a == 0- then Done 0- else Partial $ x * a----------------------------------------------------------------------------------- To Summary (Maybe)----------------------------------------------------------------------------------- | Determine the maximum element in a stream using the supplied comparison--- function.----{-# INLINE maximumBy #-}-maximumBy :: Monad m => (a -> a -> Ordering) -> Fold m a (Maybe a)-maximumBy cmp = foldl1' max'-- where-- max' x y =- case cmp x y of- GT -> x- _ -> y---- | Determine the maximum element in a stream.------ Definitions:------ >>> maximum = Fold.maximumBy compare--- >>> maximum = Fold.foldl1' max------ Same as the following but without a default maximum. The 'Max' Monoid uses--- the 'minBound' as the default maximum:------ >>> maximum = fmap Data.Semigroup.getMax $ Fold.foldMap Data.Semigroup.Max----{-# INLINE maximum #-}-maximum :: (Monad m, Ord a) => Fold m a (Maybe a)-maximum = foldl1' max---- | Computes the minimum element with respect to the given comparison function----{-# INLINE minimumBy #-}-minimumBy :: Monad m => (a -> a -> Ordering) -> Fold m a (Maybe a)-minimumBy cmp = foldl1' min'-- where-- min' x y =- case cmp x y of- GT -> y- _ -> x---- | Determine the minimum element in a stream using the supplied comparison--- function.------ Definitions:------ >>> minimum = Fold.minimumBy compare--- >>> minimum = Fold.foldl1' min------ Same as the following but without a default minimum. The 'Min' Monoid uses the--- 'maxBound' as the default maximum:------ >>> maximum = fmap Data.Semigroup.getMin $ Fold.foldMap Data.Semigroup.Min----{-# INLINE minimum #-}-minimum :: (Monad m, Ord a) => Fold m a (Maybe a)-minimum = foldl1' min----------------------------------------------------------------------------------- To Summary (Statistical)----------------------------------------------------------------------------------- | Compute a numerically stable arithmetic mean of all elements in the input--- stream.----{-# INLINE mean #-}-mean :: (Monad m, Fractional a) => Fold m a a-mean = fmap done $ foldl' step begin-- where-- begin = Tuple' 0 0-- step (Tuple' x n) y =- let n1 = n + 1- in Tuple' (x + (y - x) / n1) n1-- done (Tuple' x _) = x---- | Compute a numerically stable (population) variance over all elements in--- the input stream.----{-# DEPRECATED variance "Use the streamly-statistics package instead" #-}-{-# INLINE variance #-}-variance :: (Monad m, Fractional a) => Fold m a a-variance = fmap done $ foldl' step begin-- where-- begin = Tuple3' 0 0 0-- step (Tuple3' n mean_ m2) x = Tuple3' n' mean' m2'-- where-- n' = n + 1- mean' = (n * mean_ + x) / (n + 1)- delta = x - mean_- m2' = m2 + delta * delta * n / (n + 1)-- done (Tuple3' n _ m2) = m2 / n---- | Compute a numerically stable (population) standard deviation over all--- elements in the input stream.----{-# DEPRECATED stdDev "Use the streamly-statistics package instead" #-}-{-# INLINE stdDev #-}-stdDev :: (Monad m, Floating a) => Fold m a a-stdDev = sqrt <$> variance---- | Compute an 'Int' sized polynomial rolling hash------ > H = salt * k ^ n + c1 * k ^ (n - 1) + c2 * k ^ (n - 2) + ... + cn * k ^ 0------ Where @c1@, @c2@, @cn@ are the elements in the input stream and @k@ is a--- constant.------ This hash is often used in Rabin-Karp string search algorithm.------ See https://en.wikipedia.org/wiki/Rolling_hash----{-# INLINE rollingHashWithSalt #-}-rollingHashWithSalt :: (Monad m, Enum a) => Int64 -> Fold m a Int64-rollingHashWithSalt = foldl' step-- where-- k = 2891336453 :: Int64-- step cksum a = cksum * k + fromIntegral (fromEnum a)---- | A default salt used in the implementation of 'rollingHash'.-{-# INLINE defaultSalt #-}-defaultSalt :: Int64-defaultSalt = -2578643520546668380---- | Compute an 'Int' sized polynomial rolling hash of a stream.------ >>> rollingHash = Fold.rollingHashWithSalt Fold.defaultSalt----{-# INLINE rollingHash #-}-rollingHash :: (Monad m, Enum a) => Fold m a Int64-rollingHash = rollingHashWithSalt defaultSalt---- | Compute an 'Int' sized polynomial rolling hash of the first n elements of--- a stream.------ >>> rollingHashFirstN n = Fold.take n Fold.rollingHash------ /Pre-release/-{-# INLINE rollingHashFirstN #-}-rollingHashFirstN :: (Monad m, Enum a) => Int -> Fold m a Int64-rollingHashFirstN n = take n rollingHash---- XXX Compare this with the implementation in Fold.Window, preferrably use the--- latter if performance is good.---- | Apply a function on every two successive elements of a stream. The first--- argument of the map function is the previous element and the second argument--- is the current element. When processing the very first element in the--- stream, the previous element is 'Nothing'.------ /Pre-release/----{-# INLINE rollingMapM #-}-rollingMapM :: Monad m => (Maybe a -> a -> m b) -> Fold m a b-rollingMapM f = Fold step initial extract-- where-- -- XXX We need just a postscan. We do not need an initial result here.- -- Or we can supply a default initial result as an argument to rollingMapM.- initial = return $ Partial (Nothing, error "Empty stream")-- step (prev, _) cur = do- x <- f prev cur- return $ Partial (Just cur, x)-- extract = return . snd---- |--- >>> rollingMap f = Fold.rollingMapM (\x y -> return $ f x y)----{-# INLINE rollingMap #-}-rollingMap :: Monad m => (Maybe a -> a -> b) -> Fold m a b-rollingMap f = rollingMapM (\x y -> return $ f x y)----------------------------------------------------------------------------------- Monoidal left folds----------------------------------------------------------------------------------- | Semigroup concat. Append the elements of an input stream to a provided--- starting value.------ Definition:------ >>> sconcat = Fold.foldl' (<>)------ >>> semigroups = fmap Data.Monoid.Sum $ Stream.enumerateFromTo 1 10--- >>> Stream.fold (Fold.sconcat 10) semigroups--- Sum {getSum = 65}----{-# INLINE sconcat #-}-sconcat :: (Monad m, Semigroup a) => a -> Fold m a a-sconcat = foldl' (<>)---- | Monoid concat. Fold an input stream consisting of monoidal elements using--- 'mappend' and 'mempty'.------ Definition:------ >>> mconcat = Fold.sconcat mempty------ >>> monoids = fmap Data.Monoid.Sum $ Stream.enumerateFromTo 1 10--- >>> Stream.fold Fold.mconcat monoids--- Sum {getSum = 55}----{-# INLINE mconcat #-}-mconcat ::- ( Monad m- , Monoid a) => Fold m a a-mconcat = sconcat mempty---- |--- Definition:------ >>> foldMap f = Fold.lmap f Fold.mconcat------ Make a fold from a pure function that folds the output of the function--- using 'mappend' and 'mempty'.------ >>> sum = Fold.foldMap Data.Monoid.Sum--- >>> Stream.fold sum $ Stream.enumerateFromTo 1 10--- Sum {getSum = 55}----{-# INLINE foldMap #-}-foldMap :: (Monad m, Monoid b) => (a -> b) -> Fold m a b-foldMap f = lmap f mconcat---- |--- Definition:------ >>> foldMapM f = Fold.lmapM f Fold.mconcat------ Make a fold from a monadic function that folds the output of the function--- using 'mappend' and 'mempty'.------ >>> sum = Fold.foldMapM (return . Data.Monoid.Sum)--- >>> Stream.fold sum $ Stream.enumerateFromTo 1 10--- Sum {getSum = 55}----{-# INLINE foldMapM #-}-foldMapM :: (Monad m, Monoid b) => (a -> m b) -> Fold m a b-foldMapM act = foldlM' step (pure mempty)-- where-- step m a = do- m' <- act a- return $! mappend m m'----------------------------------------------------------------------------------- To Containers----------------------------------------------------------------------------------- $toListRev--- This is more efficient than 'Streamly.Internal.Data.Fold.toList'. toList is--- exactly the same as reversing the list after 'toListRev'.---- | Buffers the input stream to a list in the reverse order of the input.------ Definition:------ >>> toListRev = Fold.foldl' (flip (:)) []------ /Warning!/ working on large lists accumulated as buffers in memory could be--- very inefficient, consider using "Streamly.Array" instead.------- xn : ... : x2 : x1 : []-{-# INLINE toListRev #-}-toListRev :: Monad m => Fold m a [a]-toListRev = foldl' (flip (:)) []----------------------------------------------------------------------------------- Partial Folds----------------------------------------------------------------------------------- | A fold that drains the first n elements of its input, running the effects--- and discarding the results.------ Definition:------ >>> drainN n = Fold.take n Fold.drain------ /Pre-release/-{-# INLINE drainN #-}-drainN :: Monad m => Int -> Fold m a ()-drainN n = take n drain----------------------------------------------------------------------------------- To Elements----------------------------------------------------------------------------------- | Like 'index', except with a more general 'Integral' argument------ /Pre-release/-{-# INLINE indexGeneric #-}-indexGeneric :: (Integral i, Monad m) => i -> Fold m a (Maybe a)-indexGeneric i = foldt' step (Partial 0) (const Nothing)-- where-- step j a =- if i == j- then Done $ Just a- else Partial (j + 1)---- | Return the element at the given index.------ Definition:------ >>> index = Fold.indexGeneric----{-# INLINE index #-}-index :: Monad m => Int -> Fold m a (Maybe a)-index = indexGeneric---- | Consume a single input and transform it using the supplied 'Maybe'--- returning function.------ /Pre-release/----{-# INLINE maybe #-}-maybe :: Monad m => (a -> Maybe b) -> Fold m a (Maybe b)-maybe f = foldt' (const (Done . f)) (Partial Nothing) id---- | Consume a single element and return it if it passes the predicate else--- return 'Nothing'.------ Definition:------ >>> satisfy f = Fold.maybe (\a -> if f a then Just a else Nothing)------ /Pre-release/-{-# INLINE satisfy #-}-satisfy :: Monad m => (a -> Bool) -> Fold m a (Maybe a)-satisfy f = maybe (\a -> if f a then Just a else Nothing)-{--satisfy f = Fold step (return $ Partial ()) (const (return Nothing))-- where-- step () a = return $ Done $ if f a then Just a else Nothing--}---- Naming notes:------ "head" and "next" are two alternative names for the same API. head sounds--- apt in the context of lists but next sounds more apt in the context of--- streams where we think in terms of generating and consuming the next element--- rather than taking the head of some static/persistent structure.------ We also want to keep the nomenclature consistent across folds and parsers,--- "head" becomes even more unintuitive for parsers because there are two--- possible variants viz. peek and next.------ Also, the "head" fold creates confusion in situations like--- https://github.com/composewell/streamly/issues/1404 where intuitive--- expectation from head is to consume the entire stream and just give us the--- head. There we want to convey the notion that we consume one element from--- the stream and stop. The name "one" already being used in parsers for this--- purpose sounds more apt from this perspective.------ The source of confusion is perhaps due to the fact that some folds consume--- the entire stream and others terminate early. It may have been clearer if we--- had separate abstractions for the two use cases.---- XXX We can possibly use "head" for the purposes of reducing the entire--- stream to the head element i.e. take the head and drain the rest.---- | Take one element from the stream and stop.------ Definition:------ >>> one = Fold.maybe Just------ This is similar to the stream 'Stream.uncons' operation.----{-# INLINE one #-}-one :: Monad m => Fold m a (Maybe a)-one = maybe Just---- | Extract the first element of the stream, if any.------ >>> head = Fold.one----{-# DEPRECATED head "Please use \"one\" instead" #-}-{-# INLINE head #-}-head :: Monad m => Fold m a (Maybe a)-head = one---- | Returns the first element that satisfies the given predicate.------ /Pre-release/-{-# INLINE findM #-}-findM :: Monad m => (a -> m Bool) -> Fold m a (Maybe a)-findM predicate = Fold step (return $ Partial ()) (const $ return Nothing)-- where-- step () a =- let f r =- if r- then Done (Just a)- else Partial ()- in f <$> predicate a---- | Returns the first element that satisfies the given predicate.----{-# INLINE find #-}-find :: Monad m => (a -> Bool) -> Fold m a (Maybe a)-find p = findM (return . p)---- | 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@.------ Definition:------ >>> lookup x = fmap snd <$> Fold.find ((== x) . fst)----{-# INLINE lookup #-}-lookup :: (Eq a, Monad m) => a -> Fold m (a,b) (Maybe b)-lookup a0 = foldt' step (Partial ()) (const Nothing)-- where-- step () (a, b) =- if a == a0- then Done $ Just b- else Partial ()---- | Returns the first index that satisfies the given predicate.----{-# INLINE findIndex #-}-findIndex :: Monad m => (a -> Bool) -> Fold m a (Maybe Int)-findIndex predicate = foldt' step (Partial 0) (const Nothing)-- where-- step i a =- if predicate a- then Done $ Just i- else Partial (i + 1)---- | Returns the index of the latest element if the element satisfies the given--- predicate.----{-# INLINE findIndices #-}-findIndices :: Monad m => (a -> Bool) -> Fold m a (Maybe Int)-findIndices predicate =- -- XXX implement by combining indexing and filtering scans- fmap (either (const Nothing) Just) $ foldl' step (Left (-1))-- where-- step i a =- if predicate a- then Right (either id id i + 1)- else Left (either id id i + 1)---- | Returns the index of the latest element if the element matches the given--- value.------ Definition:------ >>> elemIndices a = Fold.findIndices (== a)----{-# INLINE elemIndices #-}-elemIndices :: (Monad m, Eq a) => a -> Fold m a (Maybe Int)-elemIndices a = findIndices (== a)---- | Returns the first index where a given value is found in the stream.------ Definition:------ >>> elemIndex a = Fold.findIndex (== a)----{-# INLINE elemIndex #-}-elemIndex :: (Eq a, Monad m) => a -> Fold m a (Maybe Int)-elemIndex a = findIndex (== a)----------------------------------------------------------------------------------- To Boolean----------------------------------------------------------------------------------- Similar to 'eof' parser, but the fold consumes and discards an input element--- when not at eof. XXX Remove or Rename to "eof"?---- | Consume one element, return 'True' if successful else return 'False'. In--- other words, test if the input is empty or not.------ WARNING! It consumes one element if the stream is not empty. If that is not--- what you want please use the eof parser instead.------ Definition:------ >>> null = fmap isJust Fold.one----{-# INLINE null #-}-null :: Monad m => Fold m a Bool-null = foldt' (\() _ -> Done False) (Partial ()) (const True)---- | Returns 'True' if any element of the input satisfies the predicate.------ Definition:------ >>> any p = Fold.lmap p Fold.or------ Example:------ >>> Stream.fold (Fold.any (== 0)) $ Stream.fromList [1,0,1]--- True----{-# INLINE any #-}-any :: Monad m => (a -> Bool) -> Fold m a Bool-any predicate = foldt' step initial id-- where-- initial = Partial False-- step _ a =- if predicate a- then Done True- else Partial False---- | Return 'True' if the given element is present in the stream.------ Definition:------ >>> elem a = Fold.any (== a)----{-# INLINE elem #-}-elem :: (Eq a, Monad m) => a -> Fold m a Bool-elem a = any (== a)---- | Returns 'True' if all elements of the input satisfy the predicate.------ Definition:------ >>> all p = Fold.lmap p Fold.and------ Example:------ >>> Stream.fold (Fold.all (== 0)) $ Stream.fromList [1,0,1]--- False----{-# INLINE all #-}-all :: Monad m => (a -> Bool) -> Fold m a Bool-all predicate = foldt' step initial id-- where-- initial = Partial True-- step _ a =- if predicate a- then Partial True- else Done False---- | Returns 'True' if the given element is not present in the stream.------ Definition:------ >>> notElem a = Fold.all (/= a)----{-# INLINE notElem #-}-notElem :: (Eq a, Monad m) => a -> Fold m a Bool-notElem a = all (/= a)---- | Returns 'True' if all elements are 'True', 'False' otherwise------ Definition:------ >>> and = Fold.all (== True)----{-# INLINE and #-}-and :: Monad m => Fold m Bool Bool-and = all (== True)---- | Returns 'True' if any element is 'True', 'False' otherwise------ Definition:------ >>> or = Fold.any (== True)----{-# INLINE or #-}-or :: Monad m => Fold m Bool Bool-or = any (== True)----------------------------------------------------------------------------------- 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 = Stream.fold (Fold.splitAt n Fold.toList Fold.toList) $ Stream.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],[])------ > splitAt n f1 f2 = Fold.splitWith (,) (Fold.take n f1) f2------ /Internal/--{-# INLINE splitAt #-}-splitAt- :: Monad m- => Int- -> Fold m a b- -> Fold m a c- -> Fold m a (b, c)-splitAt n fld = splitWith (,) (take n fld)----------------------------------------------------------------------------------- Element Aware APIs-------------------------------------------------------------------------------------------------------------------------------------------------------------------- Binary APIs---------------------------------------------------------------------------------{-# INLINE takingEndByM #-}-takingEndByM :: Monad m => (a -> m Bool) -> Fold m a (Maybe a)-takingEndByM p = Fold step initial (return . toMaybe)-- where-- initial = return $ Partial Nothing'-- step _ a = do- r <- p a- return- $ if r- then Done $ Just a- else Partial $ Just' a---- |------ >>> takingEndBy p = Fold.takingEndByM (return . p)----{-# INLINE takingEndBy #-}-takingEndBy :: Monad m => (a -> Bool) -> Fold m a (Maybe a)-takingEndBy p = takingEndByM (return . p)--{-# INLINE takingEndByM_ #-}-takingEndByM_ :: Monad m => (a -> m Bool) -> Fold m a (Maybe a)-takingEndByM_ p = Fold step initial (return . toMaybe)-- where-- initial = return $ Partial Nothing'-- step _ a = do- r <- p a- return- $ if r- then Done Nothing- else Partial $ Just' a---- |------ >>> takingEndBy_ p = Fold.takingEndByM_ (return . p)----{-# INLINE takingEndBy_ #-}-takingEndBy_ :: Monad m => (a -> Bool) -> Fold m a (Maybe a)-takingEndBy_ p = takingEndByM_ (return . p)--{-# INLINE droppingWhileM #-}-droppingWhileM :: Monad m => (a -> m Bool) -> Fold m a (Maybe a)-droppingWhileM p = Fold step initial (return . toMaybe)-- where-- initial = return $ Partial Nothing'-- step Nothing' a = do- r <- p a- return- $ Partial- $ if r- then Nothing'- else Just' a- step _ a = return $ Partial $ Just' a---- |--- >>> droppingWhile p = Fold.droppingWhileM (return . p)----{-# INLINE droppingWhile #-}-droppingWhile :: Monad m => (a -> Bool) -> Fold m a (Maybe a)-droppingWhile p = droppingWhileM (return . p)---- Note: Keep this consistent with S.splitOn. In fact we should eliminate--- S.splitOn in favor of the fold.------ XXX Use Fold.many instead once it is fixed.--- > Stream.splitOnSuffix p f = Stream.foldMany (Fold.takeEndBy_ p f)---- | Like 'takeEndBy' but drops the element on which the predicate succeeds.------ Example:------ >>> input = Stream.fromList "hello\nthere\n"--- >>> line = Fold.takeEndBy_ (== '\n') Fold.toList--- >>> Stream.fold line input--- "hello"------ >>> Stream.fold Fold.toList $ Stream.foldMany line input--- ["hello","there"]----{-# INLINE takeEndBy_ #-}-takeEndBy_ :: Monad m => (a -> Bool) -> Fold m a b -> Fold m a b--- takeEndBy_ predicate = scanMaybe (takingEndBy_ predicate)-takeEndBy_ predicate (Fold fstep finitial fextract) =- Fold step finitial fextract-- where-- step s a =- if not (predicate a)- then fstep s a- else Done <$> fextract s---- Note:--- > Stream.splitWithSuffix p f = Stream.foldMany (Fold.takeEndBy p f)---- | Take the input, stop when the predicate succeeds taking the succeeding--- element as well.------ Example:------ >>> input = Stream.fromList "hello\nthere\n"--- >>> line = Fold.takeEndBy (== '\n') Fold.toList--- >>> Stream.fold line input--- "hello\n"------ >>> Stream.fold Fold.toList $ Stream.foldMany line input--- ["hello\n","there\n"]----{-# INLINE takeEndBy #-}-takeEndBy :: Monad m => (a -> Bool) -> Fold m a b -> Fold m a b--- takeEndBy predicate = scanMaybe (takingEndBy predicate)-takeEndBy predicate (Fold fstep finitial fextract) =- Fold step finitial fextract-- where-- step s a = do- res <- fstep s a- if not (predicate a)- then return res- else do- case res of- Partial s1 -> Done <$> fextract s1- Done b -> return $ Done b----------------------------------------------------------------------------------- Binary splitting on a separator---------------------------------------------------------------------------------data SplitOnSeqState acc a rb rh w ck =- SplitOnSeqEmpty !acc- | SplitOnSeqSingle !acc !a- | SplitOnSeqWord !acc !Int !w- | SplitOnSeqWordLoop !acc !w- | SplitOnSeqKR !acc !Int !rb !rh- | SplitOnSeqKRLoop !acc !ck !rb !rh---- XXX Need to add tests for takeEndBySeq, we have tests for takeEndBySeq_ .---- | Continue taking the input until the input sequence matches the supplied--- sequence, taking the supplied sequence as well. If the pattern is empty this--- acts as an identity fold.------ >>> s = Stream.fromList "hello there. How are you?"--- >>> f = Fold.takeEndBySeq (Array.fromList "re") Fold.toList--- >>> Stream.fold f s--- "hello there"------ >>> Stream.fold Fold.toList $ Stream.foldMany f s--- ["hello there",". How are"," you?"]------ /Pre-release/-{-# INLINE takeEndBySeq #-}-takeEndBySeq :: forall m a b. (MonadIO m, Storable a, Unbox a, Enum a, Eq a) =>- Array.Array a- -> Fold m a b- -> Fold m a b-takeEndBySeq patArr (Fold fstep finitial fextract) =- Fold step initial extract-- where-- patLen = Array.length patArr-- initial = do- res <- finitial- case res of- Partial acc- | patLen == 0 ->- -- XXX Should we match nothing or everything on empty- -- pattern?- -- Done <$> fextract acc- return $ Partial $ SplitOnSeqEmpty acc- | patLen == 1 -> do- pat <- liftIO $ Array.unsafeIndexIO 0 patArr- return $ Partial $ SplitOnSeqSingle acc pat- | SIZE_OF(a) * patLen <= sizeOf (Proxy :: Proxy Word) ->- return $ Partial $ SplitOnSeqWord acc 0 0- | otherwise -> do- (rb, rhead) <- liftIO $ Ring.new patLen- return $ Partial $ SplitOnSeqKR acc 0 rb rhead- Done b -> return $ Done b-- -- Word pattern related- maxIndex = patLen - 1-- elemBits = SIZE_OF(a) * 8-- wordMask :: Word- wordMask = (1 `shiftL` (elemBits * patLen)) - 1-- wordPat :: Word- wordPat = wordMask .&. Array.foldl' addToWord 0 patArr-- addToWord wd a = (wd `shiftL` elemBits) .|. fromIntegral (fromEnum a)-- -- For Rabin-Karp search- 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?- -- XXX Need to keep this cached across fold calls in foldmany- -- XXX We may need refold to inject the cached state instead of- -- initializing the state every time.- -- XXX Allocation of ring buffer should also be done once- patHash = Array.foldl' addCksum 0 patArr-- step (SplitOnSeqEmpty s) x = do- res <- fstep s x- case res of- Partial s1 -> return $ Partial $ SplitOnSeqEmpty s1- Done b -> return $ Done b- step (SplitOnSeqSingle s pat) x = do- res <- fstep s x- case res of- Partial s1- | pat /= x -> return $ Partial $ SplitOnSeqSingle s1 pat- | otherwise -> Done <$> fextract s1- Done b -> return $ Done b- step (SplitOnSeqWord s idx wrd) x = do- res <- fstep s x- let wrd1 = addToWord wrd x- case res of- Partial s1- | idx == maxIndex -> do- if wrd1 .&. wordMask == wordPat- then Done <$> fextract s1- else return $ Partial $ SplitOnSeqWordLoop s1 wrd1- | otherwise ->- return $ Partial $ SplitOnSeqWord s1 (idx + 1) wrd1- Done b -> return $ Done b- step (SplitOnSeqWordLoop s wrd) x = do- res <- fstep s x- let wrd1 = addToWord wrd x- case res of- Partial s1- | wrd1 .&. wordMask == wordPat ->- Done <$> fextract s1- | otherwise ->- return $ Partial $ SplitOnSeqWordLoop s1 wrd1- Done b -> return $ Done b- step (SplitOnSeqKR s idx rb rh) x = do- res <- fstep s x- case res of- Partial s1 -> do- rh1 <- liftIO $ Ring.unsafeInsert rb rh x- if idx == maxIndex- then do- let fld = Ring.unsafeFoldRing (Ring.ringBound rb)- let !ringHash = fld addCksum 0 rb- if ringHash == patHash && Ring.unsafeEqArray rb rh1 patArr- then Done <$> fextract s1- else return $ Partial $ SplitOnSeqKRLoop s1 ringHash rb rh1- else- return $ Partial $ SplitOnSeqKR s1 (idx + 1) rb rh1- Done b -> return $ Done b- step (SplitOnSeqKRLoop s cksum rb rh) x = do- res <- fstep s x- case res of- Partial s1 -> do- old <- liftIO $ peek rh- rh1 <- liftIO $ Ring.unsafeInsert rb rh x- let ringHash = deltaCksum cksum old x- if ringHash == patHash && Ring.unsafeEqArray rb rh1 patArr- then Done <$> fextract s1- else return $ Partial $ SplitOnSeqKRLoop s1 ringHash rb rh1- Done b -> return $ Done b-- extract state =- let st =- case state of- SplitOnSeqEmpty s -> s- SplitOnSeqSingle s _ -> s- SplitOnSeqWord s _ _ -> s- SplitOnSeqWordLoop s _ -> s- SplitOnSeqKR s _ _ _ -> s- SplitOnSeqKRLoop s _ _ _ -> s- in fextract st---- | Like 'takeEndBySeq' but discards the matched sequence.------ /Pre-release/----{-# INLINE takeEndBySeq_ #-}-takeEndBySeq_ :: forall m a b. (MonadIO m, Storable a, Unbox a, Enum a, Eq a) =>- Array.Array a- -> Fold m a b- -> Fold m a b-takeEndBySeq_ patArr (Fold fstep finitial fextract) =- Fold step initial extract-- where-- patLen = Array.length patArr-- initial = do- res <- finitial- case res of- Partial acc- | patLen == 0 ->- -- XXX Should we match nothing or everything on empty- -- pattern?- -- Done <$> fextract acc- return $ Partial $ SplitOnSeqEmpty acc- | patLen == 1 -> do- pat <- liftIO $ Array.unsafeIndexIO 0 patArr- return $ Partial $ SplitOnSeqSingle acc pat- -- XXX Need to add tests for this case- | SIZE_OF(a) * patLen <= sizeOf (Proxy :: Proxy Word) ->- return $ Partial $ SplitOnSeqWord acc 0 0- | otherwise -> do- (rb, rhead) <- liftIO $ Ring.new patLen- return $ Partial $ SplitOnSeqKR acc 0 rb rhead- Done b -> return $ Done b-- -- Word pattern related- maxIndex = patLen - 1-- elemBits = SIZE_OF(a) * 8-- wordMask :: Word- wordMask = (1 `shiftL` (elemBits * patLen)) - 1-- elemMask :: Word- elemMask = (1 `shiftL` elemBits) - 1-- wordPat :: Word- wordPat = wordMask .&. Array.foldl' addToWord 0 patArr-- addToWord wd a = (wd `shiftL` elemBits) .|. fromIntegral (fromEnum a)-- -- For Rabin-Karp search- 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?- -- XXX Need to keep this cached across fold calls in foldMany- -- XXX We may need refold to inject the cached state instead of- -- initializing the state every time.- -- XXX Allocation of ring buffer should also be done once- patHash = Array.foldl' addCksum 0 patArr-- step (SplitOnSeqEmpty s) x = do- res <- fstep s x- case res of- Partial s1 -> return $ Partial $ SplitOnSeqEmpty s1- Done b -> return $ Done b- step (SplitOnSeqSingle s pat) x = do- if pat /= x- then do- res <- fstep s x- case res of- Partial s1 -> return $ Partial $ SplitOnSeqSingle s1 pat- Done b -> return $ Done b- else Done <$> fextract s- step (SplitOnSeqWord s idx wrd) x = do- let wrd1 = addToWord wrd x- if idx == maxIndex- then do- if wrd1 .&. wordMask == wordPat- then Done <$> fextract s- else return $ Partial $ SplitOnSeqWordLoop s wrd1- else return $ Partial $ SplitOnSeqWord s (idx + 1) wrd1- step (SplitOnSeqWordLoop s wrd) x = do- let wrd1 = addToWord wrd x- old = (wordMask .&. wrd)- `shiftR` (elemBits * (patLen - 1))- res <- fstep s (toEnum $ fromIntegral old)- case res of- Partial s1- | wrd1 .&. wordMask == wordPat ->- Done <$> fextract s1- | otherwise ->- return $ Partial $ SplitOnSeqWordLoop s1 wrd1- Done b -> return $ Done b- step (SplitOnSeqKR s idx rb rh) x = do- rh1 <- liftIO $ Ring.unsafeInsert rb rh x- if idx == maxIndex- then do- let fld = Ring.unsafeFoldRing (Ring.ringBound rb)- let !ringHash = fld addCksum 0 rb- if ringHash == patHash && Ring.unsafeEqArray rb rh1 patArr- then Done <$> fextract s- else return $ Partial $ SplitOnSeqKRLoop s ringHash rb rh1- else return $ Partial $ SplitOnSeqKR s (idx + 1) rb rh1- step (SplitOnSeqKRLoop s cksum rb rh) x = do- old <- liftIO $ peek rh- res <- fstep s old- case res of- Partial s1 -> do- rh1 <- liftIO $ Ring.unsafeInsert rb rh x- let ringHash = deltaCksum cksum old x- if ringHash == patHash && Ring.unsafeEqArray rb rh1 patArr- then Done <$> fextract s1- else return $ Partial $ SplitOnSeqKRLoop s1 ringHash rb rh1- Done b -> return $ Done b-- -- XXX extract should return backtrack count as well. If the fold- -- terminates early inside extract, we may still have buffered data- -- remaining which will be lost if we do not communicate that to the- -- driver.- extract state = do- let consumeWord s n wrd = do- if n == 0- then fextract s- else do- let old = elemMask .&. (wrd `shiftR` (elemBits * (n - 1)))- r <- fstep s (toEnum $ fromIntegral old)- case r of- Partial s1 -> consumeWord s1 (n - 1) wrd- Done b -> return b-- let consumeRing s n rb rh =- if n == 0- then fextract s- else do- old <- liftIO $ peek rh- let rh1 = Ring.advance rb rh- r <- fstep s old- case r of- Partial s1 -> consumeRing s1 (n - 1) rb rh1- Done b -> return b-- case state of- SplitOnSeqEmpty s -> fextract s- SplitOnSeqSingle s _ -> fextract s- SplitOnSeqWord s idx wrd -> consumeWord s idx wrd- SplitOnSeqWordLoop s wrd -> consumeWord s patLen wrd- SplitOnSeqKR s idx rb _ -> consumeRing s idx rb (Ring.startOf rb)- SplitOnSeqKRLoop s _ rb rh -> consumeRing s patLen rb rh----------------------------------------------------------------------------------- Distributing------------------------------------------------------------------------------------- | Distribute one copy of the stream to each fold and zip the results.------ @--- |-------Fold m a b--------|--- ---stream m a---| |---m (b,c)--- |-------Fold m a c--------|--- @------ Definition:------ >>> tee = Fold.teeWith (,)------ Example:------ >>> t = Fold.tee Fold.sum Fold.length--- >>> Stream.fold t (Stream.enumerateFromTo 1.0 100.0)--- (5050.0,100)----{-# INLINE tee #-}-tee :: Monad m => Fold m a b -> Fold m a c -> Fold m a (b,c)-tee = teeWith (,)---- XXX use "List" instead of "[]"?, use Array for output to scale it to a large--- number of consumers? For polymorphic case a vector could be helpful. For--- Storables we can use arrays. Will need separate APIs for those.------ | Distribute one copy of the stream to each fold and collect the results in--- a container.------ @------ |-------Fold m a b--------|--- ---stream m a---| |---m [b]--- |-------Fold m a b--------|--- | |--- ...--- @------ >>> Stream.fold (Fold.distribute [Fold.sum, Fold.length]) (Stream.enumerateFromTo 1 5)--- [15,5]------ >>> distribute = Prelude.foldr (Fold.teeWith (:)) (Fold.fromPure [])------ This is the consumer side dual of the producer side 'sequence' operation.------ Stops when all the folds stop.----{-# INLINE distribute #-}-distribute :: Monad m => [Fold m a b] -> Fold m a [b]-distribute = Prelude.foldr (teeWith (:)) (fromPure [])----------------------------------------------------------------------------------- Partitioning---------------------------------------------------------------------------------{-# INLINE partitionByMUsing #-}-partitionByMUsing :: Monad m =>- ( (x -> y -> (x, y))- -> Fold m (Either b c) x- -> Fold m (Either b c) y- -> Fold m (Either b c) (x, y)- )- -> (a -> m (Either b c))- -> Fold m b x- -> Fold m c y- -> Fold m a (x, y)-partitionByMUsing t f fld1 fld2 =- let l = lmap (fromLeft undefined) fld1 -- :: Fold m (Either b c) x- r = lmap (fromRight undefined) fld2 -- :: Fold m (Either b c) y- in lmapM f (t (,) (filter isLeft l) (filter isRight r))---- | Partition the input over two folds using an 'Either' partitioning--- predicate.------ @------ |-------Fold b x--------|--- -----stream m a --> (Either b c)----| |----(x,y)--- |-------Fold c y--------|--- @------ Example, send input to either fold randomly:------ >>> :set -package random--- >>> import System.Random (randomIO)--- >>> randomly a = randomIO >>= \x -> return $ if x then Left a else Right a--- >>> f = Fold.partitionByM randomly Fold.length Fold.length--- >>> Stream.fold f (Stream.enumerateFromTo 1 100)--- ...------ Example, send input to the two folds in a proportion of 2:1:------ >>> :{--- proportionately m n = do--- ref <- newIORef $ cycle $ concat [replicate m Left, replicate n Right]--- return $ \a -> do--- r <- readIORef ref--- writeIORef ref $ tail r--- return $ Prelude.head r a--- :}------ >>> :{--- main = do--- g <- proportionately 2 1--- let f = Fold.partitionByM g Fold.length Fold.length--- r <- Stream.fold f (Stream.enumerateFromTo (1 :: Int) 100)--- print r--- :}------ >>> main--- (67,33)--------- This is the consumer side dual of the producer side 'mergeBy' operation.------ When one fold is done, any input meant for it is ignored until the other--- fold is also done.------ Stops when both the folds stop.------ /See also: 'partitionByFstM' and 'partitionByMinM'./------ /Pre-release/-{-# INLINE partitionByM #-}-partitionByM :: Monad m- => (a -> m (Either b c)) -> Fold m b x -> Fold m c y -> Fold m a (x, y)-partitionByM = partitionByMUsing teeWith---- | Similar to 'partitionByM' but terminates when the first fold terminates.----{-# INLINE partitionByFstM #-}-partitionByFstM :: Monad m- => (a -> m (Either b c)) -> Fold m b x -> Fold m c y -> Fold m a (x, y)-partitionByFstM = partitionByMUsing teeWithFst---- | Similar to 'partitionByM' but terminates when any fold terminates.----{-# INLINE partitionByMinM #-}-partitionByMinM :: Monad m =>- (a -> m (Either b c)) -> Fold m b x -> Fold m c y -> Fold m a (x, y)-partitionByMinM = partitionByMUsing teeWithMin---- Note: we could use (a -> Bool) instead of (a -> Either b c), but the latter--- makes the signature clearer as to which case belongs to which fold.--- XXX need to check the performance in both cases.---- | Same as 'partitionByM' but with a pure partition function.------ Example, count even and odd numbers in a stream:------ >>> :{--- let f = Fold.partitionBy (\n -> if even n then Left n else Right n)--- (fmap (("Even " ++) . show) Fold.length)--- (fmap (("Odd " ++) . show) Fold.length)--- in Stream.fold f (Stream.enumerateFromTo 1 100)--- :}--- ("Even 50","Odd 50")------ /Pre-release/-{-# INLINE partitionBy #-}-partitionBy :: Monad m- => (a -> Either b c) -> Fold m b x -> Fold m c y -> Fold m a (x, y)-partitionBy f = partitionByM (return . f)---- | Compose two folds such that the combined fold accepts a stream of 'Either'--- and routes the 'Left' values to the first fold and 'Right' values to the--- second fold.------ Definition:------ >>> partition = Fold.partitionBy id----{-# INLINE partition #-}-partition :: Monad m- => Fold m b x -> Fold m c y -> Fold m (Either b c) (x, y)-partition = partitionBy id--{---- | Send one item to each fold in a round-robin fashion. This is the consumer--- side dual of producer side 'mergeN' operation.------ partitionN :: Monad m => [Fold m a b] -> Fold m a [b]--- partitionN fs = Fold step begin done--}----------------------------------------------------------------------------------- Unzipping---------------------------------------------------------------------------------{-# INLINE unzipWithMUsing #-}-unzipWithMUsing :: Monad m =>- ( (x -> y -> (x, y))- -> Fold m (b, c) x- -> Fold m (b, c) y- -> Fold m (b, c) (x, y)- )- -> (a -> m (b, c))- -> Fold m b x- -> Fold m c y- -> Fold m a (x, y)-unzipWithMUsing t f fld1 fld2 =- let f1 = lmap fst fld1 -- :: Fold m (b, c) b- f2 = lmap snd fld2 -- :: Fold m (b, c) c- in lmapM f (t (,) f1 f2)---- | Like 'unzipWith' but with a monadic splitter function.------ Definition:------ >>> unzipWithM k f1 f2 = Fold.lmapM k (Fold.unzip f1 f2)------ /Pre-release/-{-# INLINE unzipWithM #-}-unzipWithM :: Monad m- => (a -> m (b,c)) -> Fold m b x -> Fold m c y -> Fold m a (x,y)-unzipWithM = unzipWithMUsing teeWith---- | Similar to 'unzipWithM' but terminates when the first fold terminates.----{-# INLINE unzipWithFstM #-}-unzipWithFstM :: Monad m =>- (a -> m (b, c)) -> Fold m b x -> Fold m c y -> Fold m a (x, y)-unzipWithFstM = unzipWithMUsing teeWithFst---- | Similar to 'unzipWithM' but terminates when any fold terminates.----{-# INLINE unzipWithMinM #-}-unzipWithMinM :: Monad m =>- (a -> m (b,c)) -> Fold m b x -> Fold m c y -> Fold m a (x,y)-unzipWithMinM = unzipWithMUsing teeWithMin---- | Split elements in the input stream into two parts using a pure splitter--- function, direct each part to a different fold and zip the results.------ Definitions:------ >>> unzipWith f = Fold.unzipWithM (return . f)--- >>> unzipWith f fld1 fld2 = Fold.lmap f (Fold.unzip fld1 fld2)------ This fold terminates when both the input folds terminate.------ /Pre-release/-{-# INLINE unzipWith #-}-unzipWith :: Monad m- => (a -> (b,c)) -> Fold m b x -> Fold m c y -> Fold m a (x,y)-unzipWith f = unzipWithM (return . f)---- | Send the elements of tuples in a stream of tuples through two different--- folds.------ @------ |-------Fold m a x--------|--- ---------stream of (a,b)--| |----m (x,y)--- |-------Fold m b y--------|------ @------ Definition:------ >>> unzip = Fold.unzipWith id------ This is the consumer side dual of the producer side 'zip' operation.----{-# INLINE unzip #-}-unzip :: Monad m => Fold m a x -> Fold m b y -> Fold m (a,b) (x,y)-unzip = unzipWith id----------------------------------------------------------------------------------- Combining streams and folds - Zipping----------------------------------------------------------------------------------- XXX These can be implemented using the fold scan, using the stream as a--- state.--- XXX Stream Skip state cannot be efficiently handled in folds but can be--- handled in parsers using the Continue facility. See zipWithM in the Parser--- module.------ cmpBy, eqBy, isPrefixOf, isSubsequenceOf etc can be implemented using--- zipStream.---- | Zip a stream with the input of a fold using the supplied function.------ /Unimplemented/----{-# INLINE zipStreamWithM #-}-zipStreamWithM :: -- Monad m =>- (a -> b -> m c) -> Stream m a -> Fold m c x -> Fold m b x-zipStreamWithM = undefined---- | Zip a stream with the input of a fold.------ >>> zip = Fold.zipStreamWithM (curry return)------ /Unimplemented/----{-# INLINE zipStream #-}-zipStream :: Monad m => Stream m a -> Fold m (a, b) x -> Fold m b x-zipStream = zipStreamWithM (curry return)---- | Pair each element of a fold input with its index, starting from index 0.----{-# INLINE indexingWith #-}-indexingWith :: Monad m => Int -> (Int -> Int) -> Fold m a (Maybe (Int, a))-indexingWith i f = fmap toMaybe $ foldl' step initial-- where-- initial = Nothing'-- step Nothing' a = Just' (i, a)- step (Just' (n, _)) a = Just' (f n, a)---- |--- >>> indexing = Fold.indexingWith 0 (+ 1)----{-# INLINE indexing #-}-indexing :: Monad m => Fold m a (Maybe (Int, a))-indexing = indexingWith 0 (+ 1)---- |--- >>> indexingRev n = Fold.indexingWith n (subtract 1)----{-# INLINE indexingRev #-}-indexingRev :: Monad m => Int -> Fold m a (Maybe (Int, a))-indexingRev n = indexingWith n (subtract 1)---- | Pair each element of a fold input with its index, starting from index 0.------ >>> indexed = Fold.scanMaybe Fold.indexing----{-# INLINE indexed #-}-indexed :: Monad m => Fold m (Int, a) b -> Fold m a b-indexed = scanMaybe indexing---- | Change the predicate function of a Fold from @a -> b@ to accept an--- additional state input @(s, a) -> b@. Convenient to filter with an--- addiitonal index or time input.------ >>> filterWithIndex = Fold.with Fold.indexed Fold.filter------ @--- filterWithAbsTime = with timestamped filter--- filterWithRelTime = with timeIndexed filter--- @------ /Pre-release/-{-# INLINE with #-}-with ::- (Fold m (s, a) b -> Fold m a b)- -> (((s, a) -> c) -> Fold m (s, a) b -> Fold m (s, a) b)- -> (((s, a) -> c) -> Fold m a b -> Fold m a b)-with f comb g = f . comb g . lmap snd---- XXX Implement as a filter--- sampleFromthen :: Monad m => Int -> Int -> Fold m a (Maybe a)---- | @sampleFromthen offset stride@ samples the element at @offset@ index and--- then every element at strides of @stride@.----{-# INLINE sampleFromthen #-}-sampleFromthen :: Monad m => Int -> Int -> Fold m a b -> Fold m a b-sampleFromthen offset size =- with indexed filter (\(i, _) -> (i + offset) `mod` size == 0)----------------------------------------------------------------------------------- Nesting----------------------------------------------------------------------------------- | @concatSequence f t@ applies folds from stream @t@ sequentially and--- collects the results using the fold @f@.------ /Unimplemented/----{-# INLINE concatSequence #-}-concatSequence ::- -- IsStream t =>- Fold m b c -> t (Fold m a b) -> Fold m a c-concatSequence _f _p = undefined---- | Group the input stream into groups of elements between @low@ and @high@.--- Collection starts in chunks of @low@ and then keeps doubling until we reach--- @high@. Each chunk is folded using the provided fold function.------ This could be useful, for example, when we are folding a stream of unknown--- size to a stream of arrays and we want to minimize the number of--- allocations.------ NOTE: this would be an application of "many" using a terminating fold.------ /Unimplemented/----{-# INLINE chunksBetween #-}-chunksBetween :: -- Monad m =>- Int -> Int -> Fold m a b -> Fold m b c -> Fold m a c-chunksBetween _low _high _f1 _f2 = undefined---- | 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.Data.Array" instead.------ >>> toStream = fmap Stream.fromList Fold.toList------ /Pre-release/-{-# INLINE toStream #-}-toStream :: (Monad m, Monad n) => Fold m a (Stream n a)-toStream = fmap StreamD.fromList toList---- 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.------ >>> toStreamRev = fmap Stream.fromList Fold.toListRev------ /Warning!/ working on large streams accumulated as buffers in memory could--- be very inefficient, consider using "Streamly.Data.Array" instead.------ /Pre-release/---- xn : ... : x2 : x1 : []-{-# INLINE toStreamRev #-}-toStreamRev :: (Monad m, Monad n) => Fold m a (Stream n a)-toStreamRev = fmap StreamD.fromList toListRev---- XXX This does not fuse. It contains a recursive step function. We will need--- a Skip input constructor in the fold type to make it fuse.------ | Unfold and flatten the input stream of a fold.------ @--- Stream.fold (unfoldMany u f) = Stream.fold f . Stream.unfoldMany u--- @------ /Pre-release/-{-# INLINE unfoldMany #-}-unfoldMany :: Monad m => Unfold m a b -> Fold m b c -> Fold m a c-unfoldMany (Unfold ustep inject) (Fold fstep initial extract) =- Fold consume initial extract-- where-- {-# INLINE produce #-}- produce fs us = do- ures <- ustep us- case ures of- StreamD.Yield b us1 -> do- fres <- fstep fs b- case fres of- Partial fs1 -> produce fs1 us1- -- XXX What to do with the remaining stream?- Done c -> return $ Done c- StreamD.Skip us1 -> produce fs us1- StreamD.Stop -> return $ Partial fs-- {-# INLINE_LATE consume #-}- consume s a = inject a >>= produce s---- | Get the bottom most @n@ elements using the supplied comparison function.----{-# INLINE bottomBy #-}-bottomBy :: (MonadIO m, Unbox a) =>- (a -> a -> Ordering)- -> Int- -> Fold m a (MutArray a)-bottomBy cmp n = Fold step initial extract-- where-- initial = do- arr <- MA.newPinned n- if n <= 0- then return $ Done arr- else return $ Partial (arr, 0)-- step (arr, i) x =- if i < n- then do- arr' <- MA.snoc arr x- MA.bubble cmp arr'- return $ Partial (arr', i + 1)- else do- x1 <- MA.getIndexUnsafe (i - 1) arr- case x `cmp` x1 of- LT -> do- MA.putIndexUnsafe (i - 1) arr x- MA.bubble cmp arr- return $ Partial (arr, i)- _ -> return $ Partial (arr, i)-- extract = return . fst---- | Get the top @n@ elements using the supplied comparison function.------ To get bottom n elements instead:------ >>> bottomBy cmp = Fold.topBy (flip cmp)------ Example:------ >>> stream = Stream.fromList [2::Int,7,9,3,1,5,6,11,17]--- >>> Stream.fold (Fold.topBy compare 3) stream >>= MutArray.toList--- [17,11,9]------ /Pre-release/----{-# INLINE topBy #-}-topBy :: (MonadIO m, Unbox a) =>- (a -> a -> Ordering)- -> Int- -> Fold m a (MutArray a)-topBy cmp = bottomBy (flip cmp)---- | Fold the input stream to top n elements.------ Definition:------ >>> top = Fold.topBy compare------ >>> stream = Stream.fromList [2::Int,7,9,3,1,5,6,11,17]--- >>> Stream.fold (Fold.top 3) stream >>= MutArray.toList--- [17,11,9]------ /Pre-release/-{-# INLINE top #-}-top :: (MonadIO m, Unbox a, Ord a) => Int -> Fold m a (MutArray a)-top = bottomBy $ flip compare---- | Fold the input stream to bottom n elements.------ Definition:------ >>> bottom = Fold.bottomBy compare------ >>> stream = Stream.fromList [2::Int,7,9,3,1,5,6,11,17]--- >>> Stream.fold (Fold.bottom 3) stream >>= MutArray.toList--- [1,2,3]------ /Pre-release/-{-# INLINE bottom #-}-bottom :: (MonadIO m, Unbox a, Ord a) => Int -> Fold m a (MutArray a)-bottom = bottomBy compare----------------------------------------------------------------------------------- Interspersed parsing---------------------------------------------------------------------------------data IntersperseQState fs ps =- IntersperseQUnquoted !fs !ps- | IntersperseQQuoted !fs !ps- | IntersperseQQuotedEsc !fs !ps---- Useful for parsing CSV with quoting and escaping-{-# INLINE intersperseWithQuotes #-}-intersperseWithQuotes :: (Monad m, Eq a) =>- a -> a -> a -> Fold m a b -> Fold m b c -> Fold m a c-intersperseWithQuotes- quote- esc- separator- (Fold stepL initialL extractL)- (Fold stepR initialR extractR) = Fold step initial extract-- where-- errMsg p status =- error $ "intersperseWithQuotes: " ++ p ++ " parsing fold cannot "- ++ status ++ " without input"-- {-# INLINE initL #-}- initL mkState = do- resL <- initialL- case resL of- Partial sL ->- return $ Partial $ mkState sL- Done _ ->- errMsg "content" "succeed"-- initial = do- res <- initialR- case res of- Partial sR -> initL (IntersperseQUnquoted sR)- Done b -> return $ Done b-- {-# INLINE collect #-}- collect nextS sR b = do- res <- stepR sR b- case res of- Partial s ->- initL (nextS s)- Done c -> return (Done c)-- {-# INLINE process #-}- process a sL sR nextState = do- r <- stepL sL a- case r of- Partial s -> return $ Partial (nextState sR s)- Done b -> collect nextState sR b-- {-# INLINE processQuoted #-}- processQuoted a sL sR nextState = do- r <- stepL sL a- case r of- Partial s -> return $ Partial (nextState sR s)- Done _ -> error "Collecting fold finished inside quote"-- step (IntersperseQUnquoted sR sL) a- | a == separator = do- b <- extractL sL- collect IntersperseQUnquoted sR b- | a == quote = processQuoted a sL sR IntersperseQQuoted- | otherwise = process a sL sR IntersperseQUnquoted-- step (IntersperseQQuoted sR sL) a- | a == esc = processQuoted a sL sR IntersperseQQuotedEsc- | a == quote = process a sL sR IntersperseQUnquoted- | otherwise = processQuoted a sL sR IntersperseQQuoted-- step (IntersperseQQuotedEsc sR sL) a =- processQuoted a sL sR IntersperseQQuoted-- extract (IntersperseQUnquoted sR _) = extractR sR- extract (IntersperseQQuoted _ _) =- error "intersperseWithQuotes: finished inside quote"- extract (IntersperseQQuotedEsc _ _) =- error "intersperseWithQuotes: finished inside quote, at escape char"+{-# OPTIONS_GHC -Wno-deprecations #-}+-- |+-- Module : Streamly.Internal.Data.Fold+-- Copyright : (c) 2019 Composewell Technologies+-- (c) 2013 Gabriel Gonzalez+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- See "Streamly.Data.Fold" for an overview and+-- "Streamly.Internal.Data.Fold.Type" for design notes.++module Streamly.Internal.Data.Fold+ (+ -- * Imports+ -- $setup++ module Streamly.Internal.Data.Fold.Type+ , module Streamly.Internal.Data.Fold.Tee+ , module Streamly.Internal.Data.Fold.Combinators+ , module Streamly.Internal.Data.Fold.Container+ , module Streamly.Internal.Data.Fold.Window+ , module Streamly.Internal.Data.Fold.Exception+ )+where++import Streamly.Internal.Data.Fold.Combinators+import Streamly.Internal.Data.Fold.Container+import Streamly.Internal.Data.Fold.Exception+import Streamly.Internal.Data.Fold.Tee+import Streamly.Internal.Data.Fold.Type+import Streamly.Internal.Data.Fold.Window++#include "DocTestDataFold.hs"
src/Streamly/Internal/Data/Fold/Chunked.hs view
@@ -1,3 +1,6 @@+{-# OPTIONS_GHC -Wno-deprecations #-}+{-# OPTIONS_GHC -Wno-incomplete-patterns #-}+ -- | -- Module : Streamly.Internal.Data.Fold.Chunked -- Copyright : (c) 2021 Composewell Technologies@@ -6,7 +9,7 @@ -- Stability : experimental -- Portability : GHC ----- Use "Streamly.Data.Parser.Chunked" instead.+-- Use "Streamly.Data.Parser" instead. -- -- Fold a stream of foreign arrays. @Fold m a b@ in this module works -- on a stream of "Array a" and produces an output of type @b@.@@ -19,18 +22,19 @@ -- folds in Data.Fold to correctly work on an array stream as if it is an -- element stream. For example: ----- >>> import qualified Streamly.Data.Fold as Fold--- >>> import qualified Streamly.Internal.Data.Stream.Chunked as ArrayStream--- >>> import qualified Streamly.Internal.Data.Fold.Chunked as ChunkFold--- >>> import qualified Streamly.Data.Stream as Stream--- >>> import qualified Streamly.Data.StreamK as StreamK+-- >> import qualified Streamly.Data.Fold as Fold+-- >> import qualified Streamly.Internal.Data.Array.Stream as ArrayStream+-- >> import qualified Streamly.Internal.Data.Fold.Chunked as ChunkFold+-- >> import qualified Streamly.Data.Stream as Stream+-- >> import qualified Streamly.Data.StreamK as StreamK ----- >>> f = ChunkFold.fromFold (Fold.take 7 Fold.toList)--- >>> s = Stream.chunksOf 5 $ Stream.fromList "hello world"--- >>> ArrayStream.runArrayFold f (StreamK.fromStream s)+-- >> f = ChunkFold.fromFold (Fold.take 7 Fold.toList)+-- >> s = Array.chunksOf 5 $ Stream.fromList "hello world"+-- >> ArrayStream.runArrayFold f (StreamK.fromStream s) -- Right "hello w" -- module Streamly.Internal.Data.Fold.Chunked+ {-# DEPRECATED "Please use Streamly.Data.Parser instead." #-} ( ChunkFold (..) @@ -58,22 +62,22 @@ #include "ArrayMacros.h" +#if !MIN_VERSION_base(4,18,0) import Control.Applicative (liftA2)+#endif import Control.Exception (assert) import Control.Monad.IO.Class (MonadIO(..)) import Data.Bifunctor (first) import Data.Proxy (Proxy(..))-import Streamly.Internal.Data.Unboxed (peekWith, sizeOf, Unbox)+import Streamly.Internal.Data.Unbox (Unbox(..)) import GHC.Types (SPEC(..))-import Streamly.Internal.Data.Array.Mut.Type (touch)-import Streamly.Internal.Data.Array.Type (Array(..))-import Streamly.Internal.Data.Parser.ParserD (Initial(..), Step(..))+import Streamly.Internal.Data.Array (Array(..))+import Streamly.Internal.Data.Parser (Initial(..), Step(..), Final(..)) import Streamly.Internal.Data.Tuple.Strict (Tuple'(..)) -import qualified Streamly.Internal.Data.Array as Array+import qualified Streamly.Internal.Data.Array.Type as Array import qualified Streamly.Internal.Data.Fold as Fold-import qualified Streamly.Internal.Data.Parser.ParserD as ParserD-import qualified Streamly.Internal.Data.Parser.ParserD.Type as ParserD+import qualified Streamly.Internal.Data.Parser as ParserD import qualified Streamly.Internal.Data.Parser as Parser import Prelude hiding (concatMap, take)@@ -102,8 +106,8 @@ {-# INLINE fromFold #-} fromFold :: forall m a b. (MonadIO m, Unbox a) => Fold.Fold m a b -> ChunkFold m a b-fromFold (Fold.Fold fstep finitial fextract) =- ChunkFold (ParserD.Parser step initial (fmap (Done 0) . fextract))+fromFold (Fold.Fold fstep finitial _ ffinal) =+ ChunkFold (ParserD.Parser step initial extract) where @@ -123,7 +127,7 @@ assert (cur == end) (return ()) return $ Partial 0 fs goArray !_ !cur !fs = do- x <- liftIO $ peekWith contents cur+ x <- liftIO $ peekAt cur contents res <- fstep fs x let elemSize = SIZE_OF(a) next = INDEX_NEXT(cur,a)@@ -133,6 +137,8 @@ Fold.Partial fs1 -> goArray SPEC next fs1 + extract = fmap (FDone 0) . ffinal+ -- | Convert an element 'ParserD.Parser' into an array stream fold. If the -- parser fails the fold would throw an exception. --@@ -160,8 +166,7 @@ else return $ st (arrRem + n) fs1 goArray !_ !cur !fs = do- x <- liftIO $ peekWith contents cur- liftIO $ touch contents+ x <- liftIO $ peekAt cur contents res <- step1 fs x let elemSize = SIZE_OF(a) next = INDEX_NEXT(cur,a)@@ -173,7 +178,7 @@ partial arrRem cur next elemSize Partial n fs1 ParserD.Continue n fs1 -> do partial arrRem cur next elemSize Continue n fs1- Error err -> return $ Error err+ SError err -> return $ SError err -- | Convert an element 'Parser.Parser' into an array stream fold. If the -- parser fails the fold would throw an exception.@@ -315,8 +320,8 @@ iextract s = do r <- extract1 s return $ case r of- Done _ b -> IDone b- Error err -> IError err+ FDone _ b -> IDone b+ FError err -> IError err _ -> error "Bug: ChunkFold take invalid state in initial" initial = do@@ -338,10 +343,9 @@ -- i2 == i1 == j == 0 r <- extract1 s return $ case r of- Error err -> Error err- Done n1 b -> Done n1 b- Continue n1 s1 -> Continue n1 (Tuple' i2 s1)- Partial _ _ -> error "Partial in extract"+ FError err -> SError err+ FDone n1 b -> Done n1 b+ FContinue n1 s1 -> Continue n1 (Tuple' i2 s1) -- Tuple' (how many more items to take) (fold state) step (Tuple' i r) arr = do@@ -354,7 +358,7 @@ Partial j s -> partial i1 Partial j s Continue j s -> partial i1 Continue j s Done j b -> return $ Done j b- Error err -> return $ Error err+ SError err -> return $ SError err else do let !(Array contents start _) = arr end = INDEX_OF(start,i,a)@@ -364,14 +368,14 @@ res <- step1 r arr1 case res of Partial 0 s ->- ParserD.bimapOverrideCount+ ParserD.bimapMorphOverrideCount remaining (Tuple' 0) id <$> extract1 s Partial j s -> return $ Partial (remaining + j) (Tuple' j s) Continue 0 s ->- ParserD.bimapOverrideCount+ ParserD.bimapMorphOverrideCount remaining (Tuple' 0) id <$> extract1 s Continue j s -> return $ Continue (remaining + j) (Tuple' j s) Done j b -> return $ Done (remaining + j) b- Error err -> return $ Error err+ SError err -> return $ SError err extract (Tuple' i r) = first (Tuple' i) <$> extract1 r
+ src/Streamly/Internal/Data/Fold/Combinators.hs view
@@ -0,0 +1,2383 @@+{-# LANGUAGE CPP #-}+-- |+-- Module : Streamly.Internal.Data.Fold.Combinators+-- Copyright : (c) 2019 Composewell Technologies+-- (c) 2013 Gabriel Gonzalez+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- See "Streamly.Data.Fold" for an overview and+-- "Streamly.Internal.Data.Fold.Type" for design notes.++module Streamly.Internal.Data.Fold.Combinators+ (+ -- * Mappers+ -- | Monadic functions useful with mapM/lmapM on folds or streams.+ tracing+ , trace++ -- * Folds++ -- ** Accumulators+ -- *** Semigroups and Monoids+ , sconcat+ , mconcat+ , foldMap+ , foldMapM++ -- *** Reducers+ , drainMapM+ , the+ , mean+ , rollingHash+ , Scanl.defaultSalt+ , rollingHashWithSalt+ , rollingHashFirstN+ -- , rollingHashLastN++ -- *** Saturating Reducers+ -- | 'product' terminates if it becomes 0. Other folds can theoretically+ -- saturate on bounded types, and therefore terminate, however, they will+ -- run forever on unbounded types like Integer/Double.+ , sum+ , product+ , maximumBy+ , maximum+ , minimumBy+ , minimum+ , rangeBy+ , range++ -- *** Collectors+ -- | Avoid using these folds in scalable or performance critical+ -- applications, they buffer all the input in GC memory which can be+ -- detrimental to performance if the input is large.+ , toStream+ , toStreamRev+ , topBy+ , top+ , bottomBy+ , bottom++ -- *** Scanners+ -- | Stateful transformation of the elements. Useful in combination with+ -- the 'scanMaybe' combinator. For scanners the result of the fold is+ -- usually a transformation of the current element rather than an+ -- aggregation of all elements till now.+ -- , nthLast -- using RingArray array+ , rollingMap+ , rollingMapM++ -- *** Filters++ -- XXX deprecate these in favor of corresponding scans++ -- | Useful in combination with the 'scanMaybe' combinator.+ , deleteBy+ , uniqBy+ , uniq+ , repeated+ , findIndices+ , elemIndices++ -- *** Singleton folds+ -- | Folds that terminate after consuming exactly one input element. All+ -- these can be implemented in terms of the 'maybe' fold.+ , one+ , null -- XXX not very useful and could be problematic, remove it?+ , satisfy+ , maybe++ -- *** Multi folds+ -- | Terminate after consuming one or more elements.+ , drainN+ -- , lastN+ -- , (!!)+ , genericIndex+ , index+ , findM+ , find+ , lookup+ , findIndex+ , elemIndex+ , elem+ , notElem+ , all+ , any+ , and+ , or++ -- ** Trimmers+ -- | Useful in combination with the 'scanMaybe' combinator.+ , takingEndByM+ , takingEndBy+ , takingEndByM_+ , takingEndBy_+ , droppingWhileM+ , droppingWhile+ , prune++ -- * Running A Fold+ , drive+ -- , breakStream++ -- * Building Incrementally+ , addStream++ -- * Combinators+ -- ** Utilities+ , with++ -- ** Sliding Window+ , slide2++ -- ** Scanning Input+ , pipe+ , indexed++ -- ** Zipping Input+ , zipStreamWithM+ , zipStream++ -- ** Filtering Input+ , mapMaybeM+ , mapMaybe+ , sampleFromthen++ {-+ -- ** Insertion+ -- | Insertion adds more elements to the stream.++ , insertBy+ , intersperseM++ -- ** Reordering+ , reverse+ -}++ -- ** Trimming++ -- By elements+ , takeEndBySeq+ , takeEndBySeq_+ {-+ , drop+ , dropWhile+ , dropWhileM+ -}++ -- ** Serial Append+ -- , tail+ -- , init+ , splitAt -- spanN+ -- , splitIn -- sessionN++ -- ** Parallel Distribution+ , tee+ , distribute+ , distributeScan+ -- , distributeFst+ -- , distributeMin++ -- ** Unzipping+ , unzip+ -- These two can be expressed using lmap/lmapM and unzip+ , unzipWith+ , unzipWithM+ , unzipWithFstM+ , unzipWithMinM++ -- ** Partitioning+ , partitionByM+ , partitionByFstM+ , partitionByMinM+ , partitionBy+ , partition++ -- ** Splitting+ , chunksBetween+ , intersperseWithQuotes++ -- ** Nesting+ , unfoldMany+ , concatSequence++ -- * Deprecated+ , drainBy+ , head+ , sequence+ , mapM+ , variance+ , stdDev+ , indexingWith+ , indexing+ , indexingRev+ )+where++#include "inline.hs"+#include "ArrayMacros.h"++import Control.Monad (void)+import Control.Monad.IO.Class (MonadIO(..))+import Data.Bifunctor (first)+import Data.Bits (shiftL, shiftR, (.|.), (.&.))+import Data.Either (isLeft, isRight, fromLeft, fromRight)+import Data.Int (Int64)+import Data.Proxy (Proxy(..))+import Data.Word (Word32)+import Streamly.Internal.Data.Array.Type (Array(..))+import Streamly.Internal.Data.Scanl.Type (Scanl(..))+import Streamly.Internal.Data.Unbox (Unbox(..))+import Streamly.Internal.Data.MutArray.Type (MutArray(..))+import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)+import Streamly.Internal.Data.Pipe.Type (Pipe (..))+import Streamly.Internal.Data.RingArray (RingArray(..))+-- import Streamly.Internal.Data.Scan (Scan (..))+import Streamly.Internal.Data.Stream.Type (Stream)+import Streamly.Internal.Data.Tuple.Strict (Tuple'(..), Tuple3'(..))+import Streamly.Internal.Data.Unfold.Type (Unfold(..))++import qualified Prelude+import qualified Streamly.Internal.Data.MutArray.Type as MA+import qualified Streamly.Internal.Data.Array.Type as Array+import qualified Streamly.Internal.Data.Pipe.Type as Pipe+import qualified Streamly.Internal.Data.RingArray as RingArray+import qualified Streamly.Internal.Data.Scanl.Combinators as Scanl+import qualified Streamly.Internal.Data.Scanl.Type as Scanl+import qualified Streamly.Internal.Data.Stream.Type as StreamD++import Prelude hiding+ ( Foldable(..), filter, drop, dropWhile, take, takeWhile, zipWith+ , map, mapM_, sequence, all, any+ , notElem, head, last, tail+ , reverse, iterate, init, and, or, lookup, (!!)+ , scanl, scanl1, replicate, concatMap, mconcat, unzip+ , span, splitAt, break, mapM, zip, maybe)+import Streamly.Internal.Data.Fold.Type++#include "DocTestDataFold.hs"++------------------------------------------------------------------------------+-- Running+------------------------------------------------------------------------------++-- | Drive a fold using the supplied 'Stream', reducing the resulting+-- expression strictly at each step.+--+-- Definition:+--+-- >>> drive = flip Stream.fold+--+-- Example:+--+-- >>> Fold.drive (Stream.enumerateFromTo 1 100) Fold.sum+-- 5050+--+{-# INLINE drive #-}+drive :: Monad m => Stream m a -> Fold m a b -> m b+drive = flip StreamD.fold++{-+-- | Like 'drive' but also returns the remaining stream. The resulting stream+-- would be 'Stream.nil' if the stream finished before the fold.+--+-- Definition:+--+-- >>> breakStream = flip Stream.foldBreak+--+-- /CPS/+--+{-# INLINE breakStreamK #-}+breakStreamK :: Monad m => StreamK m a -> Fold m a b -> m (b, StreamK m a)+breakStreamK strm fl = fmap f $ K.foldBreak fl (Stream.toStreamK strm)++ where++ f (b, str) = (b, Stream.fromStreamK str)+-}++-- | Append a stream to a fold to build the fold accumulator incrementally. We+-- can repeatedly call 'addStream' on the same fold to continue building the+-- fold and finally use 'drive' to finish the fold and extract the result. Also+-- see the 'Streamly.Data.Fold.addOne' operation which is a singleton version+-- of 'addStream'.+--+-- Definitions:+--+-- >>> addStream stream = Fold.drive stream . Fold.duplicate+--+-- Example, build a list incrementally:+--+-- >>> :{+-- pure (Fold.toList :: Fold IO Int [Int])+-- >>= Fold.addOne 1+-- >>= Fold.addStream (Stream.enumerateFromTo 2 4)+-- >>= Fold.drive Stream.nil+-- >>= print+-- :}+-- [1,2,3,4]+--+-- This can be used as an O(n) list append compared to the O(n^2) @++@ when+-- used for incrementally building a list.+--+-- Example, build a stream incrementally:+--+-- >>> :{+-- pure (Fold.toStream :: Fold IO Int (Stream Identity Int))+-- >>= Fold.addOne 1+-- >>= Fold.addStream (Stream.enumerateFromTo 2 4)+-- >>= Fold.drive Stream.nil+-- >>= print+-- :}+-- fromList [1,2,3,4]+--+-- This can be used as an O(n) stream append compared to the O(n^2) @<>@ when+-- used for incrementally building a stream.+--+-- Example, build an array incrementally:+--+-- >>> :{+-- pure (Array.create :: Fold IO Int (Array Int))+-- >>= Fold.addOne 1+-- >>= Fold.addStream (Stream.enumerateFromTo 2 4)+-- >>= Fold.drive Stream.nil+-- >>= print+-- :}+-- fromList [1,2,3,4]+--+-- Example, build an array stream incrementally:+--+-- >>> :{+-- let f :: Fold IO Int (Stream Identity (Array Int))+-- f = Fold.groupsOf 2 (Array.createOf 3) Fold.toStream+-- in pure f+-- >>= Fold.addOne 1+-- >>= Fold.addStream (Stream.enumerateFromTo 2 4)+-- >>= Fold.drive Stream.nil+-- >>= print+-- :}+-- fromList [fromList [1,2],fromList [3,4]]+--+addStream :: Monad m => Stream m a -> Fold m a b -> m (Fold m a b)+addStream stream = drive stream . duplicate++------------------------------------------------------------------------------+-- Transformations on fold inputs+------------------------------------------------------------------------------++-- | Flatten the monadic output of a fold to pure output.+--+{-# DEPRECATED sequence "Use \"rmapM id\" instead" #-}+{-# INLINE sequence #-}+sequence :: Monad m => Fold m a (m b) -> Fold m a b+sequence = rmapM id++-- | Map a monadic function on the output of a fold.+--+{-# DEPRECATED mapM "Use rmapM instead" #-}+{-# INLINE mapM #-}+mapM :: Monad m => (b -> m c) -> Fold m a b -> Fold m a c+mapM = rmapM++-- |+-- >>> mapMaybeM f = Fold.lmapM f . Fold.catMaybes+--+{-# INLINE mapMaybeM #-}+mapMaybeM :: Monad m => (a -> m (Maybe b)) -> Fold m b r -> Fold m a r+mapMaybeM f = lmapM f . catMaybes++-- | @mapMaybe f fold@ maps a 'Maybe' returning function @f@ on the input of+-- the fold, filters out 'Nothing' elements, and return the values extracted+-- from 'Just'.+--+-- >>> mapMaybe f = Fold.lmap f . Fold.catMaybes+-- >>> mapMaybe f = Fold.mapMaybeM (return . f)+--+-- >>> f x = if even x then Just x else Nothing+-- >>> fld = Fold.mapMaybe f Fold.toList+-- >>> Stream.fold fld (Stream.enumerateFromTo 1 10)+-- [2,4,6,8,10]+--+{-# INLINE mapMaybe #-}+mapMaybe :: Monad m => (a -> Maybe b) -> Fold m b r -> Fold m a r+mapMaybe f = lmap f . catMaybes++------------------------------------------------------------------------------+-- Transformations on fold inputs+------------------------------------------------------------------------------++-- | Apply a monadic function on the input and return the input.+--+-- >>> Stream.fold (Fold.lmapM (Fold.tracing print) Fold.drain) $ (Stream.enumerateFromTo (1 :: Int) 2)+-- 1+-- 2+--+-- /Pre-release/+--+{-# INLINE tracing #-}+tracing :: Monad m => (a -> m b) -> (a -> m a)+tracing f x = void (f x) >> return x++-- | Apply a monadic function to each element flowing through and discard the+-- results.+--+-- >>> Stream.fold (Fold.trace print Fold.drain) $ (Stream.enumerateFromTo (1 :: Int) 2)+-- 1+-- 2+--+-- >>> trace f = Fold.lmapM (Fold.tracing f)+--+-- /Pre-release/+{-# INLINE trace #-}+trace :: Monad m => (a -> m b) -> Fold m a r -> Fold m a r+trace f = lmapM (tracing f)++-- | Attach a 'Pipe' on the input of a 'Fold'.+--+-- /Pre-release/+{-# INLINE pipe #-}+pipe :: Monad m => Pipe m a b -> Fold m b c -> Fold m a c+pipe (Pipe consume produce pinitial) (Fold fstep finitial fextract ffinal) =+ Fold step initial extract final++ where++ initial = first (Tuple' pinitial) <$> finitial++ step (Tuple' cs fs) x = do+ r <- consume cs x+ go fs r++ where++ -- XXX use SPEC?+ go acc (Pipe.YieldC cs1 b) = do+ acc1 <- fstep acc b+ return+ $ case acc1 of+ Partial s -> Partial $ Tuple' cs1 s+ Done b1 -> Done b1+ -- XXX this case is recursive may cause fusion issues.+ -- To remove recursion we will need a produce mode in folds which makes+ -- it similar to pipes except that it does not yield intermediate+ -- values..+ go acc (Pipe.YieldP ps1 b) = do+ acc1 <- fstep acc b+ r <- produce ps1+ case acc1 of+ Partial s -> go s r+ Done b1 -> return $ Done b1+ go acc (Pipe.SkipC cs1) =+ return $ Partial $ Tuple' cs1 acc+ -- XXX this case is recursive may cause fusion issues.+ go acc (Pipe.SkipP ps1) = do+ r <- produce ps1+ go acc r+ -- XXX a Stop in consumer means we dropped the input.+ go acc Pipe.Stop = Done <$> ffinal acc++ extract (Tuple' _ fs) = fextract fs++ final (Tuple' _ fs) = ffinal fs++------------------------------------------------------------------------------+-- Filters+------------------------------------------------------------------------------++-- | Returns the latest element omitting the first occurrence that satisfies+-- the given equality predicate.+--+-- Example:+--+-- >>> input = Stream.fromList [1,3,3,5]+--+-- >> Stream.toList $ Stream.scanMaybe (Fold.deleteBy (==) 3) input+-- [1,3,5]+--+{-# INLINE_NORMAL deleteBy #-}+deleteBy :: Monad m => (a -> a -> Bool) -> a -> Fold m a (Maybe a)+deleteBy eq = fromScanl . Scanl.deleteBy eq++-- | Provide a sliding window of length 2 elements.+--+-- See "Streamly.Internal.Data.Fold.Window".+--+{-# INLINE slide2 #-}+slide2 :: Monad m => Fold m (a, Maybe a) b -> Fold m a b+slide2 (Fold step1 initial1 extract1 final1) = Fold step initial extract final++ where++ initial =+ first (Tuple' Nothing) <$> initial1++ step (Tuple' prev s) cur =+ first (Tuple' (Just cur)) <$> step1 s (cur, prev)++ extract (Tuple' _ s) = extract1 s++ final (Tuple' _ s) = final1 s++-- | Return the latest unique element using the supplied comparison function.+-- Returns 'Nothing' if the current element is same as the last element+-- otherwise returns 'Just'.+--+-- Example, strip duplicate path separators:+--+-- >>> input = Stream.fromList "//a//b"+-- >>> f x y = x == '/' && y == '/'+--+-- >> Stream.toList $ Stream.scanMaybe (Fold.uniqBy f) input+-- "/a/b"+--+-- Space: @O(1)@+--+-- /Pre-release/+--+{-# INLINE uniqBy #-}+uniqBy :: Monad m => (a -> a -> Bool) -> Fold m a (Maybe a)+uniqBy = fromScanl . Scanl.uniqBy++-- | See 'uniqBy'.+--+-- Definition:+--+-- >>> uniq = Fold.uniqBy (==)+--+{-# INLINE uniq #-}+uniq :: (Monad m, Eq a) => Fold m a (Maybe a)+uniq = fromScanl Scanl.uniq++-- | Strip all leading and trailing occurrences of an element passing a+-- predicate and make all other consecutive occurrences uniq.+--+-- >> prune p = Stream.dropWhileAround p $ Stream.uniqBy (x y -> p x && p y)+--+-- @+-- > Stream.prune isSpace (Stream.fromList " hello world! ")+-- "hello world!"+--+-- @+--+-- Space: @O(1)@+--+-- /Unimplemented/+{-# INLINE prune #-}+prune ::+ -- (Monad m, Eq a) =>+ (a -> Bool) -> Fold m a (Maybe a)+prune = error "Not implemented yet!"++-- | Emit only repeated elements, once.+--+-- /Unimplemented/+repeated :: -- (Monad m, Eq a) =>+ Fold m a (Maybe a)+repeated = error "Not implemented yet!"++------------------------------------------------------------------------------+-- Left folds+------------------------------------------------------------------------------++------------------------------------------------------------------------------+-- Run Effects+------------------------------------------------------------------------------++-- |+-- Definitions:+--+-- >>> drainMapM f = Fold.lmapM f Fold.drain+-- >>> drainMapM f = Fold.foldMapM (void . f)+--+-- Drain all input after passing it through a monadic function. This is the+-- dual of mapM_ on stream producers.+--+{-# INLINE drainMapM #-}+drainMapM :: Monad m => (a -> m b) -> Fold m a ()+drainMapM f = lmapM f drain++{-# DEPRECATED drainBy "Please use 'drainMapM' instead." #-}+{-# INLINE drainBy #-}+drainBy :: Monad m => (a -> m b) -> Fold m a ()+drainBy = drainMapM++-- | Terminates with 'Nothing' as soon as it finds an element different than+-- the previous one, returns 'the' element if the entire input consists of the+-- same element.+--+{-# INLINE the #-}+the :: (Monad m, Eq a) => Fold m a (Maybe a)+the = fromScanl Scanl.the++------------------------------------------------------------------------------+-- To Summary+------------------------------------------------------------------------------++-- | Determine the sum of all elements of a stream of numbers. Returns additive+-- identity (@0@) when the stream is empty. Note that this is not numerically+-- stable for floating point numbers.+--+-- >>> sum = Fold.fromScanl (Scanl.cumulativeScan Scanl.incrSum)+--+-- Same as following but numerically stable:+--+-- >>> sum = Fold.foldl' (+) 0+-- >>> sum = fmap Data.Monoid.getSum $ Fold.foldMap Data.Monoid.Sum+--+{-# INLINE sum #-}+sum :: (Monad m, Num a) => Fold m a a+sum = fromScanl Scanl.sum++-- | Determine the product of all elements of a stream of numbers. Returns+-- multiplicative identity (@1@) when the stream is empty. The fold terminates+-- when it encounters (@0@) in its input.+--+-- Same as the following but terminates on multiplication by @0@:+--+-- >>> product = fmap Data.Monoid.getProduct $ Fold.foldMap Data.Monoid.Product+--+{-# INLINE product #-}+product :: (Monad m, Num a, Eq a) => Fold m a a+product = fromScanl Scanl.product++------------------------------------------------------------------------------+-- To Summary (Maybe)+------------------------------------------------------------------------------++-- | Determine the maximum element in a stream using the supplied comparison+-- function.+--+{-# INLINE maximumBy #-}+maximumBy :: Monad m => (a -> a -> Ordering) -> Fold m a (Maybe a)+maximumBy cmp = foldl1' max'++ where++ max' x y =+ case cmp x y of+ GT -> x+ _ -> y++-- | Determine the maximum element in a stream.+--+-- Definitions:+--+-- >>> maximum = Fold.maximumBy compare+-- >>> maximum = Fold.foldl1' max+--+-- Same as the following but without a default maximum. The 'Max' Monoid uses+-- the 'minBound' as the default maximum:+--+-- >>> maximum = fmap Data.Semigroup.getMax $ Fold.foldMap Data.Semigroup.Max+--+{-# INLINE maximum #-}+maximum :: (Monad m, Ord a) => Fold m a (Maybe a)+maximum = foldl1' max++-- | Computes the minimum element with respect to the given comparison function+--+{-# INLINE minimumBy #-}+minimumBy :: Monad m => (a -> a -> Ordering) -> Fold m a (Maybe a)+minimumBy cmp = foldl1' min'++ where++ min' x y =+ case cmp x y of+ GT -> y+ _ -> x++-- | Determine the minimum element in a stream using the supplied comparison+-- function.+--+-- Definitions:+--+-- >>> minimum = Fold.minimumBy compare+-- >>> minimum = Fold.foldl1' min+--+-- Same as the following but without a default minimum. The 'Min' Monoid uses the+-- 'maxBound' as the default maximum:+--+-- >>> maximum = fmap Data.Semigroup.getMin $ Fold.foldMap Data.Semigroup.Min+--+{-# INLINE minimum #-}+minimum :: (Monad m, Ord a) => Fold m a (Maybe a)+minimum = foldl1' min++{-# INLINE rangeBy #-}+rangeBy :: Monad m => (a -> a -> Ordering) -> Fold m a (Maybe (a, a))+rangeBy cmp = fromScanl (Scanl.rangeBy cmp)++-- | Find minimum and maximum elements i.e. (min, max).+--+{-# INLINE range #-}+range :: (Monad m, Ord a) => Fold m a (Maybe (a, a))+range = fromScanl Scanl.range++------------------------------------------------------------------------------+-- To Summary (Statistical)+------------------------------------------------------------------------------++-- | Compute a numerically stable arithmetic mean of all elements in the input+-- stream.+--+{-# INLINE mean #-}+mean :: (Monad m, Fractional a) => Fold m a a+mean = fromScanl Scanl.mean++-- | Compute a numerically stable (population) variance over all elements in+-- the input stream.+--+{-# DEPRECATED variance "Use the streamly-statistics package instead" #-}+{-# INLINE variance #-}+variance :: (Monad m, Fractional a) => Fold m a a+variance = fmap done $ foldl' step begin++ where++ begin = Tuple3' 0 0 0++ step (Tuple3' n mean_ m2) x = Tuple3' n' mean' m2'++ where++ n' = n + 1+ mean' = (n * mean_ + x) / (n + 1)+ delta = x - mean_+ m2' = m2 + delta * delta * n / (n + 1)++ done (Tuple3' n _ m2) = m2 / n++-- | Compute a numerically stable (population) standard deviation over all+-- elements in the input stream.+--+{-# DEPRECATED stdDev "Use the streamly-statistics package instead" #-}+{-# INLINE stdDev #-}+stdDev :: (Monad m, Floating a) => Fold m a a+stdDev = sqrt <$> variance++-- | Compute an 'Int' sized polynomial rolling hash+--+-- > H = salt * k ^ n + c1 * k ^ (n - 1) + c2 * k ^ (n - 2) + ... + cn * k ^ 0+--+-- Where @c1@, @c2@, @cn@ are the elements in the input stream and @k@ is a+-- constant.+--+-- This hash is often used in Rabin-Karp string search algorithm.+--+-- See https://en.wikipedia.org/wiki/Rolling_hash+--+{-# INLINE rollingHashWithSalt #-}+rollingHashWithSalt :: (Monad m, Enum a) => Int64 -> Fold m a Int64+rollingHashWithSalt = fromScanl . Scanl.rollingHashWithSalt++-- | Compute an 'Int' sized polynomial rolling hash of a stream.+--+-- >>> rollingHash = Fold.rollingHashWithSalt Fold.defaultSalt+--+{-# INLINE rollingHash #-}+rollingHash :: (Monad m, Enum a) => Fold m a Int64+rollingHash = fromScanl Scanl.rollingHash++-- | Compute an 'Int' sized polynomial rolling hash of the first n elements of+-- a stream.+--+-- >>> rollingHashFirstN n = Fold.take n Fold.rollingHash+--+-- /Pre-release/+{-# INLINE rollingHashFirstN #-}+rollingHashFirstN :: (Monad m, Enum a) => Int -> Fold m a Int64+rollingHashFirstN = fromScanl . Scanl.rollingHashFirstN++-- XXX Compare this with the implementation in Fold.Window, preferrably use the+-- latter if performance is good.++-- | Apply a function on every two successive elements of a stream. The first+-- argument of the map function is the previous element and the second argument+-- is the current element. When processing the very first element in the+-- stream, the previous element is 'Nothing'.+--+-- /Pre-release/+--+{-# INLINE rollingMapM #-}+rollingMapM :: Monad m => (Maybe a -> a -> m b) -> Fold m a b+rollingMapM = fromScanl . Scanl.rollingMapM++-- |+-- >>> rollingMap f = Fold.rollingMapM (\x y -> return $ f x y)+--+{-# INLINE rollingMap #-}+rollingMap :: Monad m => (Maybe a -> a -> b) -> Fold m a b+rollingMap = fromScanl . Scanl.rollingMap++------------------------------------------------------------------------------+-- Monoidal left folds+------------------------------------------------------------------------------++-- | Semigroup concat. Append the elements of an input stream to a provided+-- starting value.+--+-- Definition:+--+-- >>> sconcat = Fold.foldl' (<>)+--+-- >>> semigroups = fmap Data.Monoid.Sum $ Stream.enumerateFromTo 1 10+-- >>> Stream.fold (Fold.sconcat 10) semigroups+-- Sum {getSum = 65}+--+{-# INLINE sconcat #-}+sconcat :: (Monad m, Semigroup a) => a -> Fold m a a+sconcat = fromScanl . Scanl.sconcat++-- | Monoid concat. Fold an input stream consisting of monoidal elements using+-- 'mappend' and 'mempty'.+--+-- Definition:+--+-- >>> mconcat = Fold.sconcat mempty+--+-- >>> monoids = fmap Data.Monoid.Sum $ Stream.enumerateFromTo 1 10+-- >>> Stream.fold Fold.mconcat monoids+-- Sum {getSum = 55}+--+{-# INLINE mconcat #-}+mconcat ::+ ( Monad m+ , Monoid a) => Fold m a a+mconcat = fromScanl Scanl.mconcat++-- |+-- Definition:+--+-- >>> foldMap f = Fold.lmap f Fold.mconcat+--+-- Make a fold from a pure function that folds the output of the function+-- using 'mappend' and 'mempty'.+--+-- >>> sum = Fold.foldMap Data.Monoid.Sum+-- >>> Stream.fold sum $ Stream.enumerateFromTo 1 10+-- Sum {getSum = 55}+--+{-# INLINE foldMap #-}+foldMap :: (Monad m, Monoid b) => (a -> b) -> Fold m a b+foldMap = fromScanl . Scanl.foldMap++-- |+-- Definition:+--+-- >>> foldMapM f = Fold.lmapM f Fold.mconcat+--+-- Make a fold from a monadic function that folds the output of the function+-- using 'mappend' and 'mempty'.+--+-- >>> sum = Fold.foldMapM (return . Data.Monoid.Sum)+-- >>> Stream.fold sum $ Stream.enumerateFromTo 1 10+-- Sum {getSum = 55}+--+{-# INLINE foldMapM #-}+foldMapM :: (Monad m, Monoid b) => (a -> m b) -> Fold m a b+foldMapM = fromScanl . Scanl.foldMapM++------------------------------------------------------------------------------+-- Partial Folds+------------------------------------------------------------------------------++-- | A fold that drains the first n elements of its input, running the effects+-- and discarding the results.+--+-- Definition:+--+-- >>> drainN n = Fold.take n Fold.drain+--+-- /Pre-release/+{-# INLINE drainN #-}+drainN :: Monad m => Int -> Fold m a ()+drainN = fromScanl . Scanl.drainN++------------------------------------------------------------------------------+-- To Elements+------------------------------------------------------------------------------++-- | Like 'index', except with a more general 'Integral' argument+--+-- /Pre-release/+{-# INLINE genericIndex #-}+genericIndex :: (Integral i, Monad m) => i -> Fold m a (Maybe a)+genericIndex i = foldt' step (Partial 0) (const Nothing)++ where++ step j a =+ if i == j+ then Done $ Just a+ else Partial (j + 1)++-- | Return the element at the given index.+--+-- Definition:+--+-- >>> index = Fold.genericIndex+--+{-# INLINE index #-}+index :: Monad m => Int -> Fold m a (Maybe a)+index = genericIndex++-- | Consume a single input and transform it using the supplied 'Maybe'+-- returning function.+--+-- /Pre-release/+--+{-# INLINE maybe #-}+maybe :: Monad m => (a -> Maybe b) -> Fold m a (Maybe b)+maybe f = foldt' (const (Done . f)) (Partial Nothing) id++-- | Consume a single element and return it if it passes the predicate else+-- return 'Nothing'.+--+-- Definition:+--+-- >>> satisfy f = Fold.maybe (\a -> if f a then Just a else Nothing)+--+-- /Pre-release/+{-# INLINE satisfy #-}+satisfy :: Monad m => (a -> Bool) -> Fold m a (Maybe a)+satisfy f = maybe (\a -> if f a then Just a else Nothing)+{-+satisfy f = Fold step (return $ Partial ()) (const (return Nothing))++ where++ step () a = return $ Done $ if f a then Just a else Nothing+-}++-- Naming notes:+--+-- "head" and "next" are two alternative names for the same API. head sounds+-- apt in the context of lists but next sounds more apt in the context of+-- streams where we think in terms of generating and consuming the next element+-- rather than taking the head of some static/persistent structure.+--+-- We also want to keep the nomenclature consistent across folds and parsers,+-- "head" becomes even more unintuitive for parsers because there are two+-- possible variants viz. peek and next.+--+-- Also, the "head" fold creates confusion in situations like+-- https://github.com/composewell/streamly/issues/1404 where intuitive+-- expectation from head is to consume the entire stream and just give us the+-- head. There we want to convey the notion that we consume one element from+-- the stream and stop. The name "one" already being used in parsers for this+-- purpose sounds more apt from this perspective.+--+-- The source of confusion is perhaps due to the fact that some folds consume+-- the entire stream and others terminate early. It may have been clearer if we+-- had separate abstractions for the two use cases.++-- XXX We can possibly use "head" for the purposes of reducing the entire+-- stream to the head element i.e. take the head and drain the rest.++-- | Take one element from the stream and stop.+--+-- Definition:+--+-- >>> one = Fold.maybe Just+--+-- This is similar to the stream 'Stream.uncons' operation.+--+{-# INLINE one #-}+one :: Monad m => Fold m a (Maybe a)+one = maybe Just++-- | Extract the first element of the stream, if any.+--+-- >>> head = Fold.one+--+{-# DEPRECATED head "Please use \"one\" instead" #-}+{-# INLINE head #-}+head :: Monad m => Fold m a (Maybe a)+head = one++-- | Returns the first element that satisfies the given predicate.+--+-- /Pre-release/+{-# INLINE findM #-}+findM :: Monad m => (a -> m Bool) -> Fold m a (Maybe a)+findM predicate =+ Fold step (return $ Partial ()) extract extract++ where++ step () a =+ let f r =+ if r+ then Done (Just a)+ else Partial ()+ in f <$> predicate a++ extract = const $ return Nothing++-- | Returns the first element that satisfies the given predicate.+--+{-# INLINE find #-}+find :: Monad m => (a -> Bool) -> Fold m a (Maybe a)+find p = findM (return . p)++-- | 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@.+--+-- Definition:+--+-- >>> lookup x = fmap snd <$> Fold.find ((== x) . fst)+--+{-# INLINE lookup #-}+lookup :: (Eq a, Monad m) => a -> Fold m (a,b) (Maybe b)+lookup a0 = foldt' step (Partial ()) (const Nothing)++ where++ step () (a, b) =+ if a == a0+ then Done $ Just b+ else Partial ()++-- | Returns the first index that satisfies the given predicate.+--+{-# INLINE findIndex #-}+findIndex :: Monad m => (a -> Bool) -> Fold m a (Maybe Int)+findIndex predicate = foldt' step (Partial 0) (const Nothing)++ where++ step i a =+ if predicate a+ then Done $ Just i+ else Partial (i + 1)++-- | Returns the index of the latest element if the element satisfies the given+-- predicate.+--+{-# INLINE findIndices #-}+findIndices :: Monad m => (a -> Bool) -> Fold m a (Maybe Int)+findIndices = fromScanl . Scanl.findIndices++-- | Returns the index of the latest element if the element matches the given+-- value.+--+-- Definition:+--+-- >>> elemIndices a = Fold.findIndices (== a)+--+{-# INLINE elemIndices #-}+elemIndices :: (Monad m, Eq a) => a -> Fold m a (Maybe Int)+elemIndices = fromScanl . Scanl.elemIndices++-- | Returns the first index where a given value is found in the stream.+--+-- Definition:+--+-- >>> elemIndex a = Fold.findIndex (== a)+--+{-# INLINE elemIndex #-}+elemIndex :: (Eq a, Monad m) => a -> Fold m a (Maybe Int)+elemIndex a = findIndex (== a)++------------------------------------------------------------------------------+-- To Boolean+------------------------------------------------------------------------------++-- Similar to 'eof' parser, but the fold consumes and discards an input element+-- when not at eof. XXX Remove or Rename to "eof"?++-- | Consume one element, return 'True' if successful else return 'False'. In+-- other words, test if the input is empty or not.+--+-- WARNING! It consumes one element if the stream is not empty. If that is not+-- what you want please use the eof parser instead.+--+-- Definition:+--+-- >>> null = fmap isJust Fold.one+--+{-# INLINE null #-}+null :: Monad m => Fold m a Bool+null = foldt' (\() _ -> Done False) (Partial ()) (const True)++-- | Returns 'True' if any element of the input satisfies the predicate.+--+-- Definition:+--+-- >>> any p = Fold.lmap p Fold.or+--+-- Example:+--+-- >>> Stream.fold (Fold.any (== 0)) $ Stream.fromList [1,0,1]+-- True+--+{-# INLINE any #-}+any :: Monad m => (a -> Bool) -> Fold m a Bool+any predicate = foldt' step initial id++ where++ initial = Partial False++ step _ a =+ if predicate a+ then Done True+ else Partial False++-- | Return 'True' if the given element is present in the stream.+--+-- Definition:+--+-- >>> elem a = Fold.any (== a)+--+{-# INLINE elem #-}+elem :: (Eq a, Monad m) => a -> Fold m a Bool+elem a = any (== a)++-- | Returns 'True' if all elements of the input satisfy the predicate.+--+-- Definition:+--+-- >>> all p = Fold.lmap p Fold.and+--+-- Example:+--+-- >>> Stream.fold (Fold.all (== 0)) $ Stream.fromList [1,0,1]+-- False+--+{-# INLINE all #-}+all :: Monad m => (a -> Bool) -> Fold m a Bool+all predicate = foldt' step initial id++ where++ initial = Partial True++ step _ a =+ if predicate a+ then Partial True+ else Done False++-- | Returns 'True' if the given element is not present in the stream.+--+-- Definition:+--+-- >>> notElem a = Fold.all (/= a)+--+{-# INLINE notElem #-}+notElem :: (Eq a, Monad m) => a -> Fold m a Bool+notElem a = all (/= a)++-- | Returns 'True' if all elements are 'True', 'False' otherwise+--+-- Definition:+--+-- >>> and = Fold.all (== True)+--+{-# INLINE and #-}+and :: Monad m => Fold m Bool Bool+and = all id++-- | Returns 'True' if any element is 'True', 'False' otherwise+--+-- Definition:+--+-- >>> or = Fold.any (== True)+--+{-# INLINE or #-}+or :: Monad m => Fold m Bool Bool+or = any id++------------------------------------------------------------------------------+-- 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 = Stream.fold (Fold.splitAt n Fold.toList Fold.toList) $ Stream.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],[])+--+-- > splitAt n f1 f2 = Fold.splitWith (,) (Fold.take n f1) f2+--+-- /Internal/++{-# INLINE splitAt #-}+splitAt+ :: Monad m+ => Int+ -> Fold m a b+ -> Fold m a c+ -> Fold m a (b, c)+splitAt n fld = splitWith (,) (take n fld)++------------------------------------------------------------------------------+-- Element Aware APIs+------------------------------------------------------------------------------+--+------------------------------------------------------------------------------+-- Binary APIs+------------------------------------------------------------------------------++{-# INLINE takingEndByM #-}+takingEndByM :: Monad m => (a -> m Bool) -> Fold m a (Maybe a)+takingEndByM p = Fold step initial extract extract++ where++ initial = return $ Partial Nothing'++ step _ a = do+ r <- p a+ return+ $ if r+ then Done $ Just a+ else Partial $ Just' a++ extract = return . toMaybe++-- |+--+-- >>> takingEndBy p = Fold.takingEndByM (return . p)+--+{-# INLINE takingEndBy #-}+takingEndBy :: Monad m => (a -> Bool) -> Fold m a (Maybe a)+takingEndBy p = takingEndByM (return . p)++{-# INLINE takingEndByM_ #-}+takingEndByM_ :: Monad m => (a -> m Bool) -> Fold m a (Maybe a)+takingEndByM_ p = Fold step initial extract extract++ where++ initial = return $ Partial Nothing'++ step _ a = do+ r <- p a+ return+ $ if r+ then Done Nothing+ else Partial $ Just' a++ extract = return . toMaybe++-- |+--+-- >>> takingEndBy_ p = Fold.takingEndByM_ (return . p)+--+{-# INLINE takingEndBy_ #-}+takingEndBy_ :: Monad m => (a -> Bool) -> Fold m a (Maybe a)+takingEndBy_ p = takingEndByM_ (return . p)++{-# INLINE droppingWhileM #-}+droppingWhileM :: Monad m => (a -> m Bool) -> Fold m a (Maybe a)+droppingWhileM p = Fold step initial extract extract++ where++ initial = return $ Partial Nothing'++ step Nothing' a = do+ r <- p a+ return+ $ Partial+ $ if r+ then Nothing'+ else Just' a+ step _ a = return $ Partial $ Just' a++ extract = return . toMaybe++-- |+-- >>> droppingWhile p = Fold.droppingWhileM (return . p)+--+{-# INLINE droppingWhile #-}+droppingWhile :: Monad m => (a -> Bool) -> Fold m a (Maybe a)+droppingWhile p = droppingWhileM (return . p)++------------------------------------------------------------------------------+-- Binary splitting on a separator+------------------------------------------------------------------------------++data SplitOnSeqState mba acc a rh w ck =+ SplitOnSeqEmpty !acc+ | SplitOnSeqSingle !acc !a+ | SplitOnSeqWord !acc !Int !w+ | SplitOnSeqWordLoop !acc !w+ | SplitOnSeqKR !acc !Int !mba+ | SplitOnSeqKRLoop !acc !ck !mba !rh++-- XXX Need to add tests for takeEndBySeq, we have tests for takeEndBySeq_ .++-- | Continue taking the input until the input sequence matches the supplied+-- sequence, taking the supplied sequence as well. If the pattern is empty this+-- acts as an identity fold.+--+-- >>> s = Stream.fromList "Gauss---Euler---Noether"+-- >>> f = Fold.takeEndBySeq (Array.fromList "---") Fold.toList+-- >>> Stream.fold f s+-- "Gauss---"+--+-- >>> Stream.fold Fold.toList $ Stream.foldMany f s+-- ["Gauss---","Euler---","Noether"]+--+-- Uses Rabin-Karp algorithm for substring search.+--+-- See also: 'Streamly.Data.Stream.splitOnSeq' and+-- 'Streamly.Data.Stream.splitEndBySeq'.+--+-- /Pre-release/+{-# INLINE takeEndBySeq #-}+takeEndBySeq :: forall m a b. (MonadIO m, Unbox a, Enum a, Eq a) =>+ Array.Array a+ -> Fold m a b+ -> Fold m a b+takeEndBySeq patArr (Fold fstep finitial fextract ffinal) =+ Fold step initial extract final++ where++ patLen = Array.length patArr+ patBytes = Array.byteLength patArr+ maxIndex = patLen - 1+ maxOffset = patBytes - SIZE_OF(a)++ initial = do+ res <- finitial+ case res of+ Partial acc+ | patLen == 0 ->+ -- XXX Should we match nothing or everything on empty+ -- pattern?+ -- Done <$> ffinal acc+ return $ Partial $ SplitOnSeqEmpty acc+ | patLen == 1 -> do+ pat <- liftIO $ Array.unsafeGetIndexIO 0 patArr+ return $ Partial $ SplitOnSeqSingle acc pat+ | SIZE_OF(a) * patLen <= sizeOf (Proxy :: Proxy Word) ->+ return $ Partial $ SplitOnSeqWord acc 0 0+ | otherwise -> do+ (MutArray mba _ _ _) :: MutArray a <-+ liftIO $ MA.emptyOf patLen+ return $ Partial $ SplitOnSeqKR acc 0 mba+ Done b -> return $ Done b++ -- Word pattern related+ elemBits = SIZE_OF(a) * 8++ wordMask :: Word+ wordMask = (1 `shiftL` (elemBits * patLen)) - 1++ wordPat :: Word+ wordPat = wordMask .&. Array.foldl' addToWord 0 patArr++ addToWord wd a = (wd `shiftL` elemBits) .|. fromIntegral (fromEnum a)++ -- For Rabin-Karp search+ 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?+ -- XXX Need to keep this cached across fold calls in foldmany+ -- XXX We may need refold to inject the cached state instead of+ -- initializing the state every time.+ -- XXX Allocation of ring buffer should also be done once+ patHash = Array.foldl' addCksum 0 patArr++ step (SplitOnSeqEmpty s) x = do+ res <- fstep s x+ case res of+ Partial s1 -> return $ Partial $ SplitOnSeqEmpty s1+ Done b -> return $ Done b+ step (SplitOnSeqSingle s pat) x = do+ res <- fstep s x+ case res of+ Partial s1+ | pat /= x -> return $ Partial $ SplitOnSeqSingle s1 pat+ | otherwise -> Done <$> ffinal s1+ Done b -> return $ Done b+ step (SplitOnSeqWord s idx wrd) x = do+ res <- fstep s x+ let wrd1 = addToWord wrd x+ case res of+ Partial s1+ | idx == maxIndex -> do+ if wrd1 .&. wordMask == wordPat+ then Done <$> ffinal s1+ else return $ Partial $ SplitOnSeqWordLoop s1 wrd1+ | otherwise ->+ return $ Partial $ SplitOnSeqWord s1 (idx + 1) wrd1+ Done b -> return $ Done b+ step (SplitOnSeqWordLoop s wrd) x = do+ res <- fstep s x+ let wrd1 = addToWord wrd x+ case res of+ Partial s1+ | wrd1 .&. wordMask == wordPat ->+ Done <$> ffinal s1+ | otherwise ->+ return $ Partial $ SplitOnSeqWordLoop s1 wrd1+ Done b -> return $ Done b+ step (SplitOnSeqKR s offset mba) x = do+ res <- fstep s x+ case res of+ Partial s1 -> do+ liftIO $ pokeAt offset mba x+ if offset == maxOffset+ then do+ let arr :: Array a = Array+ { arrContents = mba+ , arrStart = 0+ , arrEnd = patBytes+ }+ let ringHash = Array.foldl' addCksum 0 arr+ if ringHash == patHash && Array.byteEq arr patArr+ then Done <$> ffinal s1+ else return $ Partial $ SplitOnSeqKRLoop s1 ringHash mba 0+ else+ return $ Partial $ SplitOnSeqKR s1 (offset + SIZE_OF(a)) mba+ Done b -> return $ Done b+ step (SplitOnSeqKRLoop s cksum mba offset) x = do+ res <- fstep s x+ case res of+ Partial s1 -> do+ let rb = RingArray+ { ringContents = mba+ , ringSize = patBytes+ , ringHead = offset+ }+ (rb1, old :: a) <- liftIO (RingArray.replace rb x)+ let ringHash = deltaCksum cksum old x+ let rh1 = ringHead rb1+ matches <-+ if ringHash == patHash+ then liftIO $ RingArray.eqArray rb1 patArr+ else return False+ if matches+ then Done <$> ffinal s1+ else return $ Partial $ SplitOnSeqKRLoop s1 ringHash mba rh1+ Done b -> return $ Done b++ extractFunc fex state =+ let st =+ case state of+ SplitOnSeqEmpty s -> s+ SplitOnSeqSingle s _ -> s+ SplitOnSeqWord s _ _ -> s+ SplitOnSeqWordLoop s _ -> s+ SplitOnSeqKR s _ _ -> s+ SplitOnSeqKRLoop s _ _ _ -> s+ in fex st++ extract = extractFunc fextract++ final = extractFunc ffinal++-- | Like 'takeEndBySeq' but discards the matched sequence.+--+-- >>> s = Stream.fromList "Gauss---Euler---Noether"+-- >>> f = Fold.takeEndBySeq_ (Array.fromList "---") Fold.toList+-- >>> Stream.fold f s+-- "Gauss"+--+-- >>> Stream.fold Fold.toList $ Stream.foldMany f s+-- ["Gauss","Euler","Noether"]+--+-- See also: 'Streamly.Data.Stream.splitOnSeq' and+-- 'Streamly.Data.Stream.splitEndBySeq_'.+--+-- /Pre-release/+--+{-# INLINE takeEndBySeq_ #-}+takeEndBySeq_ :: forall m a b. (MonadIO m, Unbox a, Enum a, Eq a) =>+ Array.Array a+ -> Fold m a b+ -> Fold m a b+takeEndBySeq_ patArr (Fold fstep finitial fextract ffinal) =+ Fold step initial extract final++ where++ patLen = Array.length patArr+ patBytes = Array.byteLength patArr+ maxIndex = patLen - 1+ maxOffset = patBytes - SIZE_OF(a)++ initial = do+ res <- finitial+ case res of+ Partial acc+ | patLen == 0 ->+ -- XXX Should we match nothing or everything on empty+ -- pattern?+ -- Done <$> ffinal acc+ return $ Partial $ SplitOnSeqEmpty acc+ | patLen == 1 -> do+ pat <- liftIO $ Array.unsafeGetIndexIO 0 patArr+ return $ Partial $ SplitOnSeqSingle acc pat+ -- XXX Need to add tests for this case+ | SIZE_OF(a) * patLen <= sizeOf (Proxy :: Proxy Word) ->+ return $ Partial $ SplitOnSeqWord acc 0 0+ | otherwise -> do+ (MutArray mba _ _ _) :: MutArray a <-+ liftIO $ MA.emptyOf patLen+ return $ Partial $ SplitOnSeqKR acc 0 mba+ Done b -> return $ Done b++ -- Word pattern related+ elemBits = SIZE_OF(a) * 8++ wordMask :: Word+ wordMask = (1 `shiftL` (elemBits * patLen)) - 1++ elemMask :: Word+ elemMask = (1 `shiftL` elemBits) - 1++ wordPat :: Word+ wordPat = wordMask .&. Array.foldl' addToWord 0 patArr++ addToWord wd a = (wd `shiftL` elemBits) .|. fromIntegral (fromEnum a)++ -- For Rabin-Karp search+ 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?+ -- XXX Need to keep this cached across fold calls in foldMany+ -- XXX We may need refold to inject the cached state instead of+ -- initializing the state every time.+ -- XXX Allocation of ring buffer should also be done once+ patHash = Array.foldl' addCksum 0 patArr++ step (SplitOnSeqEmpty s) x = do+ res <- fstep s x+ case res of+ Partial s1 -> return $ Partial $ SplitOnSeqEmpty s1+ Done b -> return $ Done b+ step (SplitOnSeqSingle s pat) x = do+ if pat /= x+ then do+ res <- fstep s x+ case res of+ Partial s1 -> return $ Partial $ SplitOnSeqSingle s1 pat+ Done b -> return $ Done b+ else Done <$> ffinal s+ step (SplitOnSeqWord s idx wrd) x = do+ let wrd1 = addToWord wrd x+ if idx == maxIndex+ then do+ if wrd1 .&. wordMask == wordPat+ then Done <$> ffinal s+ else return $ Partial $ SplitOnSeqWordLoop s wrd1+ else return $ Partial $ SplitOnSeqWord s (idx + 1) wrd1+ step (SplitOnSeqWordLoop s wrd) x = do+ let wrd1 = addToWord wrd x+ old = (wordMask .&. wrd)+ `shiftR` (elemBits * (patLen - 1))+ res <- fstep s (toEnum $ fromIntegral old)+ case res of+ Partial s1+ | wrd1 .&. wordMask == wordPat ->+ Done <$> ffinal s1+ | otherwise ->+ return $ Partial $ SplitOnSeqWordLoop s1 wrd1+ Done b -> return $ Done b+ step (SplitOnSeqKR s offset mba) x = do+ liftIO $ pokeAt offset mba x+ if offset == maxOffset+ then do+ let arr :: Array a = Array+ { arrContents = mba+ , arrStart = 0+ , arrEnd = patBytes+ }+ let ringHash = Array.foldl' addCksum 0 arr+ if ringHash == patHash && Array.byteEq arr patArr+ then Done <$> ffinal s+ else return $ Partial $ SplitOnSeqKRLoop s ringHash mba 0+ else return $ Partial $ SplitOnSeqKR s (offset + SIZE_OF(a)) mba+ step (SplitOnSeqKRLoop s cksum mba offset) x = do+ let rb = RingArray+ { ringContents = mba+ , ringSize = patBytes+ , ringHead = offset+ }+ (rb1, old :: a) <- liftIO (RingArray.replace rb x)+ res <- fstep s old+ case res of+ Partial s1 -> do+ let ringHash = deltaCksum cksum old x+ let rh1 = ringHead rb1+ matches <-+ if ringHash == patHash+ then liftIO $ RingArray.eqArray rb1 patArr+ else return False+ if matches+ then Done <$> ffinal s1+ else return $ Partial $ SplitOnSeqKRLoop s1 ringHash mba rh1+ Done b -> return $ Done b++ -- XXX extract should return backtrack count as well. If the fold+ -- terminates early inside extract, we may still have buffered data+ -- remaining which will be lost if we do not communicate that to the+ -- driver.+ extractFunc fex state = do+ let consumeWord s n wrd = do+ if n == 0+ then fex s+ else do+ let old = elemMask .&. (wrd `shiftR` (elemBits * (n - 1)))+ r <- fstep s (toEnum $ fromIntegral old)+ case r of+ Partial s1 -> consumeWord s1 (n - 1) wrd+ Done b -> return b++ let consumeArray s end mba offset =+ if offset == end+ then fex s+ else do+ old <- liftIO $ peekAt offset mba+ r <- fstep s old+ case r of+ Partial s1 ->+ consumeArray s1 end mba (offset + SIZE_OF(a))+ Done b -> return b++ let consumeRing s orig mba offset = do+ let rb :: RingArray a = RingArray+ { ringContents = mba+ , ringSize = patBytes+ , ringHead = offset+ }+ old <- RingArray.unsafeGetHead rb+ let rb1 = RingArray.moveForward rb+ r <- fstep s old+ case r of+ Partial s1 ->+ let rh = ringHead rb1+ in if rh == orig+ then fex s1+ else consumeRing s1 orig mba rh+ Done b -> return b++ case state of+ SplitOnSeqEmpty s -> fex s+ SplitOnSeqSingle s _ -> fex s+ SplitOnSeqWord s idx wrd -> consumeWord s idx wrd+ SplitOnSeqWordLoop s wrd -> consumeWord s patLen wrd+ SplitOnSeqKR s end mba -> consumeArray s end mba 0+ SplitOnSeqKRLoop s _ mba rh -> consumeRing s rh mba rh++ extract = extractFunc fextract++ final = extractFunc ffinal++------------------------------------------------------------------------------+-- Distributing+------------------------------------------------------------------------------+--+-- | Distribute one copy of the stream to each fold and zip the results.+--+-- @+-- |-------Fold m a b--------|+-- ---stream m a---| |---m (b,c)+-- |-------Fold m a c--------|+-- @+--+-- Definition:+--+-- >>> tee = Fold.teeWith (,)+--+-- Example:+--+-- >>> t = Fold.tee Fold.sum Fold.length+-- >>> Stream.fold t (Stream.enumerateFromTo 1.0 100.0)+-- (5050.0,100)+--+{-# INLINE tee #-}+tee :: Monad m => Fold m a b -> Fold m a c -> Fold m a (b,c)+tee = teeWith (,)++-- XXX use "List" instead of "[]"?, use Array for output to scale it to a large+-- number of consumers? For polymorphic case a vector could be helpful. For+-- Unboxs we can use arrays. Will need separate APIs for those.+--+-- | Distribute one copy of the stream to each fold and collect the results in+-- a container.+--+-- @+--+-- |-------Fold m a b--------|+-- ---stream m a---| |---m [b]+-- |-------Fold m a b--------|+-- | |+-- ...+-- @+--+-- >>> Stream.fold (Fold.distribute [Fold.sum, Fold.length]) (Stream.enumerateFromTo 1 5)+-- [15,5]+--+-- >>> distribute = Prelude.foldr (Fold.teeWith (:)) (Fold.fromPure [])+--+-- This is the consumer side dual of the producer side 'sequence' operation.+--+-- Stops when all the folds stop.+--+{-# INLINE distribute #-}+distribute :: Monad m => [Fold m a b] -> Fold m a [b]+distribute = Prelude.foldr (teeWith (:)) (fromPure [])++-- XXX use mutable cells for better performance.++-- | Distribute the input to the folds returned by an effect. The effect is+-- executed every time an input is processed, and the folds returned by it are+-- added to the distribution list. The scan returns the results of the folds as+-- they complete. To avoid adding the same folds repeatedly, the action must+-- return the folds only once e.g. it can be implemented using modifyIORef+-- replacing the original value by an empty list before returning it.+--+-- >>> import Data.IORef+-- >>> ref <- newIORef [Fold.take 2 Fold.sum, Fold.take 2 Fold.length :: Fold IO Int Int]+-- >>> gen = atomicModifyIORef ref (\xs -> ([], xs))+-- >>> Stream.toList $ Stream.scanl (Fold.distributeScan gen) (Stream.enumerateFromTo 1 10)+-- [[],[],[],[2,3],[],[],[],[],[],[],[]]+--+{-# INLINE distributeScan #-}+distributeScan :: Monad m => m [Fold m a b] -> Scanl m a [b]+distributeScan getFolds = Scanl consume initial extract final++ where++ initial = return $ Partial (Tuple' [] [])++ run st [] _ = return $ Partial st+ run (Tuple' ys zs) (Fold step init extr fin : xs) a = do+ res <- init+ case res of+ Partial fs -> do+ r <- step fs a+ run (Tuple' (Fold step (return r) extr fin : ys) zs) xs a+ Done b -> do+ run (Tuple' ys (b : zs)) xs a++ consume (Tuple' st _) x = do+ xs <- getFolds+ xs1 <- Prelude.mapM reduce xs+ let st1 = st ++ xs1+ run (Tuple' [] []) st1 x++ extract (Tuple' _ done) = return done++ final (Tuple' st done) = do+ Prelude.mapM_ finalM st+ return done++------------------------------------------------------------------------------+-- Partitioning+------------------------------------------------------------------------------++{-# INLINE partitionByMUsing #-}+partitionByMUsing :: Monad m =>+ ( (x -> y -> (x, y))+ -> Fold m (Either b c) x+ -> Fold m (Either b c) y+ -> Fold m (Either b c) (x, y)+ )+ -> (a -> m (Either b c))+ -> Fold m b x+ -> Fold m c y+ -> Fold m a (x, y)+partitionByMUsing t f fld1 fld2 =+ let l = lmap (fromLeft undefined) fld1 -- :: Fold m (Either b c) x+ r = lmap (fromRight undefined) fld2 -- :: Fold m (Either b c) y+ in lmapM f (t (,) (filter isLeft l) (filter isRight r))++-- | Partition the input over two folds using an 'Either' partitioning+-- predicate.+--+-- @+--+-- |-------Fold b x--------|+-- -----stream m a --> (Either b c)----| |----(x,y)+-- |-------Fold c y--------|+-- @+--+-- Example, send input to either fold randomly:+--+-- >>> :set -package random+-- >>> import System.Random (randomIO)+-- >>> randomly a = randomIO >>= \x -> return $ if x then Left a else Right a+-- >>> f = Fold.partitionByM randomly Fold.length Fold.length+-- >>> Stream.fold f (Stream.enumerateFromTo 1 100)+-- ...+--+-- Example, send input to the two folds in a proportion of 2:1:+--+-- >>> :set -fno-warn-unrecognised-warning-flags+-- >>> :set -fno-warn-x-partial+-- >>> :{+-- proportionately m n = do+-- ref <- newIORef $ cycle $ concat [replicate m Left, replicate n Right]+-- return $ \a -> do+-- r <- readIORef ref+-- writeIORef ref $ tail r+-- return $ Prelude.head r a+-- :}+--+-- >>> :{+-- main = do+-- g <- proportionately 2 1+-- let f = Fold.partitionByM g Fold.length Fold.length+-- r <- Stream.fold f (Stream.enumerateFromTo (1 :: Int) 100)+-- print r+-- :}+--+-- >>> main+-- (67,33)+--+--+-- This is the consumer side dual of the producer side 'mergeBy' operation.+--+-- When one fold is done, any input meant for it is ignored until the other+-- fold is also done.+--+-- Stops when both the folds stop.+--+-- /See also: 'partitionByFstM' and 'partitionByMinM'./+--+-- /Pre-release/+{-# INLINE partitionByM #-}+partitionByM :: Monad m+ => (a -> m (Either b c)) -> Fold m b x -> Fold m c y -> Fold m a (x, y)+partitionByM = partitionByMUsing teeWith++-- | Similar to 'partitionByM' but terminates when the first fold terminates.+--+{-# INLINE partitionByFstM #-}+partitionByFstM :: Monad m+ => (a -> m (Either b c)) -> Fold m b x -> Fold m c y -> Fold m a (x, y)+partitionByFstM = partitionByMUsing teeWithFst++-- | Similar to 'partitionByM' but terminates when any fold terminates.+--+{-# INLINE partitionByMinM #-}+partitionByMinM :: Monad m =>+ (a -> m (Either b c)) -> Fold m b x -> Fold m c y -> Fold m a (x, y)+partitionByMinM = partitionByMUsing teeWithMin++-- Note: we could use (a -> Bool) instead of (a -> Either b c), but the latter+-- makes the signature clearer as to which case belongs to which fold.+-- XXX need to check the performance in both cases.++-- | Same as 'partitionByM' but with a pure partition function.+--+-- Example, count even and odd numbers in a stream:+--+-- >>> :{+-- let f = Fold.partitionBy (\n -> if even n then Left n else Right n)+-- (fmap (("Even " ++) . show) Fold.length)+-- (fmap (("Odd " ++) . show) Fold.length)+-- in Stream.fold f (Stream.enumerateFromTo 1 100)+-- :}+-- ("Even 50","Odd 50")+--+-- /Pre-release/+{-# INLINE partitionBy #-}+partitionBy :: Monad m+ => (a -> Either b c) -> Fold m b x -> Fold m c y -> Fold m a (x, y)+partitionBy f = partitionByM (return . f)++-- | Compose two folds such that the combined fold accepts a stream of 'Either'+-- and routes the 'Left' values to the first fold and 'Right' values to the+-- second fold.+--+-- Definition:+--+-- >>> partition = Fold.partitionBy id+--+{-# INLINE partition #-}+partition :: Monad m+ => Fold m b x -> Fold m c y -> Fold m (Either b c) (x, y)+partition = partitionBy id++{-+-- | Send one item to each fold in a round-robin fashion. This is the consumer+-- side dual of producer side 'mergeN' operation.+--+-- partitionN :: Monad m => [Fold m a b] -> Fold m a [b]+-- partitionN fs = Fold step begin done+-}++------------------------------------------------------------------------------+-- Unzipping+------------------------------------------------------------------------------++{-# INLINE unzipWithMUsing #-}+unzipWithMUsing :: Monad m =>+ ( (x -> y -> (x, y))+ -> Fold m (b, c) x+ -> Fold m (b, c) y+ -> Fold m (b, c) (x, y)+ )+ -> (a -> m (b, c))+ -> Fold m b x+ -> Fold m c y+ -> Fold m a (x, y)+unzipWithMUsing t f fld1 fld2 =+ let f1 = lmap fst fld1 -- :: Fold m (b, c) b+ f2 = lmap snd fld2 -- :: Fold m (b, c) c+ in lmapM f (t (,) f1 f2)++-- | Like 'unzipWith' but with a monadic splitter function.+--+-- Definition:+--+-- >>> unzipWithM k f1 f2 = Fold.lmapM k (Fold.unzip f1 f2)+--+-- /Pre-release/+{-# INLINE unzipWithM #-}+unzipWithM :: Monad m+ => (a -> m (b,c)) -> Fold m b x -> Fold m c y -> Fold m a (x,y)+unzipWithM = unzipWithMUsing teeWith++-- | Similar to 'unzipWithM' but terminates when the first fold terminates.+--+{-# INLINE unzipWithFstM #-}+unzipWithFstM :: Monad m =>+ (a -> m (b, c)) -> Fold m b x -> Fold m c y -> Fold m a (x, y)+unzipWithFstM = unzipWithMUsing teeWithFst++-- | Similar to 'unzipWithM' but terminates when any fold terminates.+--+{-# INLINE unzipWithMinM #-}+unzipWithMinM :: Monad m =>+ (a -> m (b,c)) -> Fold m b x -> Fold m c y -> Fold m a (x,y)+unzipWithMinM = unzipWithMUsing teeWithMin++-- | Split elements in the input stream into two parts using a pure splitter+-- function, direct each part to a different fold and zip the results.+--+-- Definitions:+--+-- >>> unzipWith f = Fold.unzipWithM (return . f)+-- >>> unzipWith f fld1 fld2 = Fold.lmap f (Fold.unzip fld1 fld2)+--+-- This fold terminates when both the input folds terminate.+--+-- /Pre-release/+{-# INLINE unzipWith #-}+unzipWith :: Monad m+ => (a -> (b,c)) -> Fold m b x -> Fold m c y -> Fold m a (x,y)+unzipWith f = unzipWithM (return . f)++-- | Send the elements of tuples in a stream of tuples through two different+-- folds.+--+-- @+--+-- |-------Fold m a x--------|+-- ---------stream of (a,b)--| |----m (x,y)+-- |-------Fold m b y--------|+--+-- @+--+-- Definition:+--+-- >>> unzip = Fold.unzipWith id+--+-- This is the consumer side dual of the producer side 'zip' operation.+--+{-# INLINE unzip #-}+unzip :: Monad m => Fold m a x -> Fold m b y -> Fold m (a,b) (x,y)+unzip = unzipWith id++------------------------------------------------------------------------------+-- Combining streams and folds - Zipping+------------------------------------------------------------------------------++-- XXX These can be implemented using the fold scan, using the stream as a+-- state.+-- XXX Stream Skip state cannot be efficiently handled in folds but can be+-- handled in parsers using the Continue facility. See zipWithM in the Parser+-- module.+--+-- cmpBy, eqBy, isPrefixOf, isSubsequenceOf etc can be implemented using+-- zipStream.++-- | Zip a stream with the input of a fold using the supplied function.+--+-- /Unimplemented/+--+{-# INLINE zipStreamWithM #-}+zipStreamWithM :: -- Monad m =>+ (a -> b -> m c) -> Stream m a -> Fold m c x -> Fold m b x+zipStreamWithM = undefined++-- | Zip a stream with the input of a fold.+--+-- >>> zip = Fold.zipStreamWithM (curry return)+--+-- /Unimplemented/+--+{-# INLINE zipStream #-}+zipStream :: Monad m => Stream m a -> Fold m (a, b) x -> Fold m b x+zipStream = zipStreamWithM (curry return)++-- | Pair each element of a fold input with its index, starting from index 0.+--+{-# DEPRECATED indexingWith "Use Scanl.indexingWith instead" #-}+{-# INLINE indexingWith #-}+indexingWith :: Monad m => Int -> (Int -> Int) -> Fold m a (Maybe (Int, a))+indexingWith i f = fmap toMaybe $ foldl' step initial++ where++ initial = Nothing'++ step Nothing' a = Just' (i, a)+ step (Just' (n, _)) a = Just' (f n, a)++-- |+-- >> indexing = Fold.indexingWith 0 (+ 1)+--+{-# DEPRECATED indexing "Use Scanl.indexing instead" #-}+{-# INLINE indexing #-}+indexing :: Monad m => Fold m a (Maybe (Int, a))+indexing = indexingWith 0 (+ 1)++-- |+-- >> indexingRev n = Fold.indexingWith n (subtract 1)+--+{-# DEPRECATED indexingRev "Use Scanl.indexingRev instead" #-}+{-# INLINE indexingRev #-}+indexingRev :: Monad m => Int -> Fold m a (Maybe (Int, a))+indexingRev n = indexingWith n (subtract 1)++-- | Pair each element of a fold input with its index, starting from index 0.+--+-- >>> indexed = Fold.postscanlMaybe Scanl.indexing+--+{-# INLINE indexed #-}+indexed :: Monad m => Fold m (Int, a) b -> Fold m a b+indexed = postscanlMaybe Scanl.indexing++-- | Change the predicate function of a Fold from @a -> b@ to accept an+-- additional state input @(s, a) -> b@. Convenient to filter with an+-- addiitonal index or time input.+--+-- >>> filterWithIndex = Fold.with Fold.indexed Fold.filter+--+-- @+-- filterWithAbsTime = with timestamped filter+-- filterWithRelTime = with timeIndexed filter+-- @+--+-- /Pre-release/+{-# INLINE with #-}+with ::+ (Fold m (s, a) b -> Fold m a b)+ -> (((s, a) -> c) -> Fold m (s, a) b -> Fold m (s, a) b)+ -> (((s, a) -> c) -> Fold m a b -> Fold m a b)+with f comb g = f . comb g . lmap snd++-- XXX Implement as a filter+-- sampleFromthen :: Monad m => Int -> Int -> Fold m a (Maybe a)++-- | @sampleFromthen offset stride@ samples the element at @offset@ index and+-- then every element at strides of @stride@.+--+{-# INLINE sampleFromthen #-}+sampleFromthen :: Monad m => Int -> Int -> Fold m a b -> Fold m a b+sampleFromthen offset size =+ with indexed filter (\(i, _) -> (i + offset) `mod` size == 0)++------------------------------------------------------------------------------+-- Nesting+------------------------------------------------------------------------------++-- | @concatSequence f t@ applies folds from stream @t@ sequentially and+-- collects the results using the fold @f@.+--+-- /Unimplemented/+--+{-# INLINE concatSequence #-}+concatSequence ::+ -- IsStream t =>+ Fold m b c -> t (Fold m a b) -> Fold m a c+concatSequence _f _p = undefined++-- | Group the input stream into groups of elements between @low@ and @high@.+-- Collection starts in chunks of @low@ and then keeps doubling until we reach+-- @high@. Each chunk is folded using the provided fold function.+--+-- This could be useful, for example, when we are folding a stream of unknown+-- size to a stream of arrays and we want to minimize the number of+-- allocations.+--+-- NOTE: this would be an application of "many" using a terminating fold.+--+-- /Unimplemented/+--+{-# INLINE chunksBetween #-}+chunksBetween :: -- Monad m =>+ Int -> Int -> Fold m a b -> Fold m b c -> Fold m a c+chunksBetween _low _high _f1 _f2 = undefined++-- | 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.Data.Array" instead.+--+-- >>> toStream = fmap Stream.fromList Fold.toList+--+-- /Pre-release/+{-# INLINE toStream #-}+toStream :: (Monad m, Monad n) => Fold m a (Stream n a)+toStream = fromScanl Scanl.toStream++-- 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.+--+-- >>> toStreamRev = fmap Stream.fromList Fold.toListRev+--+-- /Warning!/ working on large streams accumulated as buffers in memory could+-- be very inefficient, consider using "Streamly.Data.Array" instead.+--+-- /Pre-release/++-- xn : ... : x2 : x1 : []+{-# INLINE toStreamRev #-}+toStreamRev :: (Monad m, Monad n) => Fold m a (Stream n a)+toStreamRev = fromScanl Scanl.toStreamRev++-- XXX This does not fuse. It contains a recursive step function. We will need+-- a Skip input constructor in the fold type to make it fuse.+--+-- | Unfold and flatten the input stream of a fold.+--+-- @+-- Stream.fold (unfoldMany u f) = Stream.fold f . Stream.unfoldMany u+-- @+--+-- /Pre-release/+{-# INLINE unfoldMany #-}+unfoldMany :: Monad m => Unfold m a b -> Fold m b c -> Fold m a c+unfoldMany (Unfold ustep inject) (Fold fstep initial extract final) =+ Fold consume initial extract final++ where++ {-# INLINE produce #-}+ produce fs us = do+ ures <- ustep us+ case ures of+ StreamD.Yield b us1 -> do+ fres <- fstep fs b+ case fres of+ Partial fs1 -> produce fs1 us1+ -- XXX What to do with the remaining stream?+ Done c -> return $ Done c+ StreamD.Skip us1 -> produce fs us1+ StreamD.Stop -> return $ Partial fs++ {-# INLINE_LATE consume #-}+ consume s a = inject a >>= produce s++-- | Get the bottom most @n@ elements using the supplied comparison function.+--+{-# INLINE bottomBy #-}+bottomBy :: (MonadIO m, Unbox a) =>+ (a -> a -> Ordering)+ -> Int+ -> Fold m a (MutArray a)+bottomBy cmp = fromScanl . Scanl.bottomBy cmp++-- | Get the top @n@ elements using the supplied comparison function.+--+-- To get bottom n elements instead:+--+-- >>> bottomBy cmp = Fold.topBy (flip cmp)+--+-- Example:+--+-- >>> stream = Stream.fromList [2::Int,7,9,3,1,5,6,11,17]+-- >>> Stream.fold (Fold.topBy compare 3) stream >>= MutArray.toList+-- [17,11,9]+--+-- /Pre-release/+--+{-# INLINE topBy #-}+topBy :: (MonadIO m, Unbox a) =>+ (a -> a -> Ordering)+ -> Int+ -> Fold m a (MutArray a)+topBy cmp = bottomBy (flip cmp)++-- | Fold the input stream to top n elements.+--+-- Definition:+--+-- >>> top = Fold.topBy compare+--+-- >>> stream = Stream.fromList [2::Int,7,9,3,1,5,6,11,17]+-- >>> Stream.fold (Fold.top 3) stream >>= MutArray.toList+-- [17,11,9]+--+-- /Pre-release/+{-# INLINE top #-}+top :: (MonadIO m, Unbox a, Ord a) => Int -> Fold m a (MutArray a)+top = fromScanl . Scanl.top++-- | Fold the input stream to bottom n elements.+--+-- Definition:+--+-- >>> bottom = Fold.bottomBy compare+--+-- >>> stream = Stream.fromList [2::Int,7,9,3,1,5,6,11,17]+-- >>> Stream.fold (Fold.bottom 3) stream >>= MutArray.toList+-- [1,2,3]+--+-- /Pre-release/+{-# INLINE bottom #-}+bottom :: (MonadIO m, Unbox a, Ord a) => Int -> Fold m a (MutArray a)+bottom = fromScanl . Scanl.bottom++------------------------------------------------------------------------------+-- Interspersed parsing+------------------------------------------------------------------------------++data IntersperseQState fs ps =+ IntersperseQUnquoted !fs !ps+ | IntersperseQQuoted !fs !ps+ | IntersperseQQuotedEsc !fs !ps++-- Useful for parsing CSV with quoting and escaping+{-# INLINE intersperseWithQuotes #-}+intersperseWithQuotes :: (Monad m, Eq a) =>+ a -> a -> a -> Fold m a b -> Fold m b c -> Fold m a c+intersperseWithQuotes+ quote+ esc+ separator+ (Fold stepL initialL _ finalL)+ (Fold stepR initialR extractR finalR) = Fold step initial extract final++ where++ errMsg p status =+ error $ "intersperseWithQuotes: " ++ p ++ " parsing fold cannot "+ ++ status ++ " without input"++ {-# INLINE initL #-}+ initL mkState = do+ resL <- initialL+ case resL of+ Partial sL ->+ return $ Partial $ mkState sL+ Done _ ->+ errMsg "content" "succeed"++ initial = do+ res <- initialR+ case res of+ Partial sR -> initL (IntersperseQUnquoted sR)+ Done b -> return $ Done b++ {-# INLINE collect #-}+ collect nextS sR b = do+ res <- stepR sR b+ case res of+ Partial s ->+ initL (nextS s)+ Done c -> return (Done c)++ {-# INLINE process #-}+ process a sL sR nextState = do+ r <- stepL sL a+ case r of+ Partial s -> return $ Partial (nextState sR s)+ Done b -> collect nextState sR b++ {-# INLINE processQuoted #-}+ processQuoted a sL sR nextState = do+ r <- stepL sL a+ case r of+ Partial s -> return $ Partial (nextState sR s)+ Done _ -> do+ _ <- finalR sR+ error "Collecting fold finished inside quote"++ step (IntersperseQUnquoted sR sL) a+ | a == separator = do+ b <- finalL sL+ collect IntersperseQUnquoted sR b+ | a == quote = processQuoted a sL sR IntersperseQQuoted+ | otherwise = process a sL sR IntersperseQUnquoted++ step (IntersperseQQuoted sR sL) a+ | a == esc = processQuoted a sL sR IntersperseQQuotedEsc+ | a == quote = process a sL sR IntersperseQUnquoted+ | otherwise = processQuoted a sL sR IntersperseQQuoted++ step (IntersperseQQuotedEsc sR sL) a =+ processQuoted a sL sR IntersperseQQuoted++ extract (IntersperseQUnquoted sR _) = extractR sR+ extract (IntersperseQQuoted _ _) =+ error "intersperseWithQuotes: finished inside quote"+ extract (IntersperseQQuotedEsc _ _) =+ error "intersperseWithQuotes: finished inside quote, at escape char"++ final (IntersperseQUnquoted sR sL) = finalL sL *> finalR sR+ final (IntersperseQQuoted sR sL) = do+ _ <- finalR sR+ _ <- finalL sL+ error "intersperseWithQuotes: finished inside quote"+ final (IntersperseQQuotedEsc sR sL) = do+ _ <- finalR sR+ _ <- finalL sL+ error "intersperseWithQuotes: finished inside quote, at escape char"
src/Streamly/Internal/Data/Fold/Container.hs view
@@ -1,3 +1,11 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE TypeFamilies #-}+-- Must come after TypeFamilies, otherwise it is re-enabled.+-- MonoLocalBinds enabled by TypeFamilies causes perf regressions in general.+{-# LANGUAGE NoMonoLocalBinds #-}++{-# OPTIONS_GHC -Wno-deprecations #-}+ -- | -- Module : Streamly.Internal.Data.Fold.Container -- Copyright : (c) 2019 Composewell Technologies@@ -9,16 +17,13 @@ module Streamly.Internal.Data.Fold.Container (- -- * Imports- -- $setup- -- * Set operations toSet , toIntSet , countDistinct , countDistinctInt- , nub- , nubInt+ , nub -- XXX deprecate in favor of scan+ , nubInt -- XXX deprecate in favor of scan -- * Map operations , frequency@@ -27,18 +32,31 @@ -- | Direct values in the input stream to different folds using an n-ary -- fold selector. 'demux' is a generalization of 'classify' (and -- 'partition') where each key of the classifier can use a different fold.+ --+ -- You need to see only 'demux' if you are looking to find the capabilities+ -- of these combinators, all others are variants of that.++ -- *** Output is a container+ -- | Use key specific folds to fold corresponding values to a key-value+ -- container.+ , demuxerToContainer+ , demuxerToContainerIO+ , demuxerToMap+ , demuxerToMapIO++ -- *** Input is explicit key-value tuple+ -- | Like above but inputs are in explicit key-value pair form. , demuxKvToContainer , demuxKvToMap - , demuxToContainer- , demuxToContainerIO- , demuxToMap- , demuxToMapIO-- , demuxGeneric- , demux- , demuxGenericIO- , demuxIO+ -- *** Scan of finished fold results+ -- | Use key specific folds to fold corresponding values to a key-value+ -- stream, restarts the fold again after it terminates, thus resulting in a+ -- stream of values for each key.+ , demuxScanGeneric+ , demuxScan+ , demuxScanGenericIO+ , demuxScanIO -- TODO: These can be implemented using the above operations -- , demuxSel -- Stop when the fold for the specified key stops@@ -65,12 +83,27 @@ , toMap , toMapIO + , classifyScanGeneric+ , classifyScan+ , classifyScanGenericIO+ , classifyScanIO+ -- , toContainerSel+ -- , toContainerMin++ -- * Deprecated+ , demuxGeneric+ , demux+ , demuxGenericIO+ , demuxIO+ , demuxToContainer+ , demuxToContainerIO+ , demuxToMap+ , demuxToMapIO+ , classifyGeneric , classify , classifyGenericIO , classifyIO- -- , toContainerSel- -- , toContainerMin ) where @@ -83,24 +116,17 @@ import Data.IntSet (IntSet) import Data.Set (Set) import Streamly.Internal.Data.IsMap (IsMap(..))+import Streamly.Internal.Data.Scanl.Type (Scanl(..)) import Streamly.Internal.Data.Tuple.Strict (Tuple'(..), Tuple3'(..)) -import qualified Data.IntSet as IntSet import qualified Data.Set as Set import qualified Streamly.Internal.Data.IsMap as IsMap+import qualified Streamly.Internal.Data.Scanl.Container as Scanl -import Prelude hiding (length)-import Streamly.Internal.Data.Fold+import Prelude hiding (Foldable(..))+import Streamly.Internal.Data.Fold.Type --- $setup--- >>> :m--- >>> :set -XFlexibleContexts--- >>> import qualified Data.Map as Map--- >>> import qualified Data.Set as Set--- >>> import qualified Data.IntSet as IntSet--- >>> import qualified Streamly.Data.Fold as Fold--- >>> import qualified Streamly.Data.Stream as Stream--- >>> import qualified Streamly.Internal.Data.Fold.Container as Fold+#include "DocTestDataFold.hs" -- | Fold the input to a set. --@@ -110,7 +136,7 @@ -- {-# INLINE toSet #-} toSet :: (Monad m, Ord a) => Fold m a (Set a)-toSet = foldl' (flip Set.insert) Set.empty+toSet = fromScanl Scanl.toSet -- | Fold the input to an int set. For integer inputs this performs better than -- 'toSet'.@@ -121,7 +147,7 @@ -- {-# INLINE toIntSet #-} toIntSet :: Monad m => Fold m Int IntSet-toIntSet = foldl' (flip IntSet.insert) IntSet.empty+toIntSet = fromScanl Scanl.toIntSet -- XXX Name as nubOrd? Or write a nubGeneric @@ -131,38 +157,21 @@ -- Example: -- -- >>> stream = Stream.fromList [1::Int,1,2,3,4,4,5,1,5,7]--- >>> Stream.fold Fold.toList $ Stream.scanMaybe Fold.nub stream+--+-- >> Stream.toList $ Stream.scanMaybe Fold.nub stream -- [1,2,3,4,5,7] -- -- /Pre-release/ {-# INLINE nub #-} nub :: (Monad m, Ord a) => Fold m a (Maybe a)-nub = fmap (\(Tuple' _ x) -> x) $ foldl' step initial-- where-- initial = Tuple' Set.empty Nothing-- step (Tuple' set _) x =- if Set.member x set- then Tuple' set Nothing- else Tuple' (Set.insert x set) (Just x)+nub = fromScanl Scanl.nub -- | Like 'nub' but specialized to a stream of 'Int', for better performance. -- -- /Pre-release/ {-# INLINE nubInt #-} nubInt :: Monad m => Fold m Int (Maybe Int)-nubInt = fmap (\(Tuple' _ x) -> x) $ foldl' step initial-- where-- initial = Tuple' IntSet.empty Nothing-- step (Tuple' set _) x =- if IntSet.member x set- then Tuple' set Nothing- else Tuple' (IntSet.insert x set) (Just x)+nubInt = fromScanl Scanl.nubInt -- XXX Try Hash set -- XXX Add a countDistinct window fold@@ -173,7 +182,7 @@ -- Definition: -- -- >>> countDistinct = fmap Set.size Fold.toSet--- >>> countDistinct = Fold.postscan Fold.nub $ Fold.catMaybes $ Fold.length+-- >>> countDistinct = Fold.postscanl Scanl.nub $ Fold.catMaybes $ Fold.length -- -- The memory used is proportional to the number of distinct elements in the -- stream, to guard against using too much memory use it as a scan and@@ -186,7 +195,7 @@ {-# INLINE countDistinct #-} countDistinct :: (Monad m, Ord a) => Fold m a Int -- countDistinct = postscan nub $ catMaybes length-countDistinct = fmap Set.size toSet+countDistinct = fromScanl Scanl.countDistinct {- countDistinct = fmap (\(Tuple' _ n) -> n) $ foldl' step initial @@ -209,13 +218,13 @@ -- Definition: -- -- >>> countDistinctInt = fmap IntSet.size Fold.toIntSet--- >>> countDistinctInt = Fold.postscan Fold.nubInt $ Fold.catMaybes $ Fold.length+-- >>> countDistinctInt = Fold.postscanl Scanl.nubInt $ Fold.catMaybes $ Fold.length -- -- /Pre-release/ {-# INLINE countDistinctInt #-} countDistinctInt :: Monad m => Fold m Int Int -- countDistinctInt = postscan nubInt $ catMaybes length-countDistinctInt = fmap IntSet.size toIntSet+countDistinctInt = fromScanl Scanl.countDistinctInt {- countDistinctInt = fmap (\(Tuple' _ n) -> n) $ foldl' step initial @@ -245,25 +254,33 @@ -- -- XXX If we use Refold in it, it can perhaps fuse/be more efficient. For -- example we can store just the result rather than storing the whole fold in--- the Map.+-- the Map. This would be similar to a refold based classify. -- -- Note: There are separate functions to determine Key and Fold from the input -- because key is to be determined on each input whereas fold is to be -- determined only once for a key.+--+-- XXX Should we use (k -> m (Fold m a b)) instead since the fold is key+-- specific? This should give better safety. +-- | This is the most general of all demux, classify operations.+--+-- See 'demux' for documentation.+{-# DEPRECATED demuxGeneric "Use demuxScanGeneric instead" #-} {-# INLINE demuxGeneric #-} demuxGeneric :: (Monad m, IsMap f, Traversable f) => (a -> Key f) -> (a -> m (Fold m a b)) -> Fold m a (m (f b), Maybe (Key f, b))-demuxGeneric getKey getFold = fmap extract $ foldlM' step initial+demuxGeneric getKey getFold =+ Fold (\s a -> Partial <$> step s a) (Partial <$> initial) extract final where initial = return $ Tuple' IsMap.mapEmpty Nothing {-# INLINE runFold #-}- runFold kv (Fold step1 initial1 extract1) (k, a) = do+ runFold kv (Fold step1 initial1 extract1 final1) (k, a) = do res <- initial1 case res of Partial s -> do@@ -271,10 +288,14 @@ return $ case res1 of Partial _ ->- let fld = Fold step1 (return res1) extract1+ let fld = Fold step1 (return res1) extract1 final1 in Tuple' (IsMap.mapInsert k fld kv) Nothing Done b -> Tuple' (IsMap.mapDelete k kv) (Just (k, b))- Done b -> return $ Tuple' kv (Just (k, b))+ Done b ->+ -- Done in "initial" is possible only for the very first time+ -- the fold is initialized, and in that case we have not yet+ -- inserted it in the Map, so we do not need to delete it.+ return $ Tuple' kv (Just (k, b)) step (Tuple' kv _) a = do let k = getKey a@@ -284,27 +305,177 @@ runFold kv fld (k, a) Just f -> runFold kv f (k, a) - extract (Tuple' kv x) = (Prelude.mapM f kv, x)+ extract (Tuple' kv x) = return (Prelude.mapM f kv, x) where - f (Fold _ i e) = do+ f (Fold _ i e _) = do r <- i case r of Partial s -> e s- Done b -> return b+ _ -> error "demuxGeneric: unreachable code" --- | In a key value stream, fold values corresponding to each key with a key--- specific fold. The fold returns the fold result as the second component of--- the output tuple whenever a fold terminates. The first component of the--- tuple is a Map of in-progress folds. If a fold terminates, another--- instance of the fold is started upon receiving an input with that key.+ final (Tuple' kv x) = return (Prelude.mapM f kv, x)++ where++ f (Fold _ i _ fin) = do+ r <- i+ case r of+ Partial s -> fin s+ _ -> error "demuxGeneric: unreachable code"++-- XXX There seem to be a significant difference in demux and classify. In+-- demux once a key is done we again restart it and give the result of the+-- last one. In classify, we do not restart once it is done. To keep it+-- simple we should use the classify behavior.++-- | This is the most general of all demux, classify operations. --+-- See 'demux' for documentation.+{-# INLINE demuxerToContainer #-}+demuxerToContainer :: (Monad m, IsMap f, Traversable f) =>+ (a -> Key f)+ -> (Key f -> m (Maybe (Fold m a b)))+ -> Fold m a (f b)+demuxerToContainer getKey getFold =+ Fold (\s a -> Partial <$> step s a) (Partial <$> initial) undefined final++ where++ initial = return $ Tuple' IsMap.mapEmpty IsMap.mapEmpty++ {-# INLINE runFold #-}+ runFold kv kv1 (Fold step1 initial1 _ final1) (k, a) = do+ res <- initial1+ case res of+ Partial s -> do+ res1 <- step1 s a+ return+ $ case res1 of+ Partial _ ->+ let fld = Fold step1 (return res1) undefined final1+ in Tuple' (IsMap.mapInsert k fld kv) kv1+ Done b ->+ Tuple'+ (IsMap.mapDelete k kv)+ (IsMap.mapInsert k b kv1)+ Done b ->+ -- Done in "initial" is possible only for the very first time+ -- the fold is initialized, and in that case we have not yet+ -- inserted it in the Map, so we do not need to delete it.+ return $ Tuple' kv (IsMap.mapInsert k b kv1)++ step (Tuple' kv kv1) a = do+ let k = getKey a+ case IsMap.mapLookup k kv of+ Nothing -> do+ mfld <- getFold k+ case mfld of+ Nothing -> pure $ Tuple' kv kv1+ Just fld -> runFold kv kv1 fld (k, a)+ Just f -> runFold kv kv1 f (k, a)++ final (Tuple' kv kv1) = do+ r <- Prelude.mapM f kv+ return $ IsMap.mapUnion r kv1++ where++ f (Fold _ i _ fin) = do+ r <- i+ case r of+ Partial s -> fin s+ _ -> error "demuxerToContainer: unreachable code"++-- | Scanning variant of 'demuxerToContainer'.+{-# INLINE demuxScanGeneric #-}+demuxScanGeneric :: (Monad m, IsMap f, Traversable f) =>+ (a -> Key f)+ -> (Key f -> m (Maybe (Fold m a b)))+ -> Scanl m a (m (f b), Maybe (Key f, b))+demuxScanGeneric getKey getFold =+ Scanl (\s a -> Partial <$> step s a) (Partial <$> initial) extract final++ where++ initial = return $ Tuple' IsMap.mapEmpty Nothing++ {-# INLINE runFold #-}+ runFold kv (Fold step1 initial1 extract1 final1) (k, a) = do+ res <- initial1+ case res of+ Partial s -> do+ res1 <- step1 s a+ return+ $ case res1 of+ Partial _ ->+ let fld = Fold step1 (return res1) extract1 final1+ in Tuple' (IsMap.mapInsert k fld kv) Nothing+ Done b -> Tuple' (IsMap.mapDelete k kv) (Just (k, b))+ Done b ->+ -- Done in "initial" is possible only for the very first time+ -- the fold is initialized, and in that case we have not yet+ -- inserted it in the Map, so we do not need to delete it.+ return $ Tuple' kv (Just (k, b))++ step (Tuple' kv _) a = do+ let k = getKey a+ case IsMap.mapLookup k kv of+ Nothing -> do+ mfld <- getFold k+ case mfld of+ Nothing -> pure $ Tuple' kv Nothing+ Just fld -> runFold kv fld (k, a)+ Just f -> runFold kv f (k, a)++ extract (Tuple' kv x) = return (Prelude.mapM f kv, x)++ where++ f (Fold _ i e _) = do+ r <- i+ case r of+ Partial s -> e s+ _ -> error "demuxGeneric: unreachable code"++ final (Tuple' kv x) = return (Prelude.mapM f kv, x)++ where++ f (Fold _ i _ fin) = do+ r <- i+ case r of+ Partial s -> fin s+ _ -> error "demuxGeneric: unreachable code"++-- | @demux getKey getFold@: In a key value stream, fold values corresponding+-- to each key using a key specific fold. @getFold@ is invoked to generate a+-- key specific fold when a key is encountered for the first time in the+-- stream.+--+-- The first component of the output tuple is a key-value Map of in-progress+-- folds. The fold returns the fold result as the second component of the+-- output tuple whenever a fold terminates.+--+-- If a fold terminates, another instance of the fold is started upon receiving+-- an input with that key, @getFold@ is invoked again whenever the key is+-- encountered again.+-- -- This can be used to scan a stream and collect the results from the scan -- output. --+-- Since the fold generator function is monadic we can add folds dynamically.+-- For example, we can maintain a Map of keys to folds in an IORef and lookup+-- the fold from that corresponding to a key. This Map can be changed+-- dynamically, folds for new keys can be added or folds for old keys can be+-- deleted or modified.+--+-- Compare with 'classify', the fold in 'classify' is a static fold.+-- -- /Pre-release/ --+{-# DEPRECATED demux "Use demuxScan instead" #-} {-# INLINE demux #-} demux :: (Monad m, Ord k) => (a -> k)@@ -312,40 +483,66 @@ -> Fold m a (m (Map k b), Maybe (k, b)) demux = demuxGeneric +{-# INLINE demuxUsingMap #-}+demuxUsingMap :: (Monad m, Ord k) =>+ (a -> k)+ -> (k -> m (Maybe (Fold m a b)))+ -> Scanl m a (m (Map k b), Maybe (k, b))+demuxUsingMap = demuxScanGeneric++-- | Scanning variant of 'demuxerToMap'.+--+-- TODO: To drain the final in-progress folds this requires the drain step of+-- Scanl to be streaming.+--+{-# INLINE demuxScan #-}+demuxScan :: (Monad m, Ord k) =>+ (a -> k)+ -> (k -> m (Maybe (Fold m a b)))+ -> Scanl m a (Maybe (k, b))+demuxScan getKey = fmap snd . demuxUsingMap getKey++-- | This is specialized version of 'demuxGeneric' that uses mutable IO cells+-- as fold accumulators for better performance.+{-# DEPRECATED demuxGenericIO "Use demuxScanGenericIO instead" #-} {-# INLINE demuxGenericIO #-} demuxGenericIO :: (MonadIO m, IsMap f, Traversable f) => (a -> Key f) -> (a -> m (Fold m a b)) -> Fold m a (m (f b), Maybe (Key f, b))-demuxGenericIO getKey getFold = fmap extract $ foldlM' step initial+demuxGenericIO getKey getFold =+ Fold (\s a -> Partial <$> step s a) (Partial <$> initial) extract final where initial = return $ Tuple' IsMap.mapEmpty Nothing {-# INLINE initFold #-}- initFold kv (Fold step1 initial1 extract1) (k, a) = do+ initFold kv (Fold step1 initial1 extract1 final1) (k, a) = do res <- initial1 case res of Partial s -> do res1 <- step1 s a case res1 of Partial _ -> do- let fld = Fold step1 (return res1) extract1+ -- XXX Instead of using a Fold type here use a custom+ -- type with an IORef (possibly unboxed) for the+ -- accumulator. That will reduce the allocations.+ let fld = Fold step1 (return res1) extract1 final1 ref <- liftIO $ newIORef fld return $ Tuple' (IsMap.mapInsert k ref kv) Nothing Done b -> return $ Tuple' kv (Just (k, b)) Done b -> return $ Tuple' kv (Just (k, b)) {-# INLINE runFold #-}- runFold kv ref (Fold step1 initial1 extract1) (k, a) = do+ runFold kv ref (Fold step1 initial1 extract1 final1) (k, a) = do res <- initial1 case res of Partial s -> do res1 <- step1 s a case res1 of Partial _ -> do- let fld = Fold step1 (return res1) extract1+ let fld = Fold step1 (return res1) extract1 final1 liftIO $ writeIORef ref fld return $ Tuple' kv Nothing Done b ->@@ -363,20 +560,192 @@ f <- liftIO $ readIORef ref runFold kv ref f (k, a) - extract (Tuple' kv x) = (Prelude.mapM f kv, x)+ extract (Tuple' kv x) = return (Prelude.mapM f kv, x) where f ref = do- (Fold _ i e) <- liftIO $ readIORef ref+ Fold _ i e _ <- liftIO $ readIORef ref r <- i case r of Partial s -> e s- Done b -> return b+ _ -> error "demuxGenericIO: unreachable code" + final (Tuple' kv x) = return (Prelude.mapM f kv, x)++ where++ f ref = do+ Fold _ i _ fin <- liftIO $ readIORef ref+ r <- i+ case r of+ Partial s -> fin s+ _ -> error "demuxGenericIO: unreachable code"++-- | This is a specialized version of 'demuxToContainer' that uses mutable IO cells+-- as fold accumulators for better performance.+{-# INLINE demuxerToContainerIO #-}+demuxerToContainerIO :: (MonadIO m, IsMap f, Traversable f) =>+ (a -> Key f)+ -> (Key f -> m (Maybe (Fold m a b)))+ -> Fold m a (f b)+demuxerToContainerIO getKey getFold =+ Fold (\s a -> Partial <$> step s a) (Partial <$> initial) undefined final++ where++ initial = return $ Tuple' IsMap.mapEmpty IsMap.mapEmpty++ {-# INLINE initFold #-}+ initFold kv kv1 (Fold step1 initial1 _ final1) (k, a) = do+ res <- initial1+ case res of+ Partial s -> do+ res1 <- step1 s a+ case res1 of+ Partial _ -> do+ -- XXX Instead of using a Fold type here use a custom+ -- type with an IORef (possibly unboxed) for the+ -- accumulator. That will reduce the allocations.+ let fld = Fold step1 (return res1) undefined final1+ ref <- liftIO $ newIORef fld+ return $ Tuple' (IsMap.mapInsert k ref kv) kv1+ Done b -> return $ Tuple' kv (IsMap.mapInsert k b kv1)+ Done b -> return $ Tuple' kv (IsMap.mapInsert k b kv1)++ {-# INLINE runFold #-}+ runFold kv kv1 ref (Fold step1 initial1 _ final1) (k, a) = do+ res <- initial1+ case res of+ Partial s -> do+ res1 <- step1 s a+ case res1 of+ Partial _ -> do+ let fld = Fold step1 (return res1) undefined final1+ liftIO $ writeIORef ref fld+ return $ Tuple' kv kv1+ Done b ->+ let r = IsMap.mapDelete k kv+ in return $ Tuple' r (IsMap.mapInsert k b kv1)+ Done _ -> error "demuxGenericIO: unreachable"++ step (Tuple' kv kv1) a = do+ let k = getKey a+ case IsMap.mapLookup k kv of+ Nothing -> do+ res <- getFold k+ case res of+ Nothing -> pure $ Tuple' kv kv1+ Just f -> initFold kv kv1 f (k, a)+ Just ref -> do+ f <- liftIO $ readIORef ref+ runFold kv kv1 ref f (k, a)++ final (Tuple' kv kv1) = do+ r <- Prelude.mapM f kv+ return $ IsMap.mapUnion r kv1++ where++ f ref = do+ Fold _ i _ fin <- liftIO $ readIORef ref+ r <- i+ case r of+ Partial s -> fin s+ _ -> error "demuxGenericIO: unreachable code"++-- | This is a specialized version of 'demux' that uses mutable IO cells as+-- fold accumulators for better performance.+--+-- Keep in mind that the values in the returned Map may be changed by the+-- ongoing fold if you are using those concurrently in another thread.+--+{-# INLINE demuxScanGenericIO #-}+demuxScanGenericIO :: (MonadIO m, IsMap f, Traversable f) =>+ (a -> Key f)+ -> (Key f -> m (Maybe (Fold m a b)))+ -> Scanl m a (m (f b), Maybe (Key f, b))+demuxScanGenericIO getKey getFold =+ Scanl (\s a -> Partial <$> step s a) (Partial <$> initial) extract final++ where++ initial = return $ Tuple' IsMap.mapEmpty Nothing++ {-# INLINE initFold #-}+ initFold kv (Fold step1 initial1 extract1 final1) (k, a) = do+ res <- initial1+ case res of+ Partial s -> do+ res1 <- step1 s a+ case res1 of+ Partial _ -> do+ -- XXX Instead of using a Fold type here use a custom+ -- type with an IORef (possibly unboxed) for the+ -- accumulator. That will reduce the allocations.+ let fld = Fold step1 (return res1) extract1 final1+ ref <- liftIO $ newIORef fld+ return $ Tuple' (IsMap.mapInsert k ref kv) Nothing+ Done b -> return $ Tuple' kv (Just (k, b))+ Done b -> return $ Tuple' kv (Just (k, b))++ {-# INLINE runFold #-}+ runFold kv ref (Fold step1 initial1 extract1 final1) (k, a) = do+ res <- initial1+ case res of+ Partial s -> do+ res1 <- step1 s a+ case res1 of+ Partial _ -> do+ let fld = Fold step1 (return res1) extract1 final1+ liftIO $ writeIORef ref fld+ return $ Tuple' kv Nothing+ Done b ->+ let kv1 = IsMap.mapDelete k kv+ in return $ Tuple' kv1 (Just (k, b))+ Done _ -> error "demuxGenericIO: unreachable"++ step (Tuple' kv _) a = do+ let k = getKey a+ case IsMap.mapLookup k kv of+ Nothing -> do+ res <- getFold k+ case res of+ Nothing -> pure $ Tuple' kv Nothing+ Just f -> initFold kv f (k, a)+ Just ref -> do+ f <- liftIO $ readIORef ref+ runFold kv ref f (k, a)++ extract (Tuple' kv x) = return (Prelude.mapM f kv, x)++ where++ f ref = do+ Fold _ i e _ <- liftIO $ readIORef ref+ r <- i+ case r of+ Partial s -> e s+ _ -> error "demuxGenericIO: unreachable code"++ final (Tuple' kv x) = return (Prelude.mapM f kv, x)++ where++ f ref = do+ Fold _ i _ fin <- liftIO $ readIORef ref+ r <- i+ case r of+ Partial s -> fin s+ _ -> error "demuxGenericIO: unreachable code"+ -- | This is specialized version of 'demux' that uses mutable IO cells as -- fold accumulators for better performance. --+-- Keep in mind that the values in the returned Map may be changed by the+-- ongoing fold if you are using those concurrently in another thread.+--+{-# DEPRECATED demuxIO "Use demuxScanIO instead" #-} {-# INLINE demuxIO #-} demuxIO :: (MonadIO m, Ord k) => (a -> k)@@ -384,6 +753,34 @@ -> Fold m a (m (Map k b), Maybe (k, b)) demuxIO = demuxGenericIO +{-# INLINE demuxUsingMapIO #-}+demuxUsingMapIO :: (MonadIO m, Ord k) =>+ (a -> k)+ -> (k -> m (Maybe (Fold m a b)))+ -> Scanl m a (m (Map k b), Maybe (k, b))+demuxUsingMapIO = demuxScanGenericIO++-- | This is a specialized version of 'demuxScan' that uses mutable IO cells as+-- scan accumulators for better performance.+--+-- TODO: To drain the final in-progress folds this requires the drain step of+-- Scanl to be streaming.+--+{-# INLINE demuxScanIO #-}+demuxScanIO :: (MonadIO m, Ord k) =>+ (a -> k)+ -> (k -> m (Maybe (Fold m a b)))+ -> Scanl m a (Maybe (k, b))+demuxScanIO getKey = fmap snd . demuxUsingMapIO getKey++-- | Fold a key value stream to a key-value Map. If the same key appears+-- multiple times, only the last value is retained.+{-# INLINE kvToMapOverwriteGeneric #-}+kvToMapOverwriteGeneric :: (Monad m, IsMap f) => Fold m (Key f, a) (f a)+kvToMapOverwriteGeneric =+ foldl' (\kv (k, v) -> IsMap.mapInsert k v kv) IsMap.mapEmpty++{-# DEPRECATED demuxToContainer "Use demuxerToContainer instead" #-} {-# INLINE demuxToContainer #-} demuxToContainer :: (Monad m, IsMap f, Traversable f) => (a -> Key f) -> (a -> m (Fold m a b)) -> Fold m a (f b)@@ -400,11 +797,42 @@ -- | This collects all the results of 'demux' in a Map. --+{-# DEPRECATED demuxToMap "Use demuxerToMap instead" #-} {-# INLINE demuxToMap #-} demuxToMap :: (Monad m, Ord k) => (a -> k) -> (a -> m (Fold m a b)) -> Fold m a (Map k b) demuxToMap = demuxToContainer +-- | @demuxerToMap getKey getFold@: In a key value stream, fold values+-- corresponding to each key using a key specific fold. @getFold@ is invoked to+-- generate a key specific fold when a key is encountered for the first time in+-- the stream.+--+-- If a fold terminates, another instance of the fold is started upon receiving+-- an input with that key, @getFold@ is invoked again whenever the key is+-- encountered again.+--+-- This combinator can be used to scan a stream and collect the results from+-- the scan output.+--+-- Since the fold generator function is monadic, folds for new keys can be+-- added dynamically or folds for old keys can be deleted or modified. For+-- example, we can maintain a Map of keys to folds in an IORef and lookup the+-- fold from that corresponding to a key. This Map can be changed dynamically.+--+-- Note that this fold never terminates. Inputs that do not correspond to a+-- fold in the map are dropped.+--+-- Compare with 'classify', the fold in 'classify' is a static fold.+--+-- /Pre-release/+--+{-# INLINE demuxerToMap #-}+demuxerToMap :: (Monad m, Ord k) =>+ (a -> k) -> (k -> m (Maybe (Fold m a b))) -> Fold m a (Map k b)+demuxerToMap = demuxerToContainer++{-# DEPRECATED demuxToContainerIO "Use demuxerToContainerIO instead" #-} {-# INLINE demuxToContainerIO #-} demuxToContainerIO :: (MonadIO m, IsMap f, Traversable f) => (a -> Key f) -> (a -> m (Fold m a b)) -> Fold m a (f b)@@ -421,28 +849,36 @@ -- | Same as 'demuxToMap' but uses 'demuxIO' for better performance. --+{-# DEPRECATED demuxToMapIO "Use demuxerToMapIO instead" #-} {-# INLINE demuxToMapIO #-} demuxToMapIO :: (MonadIO m, Ord k) => (a -> k) -> (a -> m (Fold m a b)) -> Fold m a (Map k b) demuxToMapIO = demuxToContainerIO +-- | Same as 'demuxerToMap' but uses mutable cells for better performance.+--+{-# INLINE demuxerToMapIO #-}+demuxerToMapIO :: (MonadIO m, Ord k) =>+ (a -> k) -> (k -> m (Maybe (Fold m a b))) -> Fold m a (Map k b)+demuxerToMapIO = demuxerToContainerIO+ {-# INLINE demuxKvToContainer #-} demuxKvToContainer :: (Monad m, IsMap f, Traversable f) =>- (Key f -> m (Fold m a b)) -> Fold m (Key f, a) (f b)-demuxKvToContainer f = demuxToContainer fst (\(k, _) -> fmap (lmap snd) (f k))+ (Key f -> m (Maybe (Fold m a b))) -> Fold m (Key f, a) (f b)+demuxKvToContainer f = demuxerToContainer fst (fmap (fmap (lmap snd)) . f) -- | Fold a stream of key value pairs using a function that maps keys to folds. -- -- Definition: ----- >>> demuxKvToMap f = Fold.demuxToContainer fst (Fold.lmap snd . f)+-- >>> demuxKvToMap f = Fold.demuxerToContainer fst (Fold.lmap snd . f) -- -- Example: -- -- >>> import Data.Map (Map) -- >>> :{--- let f "SUM" = return Fold.sum--- f _ = return Fold.product+-- let f "SUM" = return (Just Fold.sum)+-- f _ = return (Just Fold.product) -- input = Stream.fromList [("SUM",1),("PRODUCT",2),("SUM",3),("PRODUCT",4)] -- in Stream.fold (Fold.demuxKvToMap f) input :: IO (Map String Int) -- :}@@ -451,7 +887,7 @@ -- /Pre-release/ {-# INLINE demuxKvToMap #-} demuxKvToMap :: (Monad m, Ord k) =>- (k -> m (Fold m a b)) -> Fold m (k, a) (Map k b)+ (k -> m (Maybe (Fold m a b))) -> Fold m (k, a) (Map k b) demuxKvToMap = demuxKvToContainer ------------------------------------------------------------------------------@@ -464,6 +900,10 @@ -- done then initial would set a flag in the state to ignore the input or -- return an error. +-- XXX Use a Refold m k a b so that we can make the fold key specifc.+-- XXX Is using a function (a -> k) better than using the input (k,a)?++{-# DEPRECATED classifyGeneric "Use classifyScanGeneric instead" #-} {-# INLINE classifyGeneric #-} classifyGeneric :: (Monad m, IsMap f, Traversable f, Ord (Key f)) => -- Note: we need to return the Map itself to display the in-progress values@@ -471,8 +911,8 @@ -- for that use case. We return an action because we want it to be lazy so -- that the downstream consumers can choose to process or discard it. (a -> Key f) -> Fold m a b -> Fold m a (m (f b), Maybe (Key f, b))-classifyGeneric f (Fold step1 initial1 extract1) =- fmap extract $ foldlM' step initial+classifyGeneric f (Fold step1 initial1 extract1 final1) =+ Fold (\s a -> Partial <$> step s a) (Partial <$> initial) extract final where @@ -509,8 +949,124 @@ let kv1 = IsMap.mapDelete k kv in Tuple3' kv1 (Set.insert k set) (Just (k, b)) - extract (Tuple3' kv _ x) = (Prelude.mapM extract1 kv, x)+ extract (Tuple3' kv _ x) = return (Prelude.mapM extract1 kv, x) + final (Tuple3' kv set x) = return (IsMap.mapTraverseWithKey f1 kv, x)++ where++ f1 k s = do+ if Set.member k set+ -- XXX Why are we doing this? If it is in the set then it will not+ -- be in the map and vice-versa.+ then extract1 s+ else final1 s++{-# INLINE toContainer #-}+toContainer :: (Monad m, IsMap f, Traversable f) =>+ (a -> Key f) -> Fold m a b -> Fold m a (f b)+toContainer f (Fold step1 initial1 _ final1) =+ Fold (\s a -> Partial <$> step s a) (Partial <$> initial) undefined final++ where++ initial = return $ Tuple' IsMap.mapEmpty IsMap.mapEmpty++ {-# INLINE initFold #-}+ initFold kv kv1 k a = do+ x <- initial1+ case x of+ Partial s -> do+ r <- step1 s a+ return+ $ case r of+ Partial s1 ->+ Tuple' (IsMap.mapInsert k s1 kv) kv1+ Done b ->+ Tuple' kv (IsMap.mapInsert k b kv1)+ Done b -> return (Tuple' kv (IsMap.mapInsert k b kv1))++ step (Tuple' kv kv1) a = do+ let k = f a+ case IsMap.mapLookup k kv of+ Nothing -> do+ case IsMap.mapLookup k kv1 of+ Nothing -> initFold kv kv1 k a+ Just _ -> return (Tuple' kv kv1)+ Just s -> do+ r <- step1 s a+ return+ $ case r of+ Partial s1 ->+ Tuple' (IsMap.mapInsert k s1 kv) kv1+ Done b ->+ let res = IsMap.mapDelete k kv+ in Tuple' res (IsMap.mapInsert k b kv1)++ final (Tuple' kv kv1) = do+ r <- Prelude.mapM final1 kv+ return $ IsMap.mapUnion r kv1++-- | Scanning variant of 'toContainer'.+--+{-# INLINE classifyScanGeneric #-}+classifyScanGeneric :: (Monad m, IsMap f, Traversable f, Ord (Key f)) =>+ -- Note: we need to return the Map itself to display the in-progress values+ -- e.g. to implement top. We could possibly create a separate abstraction+ -- for that use case. We return an action because we want it to be lazy so+ -- that the downstream consumers can choose to process or discard it.+ (a -> Key f) -> Fold m a b -> Scanl m a (m (f b), Maybe (Key f, b))+classifyScanGeneric f (Fold step1 initial1 extract1 final1) =+ Scanl (\s a -> Partial <$> step s a) (Partial <$> initial) extract final++ where++ initial = return $ Tuple3' IsMap.mapEmpty Set.empty Nothing++ {-# INLINE initFold #-}+ initFold kv set k a = do+ x <- initial1+ case x of+ Partial s -> do+ r <- step1 s a+ return+ $ case r of+ Partial s1 ->+ Tuple3' (IsMap.mapInsert k s1 kv) set Nothing+ Done b ->+ Tuple3' kv set (Just (k, b))+ Done b -> return (Tuple3' kv (Set.insert k set) (Just (k, b)))++ step (Tuple3' kv set _) a = do+ let k = f a+ case IsMap.mapLookup k kv of+ Nothing -> do+ if Set.member k set+ then return (Tuple3' kv set Nothing)+ else initFold kv set k a+ Just s -> do+ r <- step1 s a+ return+ $ case r of+ Partial s1 ->+ Tuple3' (IsMap.mapInsert k s1 kv) set Nothing+ Done b ->+ let kv1 = IsMap.mapDelete k kv+ in Tuple3' kv1 (Set.insert k set) (Just (k, b))++ extract (Tuple3' kv _ x) = return (Prelude.mapM extract1 kv, x)++ final (Tuple3' kv set x) = return (IsMap.mapTraverseWithKey f1 kv, x)++ where++ f1 k s = do+ if Set.member k set+ -- XXX Why are we doing this? If it is in the set then it will not+ -- be in the map and vice-versa.+ then extract1 s+ else final1 s+ -- | Folds the values for each key using the supplied fold. When scanning, as -- soon as the fold is complete, its result is available in the second -- component of the tuple. The first component of the tuple is a snapshot of@@ -520,22 +1076,38 @@ -- -- Definition: ----- >>> classify f fld = Fold.demux f (const fld)+-- >> classify f fld = Fold.demux f (const fld) --+{-# DEPRECATED classify "Use classifyScan instead" #-} {-# INLINE classify #-} classify :: (Monad m, Ord k) => (a -> k) -> Fold m a b -> Fold m a (m (Map k b), Maybe (k, b)) classify = classifyGeneric +{-# INLINE classifyUsingMap #-}+classifyUsingMap :: (Monad m, Ord k) =>+ (a -> k) -> Fold m a b -> Scanl m a (m (Map k b), Maybe (k, b))+classifyUsingMap = classifyScanGeneric++-- XXX Make it consistent with demux.++-- | Scanning variant of 'toMap'.+--+{-# INLINE classifyScan #-}+classifyScan :: (MonadIO m, Ord k) =>+ (a -> k) -> Fold m a b -> Scanl m a (Maybe (k, b))+classifyScan getKey = fmap snd . classifyUsingMap getKey+ -- XXX we can use a Prim IORef if we can constrain the state "s" to be Prim -- -- The code is almost the same as classifyGeneric except the IORef operations. +{-# DEPRECATED classifyGenericIO "Use classifyGenericIO from Scanl module" #-} {-# INLINE classifyGenericIO #-} classifyGenericIO :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) => (a -> Key f) -> Fold m a b -> Fold m a (m (f b), Maybe (Key f, b))-classifyGenericIO f (Fold step1 initial1 extract1) =- fmap extract $ foldlM' step initial+classifyGenericIO f (Fold step1 initial1 extract1 final1) =+ Fold (\s a -> Partial <$> step s a) (Partial <$> initial) extract final where @@ -574,29 +1146,169 @@ in return $ Tuple3' kv1 (Set.insert k set) (Just (k, b)) - extract (Tuple3' kv _ x) =- (Prelude.mapM (\ref -> liftIO (readIORef ref) >>= extract1) kv, x)+ extract (Tuple3' kv _ x) = return (Prelude.mapM g kv, x) + where++ g ref = liftIO (readIORef ref) >>= extract1++ final (Tuple3' kv set x) = return (IsMap.mapTraverseWithKey g kv, x)++ where++ g k ref = do+ s <- liftIO $ readIORef ref+ if Set.member k set+ then extract1 s+ else final1 s++-- XXX we can use a Prim IORef if we can constrain the state "s" to be Prim+--+-- The code is almost the same as classifyGeneric except the IORef operations.++{-# INLINE toContainerIO #-}+toContainerIO :: (MonadIO m, IsMap f, Traversable f) =>+ (a -> Key f) -> Fold m a b -> Fold m a (f b)+toContainerIO f (Fold step1 initial1 _ final1) =+ Fold (\s a -> Partial <$> step s a) (Partial <$> initial) undefined final++ where++ initial = return $ Tuple' IsMap.mapEmpty IsMap.mapEmpty++ {-# INLINE initFold #-}+ initFold kv kv1 k a = do+ x <- initial1+ case x of+ Partial s -> do+ r <- step1 s a+ case r of+ Partial s1 -> do+ ref <- liftIO $ newIORef s1+ return $ Tuple' (IsMap.mapInsert k ref kv) kv1+ Done b ->+ return $ Tuple' kv (IsMap.mapInsert k b kv1)+ Done b -> return (Tuple' kv (IsMap.mapInsert k b kv1))++ step (Tuple' kv kv1) a = do+ let k = f a+ case IsMap.mapLookup k kv of+ Nothing -> do+ case IsMap.mapLookup k kv1 of+ Nothing -> initFold kv kv1 k a+ Just _ -> return $ Tuple' kv kv1+ Just ref -> do+ s <- liftIO $ readIORef ref+ r <- step1 s a+ case r of+ Partial s1 -> do+ liftIO $ writeIORef ref s1+ return $ Tuple' kv kv1+ Done b ->+ let res = IsMap.mapDelete k kv+ in return+ $ Tuple' res (IsMap.mapInsert k b kv1)++ final (Tuple' kv kv1) = do+ r <- Prelude.mapM g kv+ return $ IsMap.mapUnion r kv1++ where++ g ref = liftIO (readIORef ref) >>= final1++-- | Scanning variant of 'classifyGenericIO'.+--+{-# INLINE classifyScanGenericIO #-}+classifyScanGenericIO :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) =>+ (a -> Key f) -> Fold m a b -> Scanl m a (m (f b), Maybe (Key f, b))+classifyScanGenericIO f (Fold step1 initial1 extract1 final1) =+ Scanl (\s a -> Partial <$> step s a) (Partial <$> initial) extract final++ where++ initial = return $ Tuple3' IsMap.mapEmpty Set.empty Nothing++ {-# INLINE initFold #-}+ initFold kv set k a = do+ x <- initial1+ case x of+ Partial s -> do+ r <- step1 s a+ case r of+ Partial s1 -> do+ ref <- liftIO $ newIORef s1+ return $ Tuple3' (IsMap.mapInsert k ref kv) set Nothing+ Done b ->+ return $ Tuple3' kv set (Just (k, b))+ Done b -> return (Tuple3' kv (Set.insert k set) (Just (k, b)))++ step (Tuple3' kv set _) a = do+ let k = f a+ case IsMap.mapLookup k kv of+ Nothing -> do+ if Set.member k set+ then return (Tuple3' kv set Nothing)+ else initFold kv set k a+ Just ref -> do+ s <- liftIO $ readIORef ref+ r <- step1 s a+ case r of+ Partial s1 -> do+ liftIO $ writeIORef ref s1+ return $ Tuple3' kv set Nothing+ Done b ->+ let kv1 = IsMap.mapDelete k kv+ in return+ $ Tuple3' kv1 (Set.insert k set) (Just (k, b))++ extract (Tuple3' kv _ x) = return (Prelude.mapM g kv, x)++ where++ g ref = liftIO (readIORef ref) >>= extract1++ final (Tuple3' kv set x) = return (IsMap.mapTraverseWithKey g kv, x)++ where++ g k ref = do+ s <- liftIO $ readIORef ref+ if Set.member k set+ then extract1 s+ else final1 s+ -- | Same as classify except that it uses mutable IORef cells in the -- Map providing better performance. Be aware that if this is used as a scan, -- the values in the intermediate Maps would be mutable. -- -- Definitions: ----- >>> classifyIO f fld = Fold.demuxIO f (const fld)+-- >> classifyIO f fld = Fold.demuxIO f (const fld) --+{-# DEPRECATED classifyIO "Use classifyScanIO instead" #-} {-# INLINE classifyIO #-} classifyIO :: (MonadIO m, Ord k) => (a -> k) -> Fold m a b -> Fold m a (m (Map k b), Maybe (k, b)) classifyIO = classifyGenericIO --- | Fold a key value stream to a key-value Map. If the same key appears--- multiple times, only the last value is retained.-{-# INLINE kvToMapOverwriteGeneric #-}-kvToMapOverwriteGeneric :: (Monad m, IsMap f) => Fold m (Key f, a) (f a)-kvToMapOverwriteGeneric =- foldl' (\kv (k, v) -> IsMap.mapInsert k v kv) IsMap.mapEmpty+{-# INLINE classifyUsingMapIO #-}+classifyUsingMapIO :: (MonadIO m, Ord k) =>+ (a -> k) -> Fold m a b -> Scanl m a (m (Map k b), Maybe (k, b))+classifyUsingMapIO = classifyScanGenericIO +-- | This is a specialized version of 'classifyScan' that uses mutable IO cells+-- as scan accumulators for better performance.+--+-- TODO: To drain the final in-progress folds this requires the drain step of+-- Scanl to be streaming.+--+{-# INLINE classifyScanIO #-}+classifyScanIO :: (MonadIO m, Ord k) =>+ (a -> k) -> Fold m a b -> Scanl m a (Maybe (k, b))+classifyScanIO getKey = fmap snd . classifyUsingMapIO getKey++{- {-# INLINE toContainer #-} toContainer :: (Monad m, IsMap f, Traversable f, Ord (Key f)) => (a -> Key f) -> Fold m a b -> Fold m a (f b)@@ -610,6 +1322,7 @@ (rmapM getMap $ lmap fst latest) (lmap snd $ catMaybes kvToMapOverwriteGeneric) in postscan classifier aggregator+-} -- | Split the input stream based on a key field and fold each split using the -- given fold. Useful for map/reduce, bucketizing the input in different bins@@ -647,6 +1360,7 @@ (a -> k) -> Fold m a b -> Fold m a (Map k b) toMap = toContainer +{- {-# INLINE toContainerIO #-} toContainerIO :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) => (a -> Key f) -> Fold m a b -> Fold m a (f b)@@ -660,6 +1374,7 @@ (rmapM getMap $ lmap fst latest) (lmap snd $ catMaybes kvToMapOverwriteGeneric) in postscan classifier aggregator+-} -- | Same as 'toMap' but maybe faster because it uses mutable cells as -- fold accumulators in the Map.
+ src/Streamly/Internal/Data/Fold/Exception.hs view
@@ -0,0 +1,199 @@+-- |+-- Module : Streamly.Internal.Data.Fold.Exception+-- Copyright : (c) 2025 Composewell Technologies+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+module Streamly.Internal.Data.Fold.Exception+ (+ -- * Resources+ before+ , bracketIO+ , finallyIO++ -- * Exceptions+ , onException+ )+where++------------------------------------------------------------------------------+-- Imports+------------------------------------------------------------------------------++import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))+import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad.Catch (MonadCatch)+import Streamly.Internal.Data.IOFinalizer (newIOFinalizer, runIOFinalizer)++import qualified Control.Monad.Catch as MC++import Streamly.Internal.Data.Fold.Step+import Streamly.Internal.Data.Fold.Type++------------------------------------------------------------------------------+-- Exceptions+------------------------------------------------------------------------------++{-++-- | Exception handling states of a fold+data HandleExc s f1 f2 = InitDone !s | InitFailed !f1 | StepFailed !f2++-- | @handle initHandler stepHandler fold@ produces a new fold from a given+-- fold. The new fold executes the original @fold@, if an exception occurs+-- when initializing the fold then @initHandler@ is executed and fold resulting+-- from that starts execution. If an exception occurs while executing the+-- @step@ function of a fold then the @stephandler@ is executed and we start+-- executing the fold resulting from that.+--+-- The exception is caught and handled, not rethrown. If the exception handler+-- itself throws an exception that exception is thrown.+--+-- /Internal/+--+{-# INLINE handle #-}+handle :: (MonadCatch m, Exception e)+ => (e -> m (Fold m a b))+ -> (e -> Fold m a b -> m (Fold m a b))+ -> Fold m a b+ -> Fold m a b+handle initH stepH (Fold step1 initial1 extract1) = Fold step initial extract++ where++ initial = fmap InitDone initial1 `MC.catch` (fmap InitFailed . initH)++ step (InitDone s) a =+ let f = Fold step1 (return s) extract1+ in fmap InitDone (step1 s a)+ `MC.catch` (\e -> fmap StepFailed (stepH e f))+ step (InitFailed (Fold step2 initial2 extract2)) a = do+ s <- initial2+ s1 <- step2 s a+ return $ InitFailed $ Fold step2 (return s1) extract2+ step (StepFailed (Fold step2 initial2 extract2)) a = do+ s <- initial2+ s1 <- step2 s a+ return $ StepFailed $ Fold step2 (return s1) extract2++ extract (InitDone s) = extract1 s+ extract (InitFailed (Fold _ initial2 extract2)) = initial2 >>= extract2+ extract (StepFailed (Fold _ initial2 extract2)) = initial2 >>= extract2++-}++-- | @onException action fold@ runs @action@ whenever the fold throws an+-- exception. The action is executed on any exception whether it is in+-- initial, step or extract action of the fold.+--+-- The exception is not caught, simply rethrown. If the @action@ itself+-- throws an exception that exception is thrown instead of the original+-- exception.+--+-- /Internal/+--+{-# INLINE onException #-}+onException :: MonadCatch m => m x -> Fold m a b -> Fold m a b+onException action (Fold step1 initial1 extract1 final1) =+ Fold step initial extract final++ where++ initial = initial1 `MC.onException` action+ step s a = step1 s a `MC.onException` action+ extract s = extract1 s `MC.onException` action+ final s = final1 s `MC.onException` action++-- | @bracketIO before after between@ runs @before@ and invokes @between@ using+-- its output, then runs the fold generated by @between@. If the fold ends+-- normally, due to an exception or if it is garbage collected prematurely then+-- @after@ is run with the output of @before@ as argument.+--+-- If @before@ or @after@ throw an exception that exception is thrown.+--+{-# INLINE bracketIO #-}+bracketIO :: (MonadIO m, MonadCatch m)+ => IO x -> (x -> IO c) -> (x -> Fold m a b) -> Fold m a b+bracketIO bef aft bet = Fold step initial extract final++ where++ initial = do+ r <- liftIO bef+ ref <- liftIO $ newIOFinalizer (aft r)+ case bet r of+ Fold step1 initial1 extract1 final1 -> do+ res <- initial1 `MC.onException` liftIO (runIOFinalizer ref)+ case res of+ Partial s -> do+ let fld1 = Fold step1 (pure (Partial s)) extract1 final1+ pure $ Partial $ Tuple' ref fld1+ Done b -> do+ liftIO $ runIOFinalizer ref+ pure $ Done b++ step (Tuple' ref (Fold step1 initial1 extract1 final1)) a = do+ res <- initial1+ case res of+ Partial s -> do+ s1 <- step1 s a `MC.onException` liftIO (runIOFinalizer ref)+ let fld1 = Fold step1 (pure s1) extract1 final1+ pure $ Partial $ Tuple' ref fld1+ Done b -> do+ liftIO $ runIOFinalizer ref+ pure $ Done b++ extract (Tuple' ref (Fold _ initial1 extract1 _)) = do+ res <- initial1+ case res of+ Partial s -> extract1 s `MC.onException` liftIO (runIOFinalizer ref)+ Done b -> pure b++ final (Tuple' ref (Fold _ initial1 _ final1)) = do+ res <- initial1+ case res of+ Partial s -> do+ val <- final1 s `MC.onException` liftIO (runIOFinalizer ref)+ runIOFinalizer ref+ pure val+ Done b -> pure b++-- | Run a side effect whenever the fold stops normally, aborts due to an+-- exception or is garbage collected.+--+{-# INLINE finallyIO #-}+finallyIO :: (MonadIO m, MonadCatch m) => IO b -> Fold m a b -> Fold m a b+finallyIO aft (Fold step1 initial1 extract1 final1) =+ Fold step initial extract final++ where++ initial = do+ ref <- liftIO $ newIOFinalizer aft+ res <- initial1 `MC.onException` liftIO (runIOFinalizer ref)+ pure $ case res of+ Done b -> Done b+ Partial s -> Partial $ Tuple' ref s++ step (Tuple' ref s) a = do+ res <- step1 s a `MC.onException` liftIO (runIOFinalizer ref)+ pure $ case res of+ Done b -> Done b+ Partial s1 -> Partial $ Tuple' ref s1++ extract (Tuple' ref s) =+ extract1 s `MC.onException` liftIO (runIOFinalizer ref)++ final (Tuple' ref s) = do+ res <- final1 s `MC.onException` liftIO (runIOFinalizer ref)+ liftIO $ runIOFinalizer ref+ pure res+++-- | Run a side effect before the initialization of the fold.+--+{-# INLINE before #-}+before :: Monad m => m x -> Fold m a b -> Fold m a b+before effect (Fold s i e f) = Fold s (effect *> i) e f
src/Streamly/Internal/Data/Fold/Step.hs view
@@ -8,7 +8,7 @@ -- module Streamly.Internal.Data.Fold.Step (- -- * Types+ -- * Step Type Step (..) , mapMStep@@ -28,6 +28,18 @@ -- terminate early whereas we use data constructors. It allows stream fusion in -- contrast to the foldr/build fusion when composing with functions. +-- XXX Change the semantics of Done such that when we return Done, the input is+-- always unused. Then we can include the takeWhile fold as well under folds.+-- This will be a breaking change, so rename "Done" to "Stop" so that users are+-- forced to look at all places where it is used.+--+-- Perhaps we do not need to return the Step type in initial. Instead of+-- returning "Done" in initial we can wait for the next input or invocation of+-- "final". This should simplify the composition of initial considerably.+--+-- Also, rename Partial to Skip, to keep it consistent with Scans/Pipes/Streams.+-- Make Partial a pattern synonym to keep backward compatibility.+ -- | Represents the result of the @step@ of a 'Fold'. 'Partial' returns an -- intermediate state of the fold, the fold step can be called again with the -- state or the driver can use @extract@ on the state to get the result out.@@ -40,7 +52,7 @@ = Partial !s | Done !b --- | 'first' maps over 'Partial' and 'second' maps over 'Done'.+-- | 'first' maps over the fold state and 'second' maps over the fold result. -- instance Bifunctor Step where {-# INLINE bimap #-}
src/Streamly/Internal/Data/Fold/Tee.hs view
@@ -16,7 +16,9 @@ ) where +#if !MIN_VERSION_base(4,18,0) import Control.Applicative (liftA2)+#endif import Streamly.Internal.Data.Fold.Type (Fold) import qualified Streamly.Internal.Data.Fold.Type as Fold@@ -77,7 +79,7 @@ -- | '<>' distributes the input to both the argument 'Tee's and combines their -- outputs using the 'Monoid' instance of the output type. ---instance (Semigroup b, Monoid b, Monad m) => Monoid (Tee m a b) where+instance (Monoid b, Monad m) => Monoid (Tee m a b) where {-# INLINE mempty #-} mempty = pure mempty
src/Streamly/Internal/Data/Fold/Type.hs view
@@ -126,6 +126,25 @@ -- or to not be able to use `take 0` if we have it. Also, applicative and -- monadic composition of folds would not be possible. --+-- == Cleanup Action+--+-- Fold may use other folds in the downstream pipeline. When a fold is done and+-- it wants to terminate it needs to wait for the downstream folds before it+-- returns. For example, if the downstream fold is an async fold we need to+-- wait for the async fold to finish and return the final result.+--+-- To be able to support this use case we need a cleanup action in the fold.+-- The fold gets finalized once the cleanup is called and we can use extract to+-- get the final state/result of the fold.+--+-- Similar to folds we may have a cleanup action in streams as well. Currently,+-- we rely on GC to cleanup the streams, if we use a cleanup action then we can+-- perform cleanup quickly. Also, similar to folds we can also have an+-- "initial" action in streams as well to generate the initial state. It could+-- decouple the initialization of the stream from the first element being+-- pulled. For example, you may want to start a timer at initialization rather+-- than at the first element pull of the stream.+-- -- == Terminating Folds with backtracking -- -- Consider the example of @takeWhile@ operation, it needs to inspect an@@ -191,8 +210,8 @@ -- -- This means: takeWhile, groupBy, wordBy would be implemented as parsers. ----- A proposed design is to use the same Step type with Error in Folds as well--- as Parsers. Folds won't use the Error constructor and even if they use, it+-- A proposed design is to use the same Step type with SError in Folds as well+-- as Parsers. Folds won't use the SError constructor and even if they use, it -- will be equivalent to just throwing an error. They won't have an -- alternative. --@@ -225,7 +244,7 @@ -- would succeed if the condition is satisfied and it would fail otherwise, on -- failure an alternative parser can be used on the same input. ----- We add @Error@ and @Continue@ to the @Step@ type of fold. @Continue@ is to+-- We add @SError@ and @Continue@ to the @Step@ type of fold. @Continue@ is to -- skip producing an output or to backtrack. We also add the ability to -- backtrack in @Partial@ and @Done@.: --@@ -238,7 +257,7 @@ -- Partial Int s -- partial result and how much to backtrack -- | Done Int b -- final result and how much to backtrack -- | Continue Int s -- no result and how much to backtrack--- | Error String -- error+-- | SError String -- error -- -- data Parser a m b = -- forall s. Fold@@ -325,18 +344,16 @@ -- module Streamly.Internal.Data.Fold.Type (- -- * Imports- -- $setup+ module Streamly.Internal.Data.Fold.Step - -- * Types- Step (..)+ -- * Fold Type , Fold (..) -- * Constructors , foldl' , foldlM' , foldl1'- , foldlM1'+ , foldl1M' , foldt' , foldtM' , foldr'@@ -345,11 +362,18 @@ -- * Folds , fromPure , fromEffect+ -- XXX Do refold ops belong to Scanl or Fold? , fromRefold+ , fromScanl , drain , toList+ , toListRev+ -- $toListRev , toStreamK , toStreamKRev+ , genericLength+ , length+ , latest -- * Combinators @@ -359,7 +383,14 @@ -- ** Mapping Input , lmap , lmapM++ -- ** Scanning input , postscan+ , scanl+ , scanlMany+ , postscanl+ , postscanlMaybe+ -- , runScan -- ** Filtering , catMaybes@@ -374,8 +405,13 @@ -- ** Trimming , take , taking+ , takeEndBy_+ , takeEndBy , dropping + -- ** Condition+ , ifThen+ -- ** Sequential application , splitWith -- rename to "append" , split_@@ -403,13 +439,13 @@ , longest -- * Running A Fold- , extractM , reduce , snoc , addOne , snocM , snocl , snoclM+ , finalM , close , isClosed @@ -420,26 +456,38 @@ -- * Deprecated , foldr , serialWith+ , foldlM1'+ , extractM+ , scan+ , scanMany+ , last ) where #include "inline.hs" +#if !MIN_VERSION_base(4,18,0) import Control.Applicative (liftA2)-import Control.Monad ((>=>))+#endif+import Control.Monad ((>=>), void) import Data.Bifunctor (Bifunctor(..)) import Data.Either (fromLeft, fromRight, isLeft, isRight) import Data.Functor.Identity (Identity(..)) import Fusion.Plugin.Types (Fuse(..))-import Streamly.Internal.Data.Fold.Step (Step(..), mapMStep, chainStepM)-import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)-import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))+import Streamly.Internal.Data.Either.Strict (Either'(..)) import Streamly.Internal.Data.Refold.Type (Refold(..))+import Streamly.Internal.Data.Scanl.Type (Scanl(..))+import Streamly.Internal.Data.Tuple.Strict (Tuple'(..)) -import qualified Streamly.Internal.Data.Stream.StreamK.Type as K+-- import qualified Streamly.Internal.Data.Stream.Step as Stream+import qualified Streamly.Internal.Data.StreamK.Type as K+import qualified Streamly.Internal.Data.Scanl.Type as Scanl -import Prelude hiding (concatMap, filter, foldr, map, take)+import Prelude hiding (Foldable(..), concatMap, filter, map, take, scanl, last) +-- Entire module is exported, do not import selectively+import Streamly.Internal.Data.Fold.Step+ #include "DocTestDataFold.hs" ------------------------------------------------------------------------------@@ -450,24 +498,89 @@ -- The type @b@ is the accumulator of the writer. That's the reason the -- default folds in various modules are called "write". --- | The type @Fold m a b@ having constructor @Fold step initial extract@--- represents a fold over an input stream of values of type @a@ to a final--- value of type @b@ in 'Monad' @m@.+-- An alternative to using an "extract" function is to use "Partial s b" style+-- partial value so that we always emit the output value and there is no need+-- to extract. Then extract can be used for cleanup purposes. But in this case+-- in some cases we may need a "Continue" constructor where an output value is+-- not available, this was implicit earlier. Also, "b" should be lazy here so+-- that we do not always compute it even if we do not need it. ----- The fold uses an intermediate state @s@ as accumulator, the type @s@ is--- internal to the specific fold definition. The initial value of the fold--- state @s@ is returned by @initial@. The @step@ function consumes an input--- and either returns the final result @b@ if the fold is done or the next--- intermediate state (see 'Step'). At any point the fold driver can extract--- the result from the intermediate state using the @extract@ function.+-- Partial s b --> extract :: s -> b+-- Continue --> extract :: s -> Maybe b --+-- But keeping 'b' lazy does not let the fold optimize well. It leads to+-- significant regressions in the key-value folds.+--+-- The "final" function complicates combinators that take other folds as+-- argument because we need to call their finalizers at right places. An+-- alternative to reduce this complexity where it is not required is to use a+-- separate type for bracketed folds but then we need to manage the complexity+-- of two different fold types.++-- The "final" function could be (s -> m (Step s b)), like in parsers so that+-- it can be called in a loop to drain the fold.++-- | The type @Fold m a b@ represents a consumer of an input stream of values+-- of type @a@ and returning a final value of type @b@ in 'Monad' @m@. The+-- constructor of a fold is @Fold step initial extract final@.+--+-- The fold uses an internal state of type @s@. The initial value of the state+-- @s@ is created by @initial@. This function is called once and only once+-- before the fold starts consuming input. Any resource allocation can be done+-- in this function.+--+-- The @step@ function is called on each input, it consumes an input and+-- returns the next intermediate state (see 'Step') or the final result @b@ if+-- the fold terminates.+--+-- Folds are no longer used for scanning, please see the 'Streamly.Data.Scanl'+-- module for scanning. The @extract@ operation is no longer used and will be+-- removed in future.+--+-- Before a fold terminates, @final@ is called once and only once (unless the+-- fold terminated in @initial@ itself). Any resources allocated by @initial@+-- can be released in @final@.+--+-- When implementing fold combinators, care should be taken to cleanup any+-- state of the argument folds held by the fold by calling the respective+-- @final@ at all exit points of the fold. Also, @final@ should not be called+-- more than once. Note that if a fold terminates by 'Done' constructor, there+-- is no state to cleanup.+-- -- NOTE: The constructor is not yet released, smart constructors are provided -- to create folds. -- data Fold m a b =- -- | @Fold @ @ step @ @ initial @ @ extract@- forall s. Fold (s -> a -> m (Step s b)) (m (Step s b)) (s -> m b)+ -- XXX Since we have scans now, we can remove the extract function.+ -- XXX initial can be made pure, like in streams, we can add effects by using+ -- bracket like operations.+ -- | @Fold@ @step@ @initial@ @extract@ @final@+ forall s. Fold (s -> a -> m (Step s b)) (m (Step s b)) (s -> m b) (s -> m b) +-- XXX Have functions to modify initial, step, final of a fold. That way we+-- won't have to use the constructor in many cases.++{-+-- XXX Change the type to as follows. This takes care of the unfoldMany case+-- where we need to continue in produce mode. Can we keep the same Step as+-- Scanl without impacting the key-value folds?+--+-- Note: this will require a change in Parser type as well for Parser.fromFold+-- to work.+--+data Step s b =+ Consume s+ | Produce s+ | Stop b++data Fold m a b =+ forall s. Fold+ (s -> a -> m (Step s b)) -- consume step+ (m (Step s b)) -- initial+ (s -> m (Step s b)) -- produce step+ (s -> m (Step s b)) -- drain step+-}+ ------------------------------------------------------------------------------ -- Mapping on the output ------------------------------------------------------------------------------@@ -476,7 +589,8 @@ -- {-# INLINE rmapM #-} rmapM :: Monad m => (b -> m c) -> Fold m a b -> Fold m a c-rmapM f (Fold step initial extract) = Fold step1 initial1 (extract >=> f)+rmapM f (Fold step initial extract final) =+ Fold step1 initial1 (extract >=> f) (final >=> f) where @@ -487,6 +601,11 @@ -- Left fold constructors ------------------------------------------------------------------------------ +-- | Convert a left scan to a fold.+{-# INLINE fromScanl #-}+fromScanl :: Scanl m a b -> Fold m a b+fromScanl (Scanl step initial extract final) = Fold step initial extract final+ -- | Make a fold from a left fold style pure step function and initial value of -- the accumulator. --@@ -502,11 +621,7 @@ -- {-# INLINE foldl' #-} foldl' :: Monad m => (b -> a -> b) -> b -> Fold m a b-foldl' step initial =- Fold- (\s a -> return $ Partial $ step s a)- (return (Partial initial))- return+foldl' step = fromScanl . Scanl.mkScanl step -- | Make a fold from a left fold style monadic step function and initial value -- of the accumulator.@@ -520,8 +635,7 @@ -- {-# INLINE foldlM' #-} foldlM' :: Monad m => (b -> a -> m b) -> m b -> Fold m a b-foldlM' step initial =- Fold (\s a -> Partial <$> step s a) (Partial <$> initial) return+foldlM' step = fromScanl . Scanl.mkScanlM step -- | Make a strict left fold, for non-empty streams, using first element as the -- starting value. Returns Nothing if the stream is empty.@@ -529,25 +643,65 @@ -- /Pre-release/ {-# INLINE foldl1' #-} foldl1' :: Monad m => (a -> a -> a) -> Fold m a (Maybe a)-foldl1' step = fmap toMaybe $ foldl' step1 Nothing'-- where-- step1 Nothing' a = Just' a- step1 (Just' x) a = Just' $ step x a+foldl1' = fromScanl . Scanl.mkScanl1 -- | Like 'foldl1\'' but with a monadic step function. -- -- /Pre-release/+{-# DEPRECATED foldlM1' "Please use foldl1M' instead" #-} {-# INLINE foldlM1' #-} foldlM1' :: Monad m => (a -> a -> m a) -> Fold m a (Maybe a)-foldlM1' step = fmap toMaybe $ foldlM' step1 (return Nothing')+foldlM1' = foldl1M' +-- | Like 'foldl1\'' but with a monadic step function.+--+-- /Pre-release/+{-# INLINE foldl1M' #-}+foldl1M' :: Monad m => (a -> a -> m a) -> Fold m a (Maybe a)+foldl1M' = fromScanl . Scanl.mkScanl1M++{-+data FromScan s b = FromScanInit !s | FromScanGo !s !b++-- XXX we can attach a scan on the last fold e.g. "runScan s last". Or run a+-- scan on a fold that supplies a default value?+--+-- If we are pushing a value to a scan and the scan stops we will lose the+-- input. Only those scans that do not use the Stop constructor can be used as+-- folds or with folds? The Stop constructor makes them suitable to be composed+-- with pull based streams, push based folds cannot work with that. Do we need+-- two types of scans then, scans for streams and scans for folds? ScanR and+-- ScanL?++-- | This does not work correctly yet. We lose the last input.+--+{-# INLINE fromScan #-}+fromScan :: Monad m => Scan m a b -> Fold m a (Maybe b)+fromScan (Scan consume initial) =+ Fold fstep (return $ Partial (FromScanInit initial)) fextract fextract+ where - step1 Nothing' a = return $ Just' a- step1 (Just' x) a = Just' <$> step x a+ fstep (FromScanInit ss) a = do+ r <- consume ss a+ return $ case r of+ Stream.Yield b s -> Partial (FromScanGo s b)+ Stream.Skip s -> Partial (FromScanInit s)+ -- XXX We have lost the input here.+ -- XXX Need to change folds to always return Done on the next input+ Stream.Stop -> Done Nothing+ fstep (FromScanGo ss acc) a = do+ r <- consume ss a+ return $ case r of+ Stream.Yield b s -> Partial (FromScanGo s b)+ Stream.Skip s -> Partial (FromScanGo s acc)+ -- XXX We have lost the input here.+ Stream.Stop -> Done (Just acc) + fextract (FromScanInit _) = return Nothing+ fextract (FromScanGo _ acc) = return (Just acc)+-}+ ------------------------------------------------------------------------------ -- Right fold constructors ------------------------------------------------------------------------------@@ -569,7 +723,7 @@ -- {-# INLINE foldr' #-} foldr' :: Monad m => (a -> b -> b) -> b -> Fold m a b-foldr' f z = fmap ($ z) $ foldl' (\g x -> g . f x) id+foldr' f = fromScanl . Scanl.mkScanr f {-# DEPRECATED foldr "Please use foldr' instead." #-} {-# INLINE foldr #-}@@ -590,8 +744,7 @@ -- /Pre-release/ {-# INLINE foldrM' #-} foldrM' :: Monad m => (a -> b -> m b) -> m b -> Fold m a b-foldrM' g z =- rmapM (z >>=) $ foldlM' (\f x -> return $ g x >=> f) (return return)+foldrM' g = fromScanl . Scanl.mkScanrM g ------------------------------------------------------------------------------ -- General fold constructors@@ -618,8 +771,7 @@ -- {-# INLINE foldt' #-} foldt' :: Monad m => (s -> a -> Step s b) -> Step s b -> (s -> b) -> Fold m a b-foldt' step initial extract =- Fold (\s a -> return $ step s a) (return initial) (return . extract)+foldt' step initial = fromScanl . Scanl.mkScant step initial -- | Make a terminating fold with an effectful step function and initial state, -- and a state extraction function.@@ -632,21 +784,21 @@ -- {-# INLINE foldtM' #-} foldtM' :: (s -> a -> m (Step s b)) -> m (Step s b) -> (s -> m b) -> Fold m a b-foldtM' = Fold+foldtM' step initial extract = Fold step initial extract extract ------------------------------------------------------------------------------ -- Refold ------------------------------------------------------------------------------ -- This is similar to how we run an Unfold to generate a Stream. A Fold is like--- a Stream and a Fold2 is like an Unfold.+-- a Stream and a Refold is like an Unfold. -- -- | Make a fold from a consumer. -- -- /Internal/ fromRefold :: Refold m c a b -> c -> Fold m a b fromRefold (Refold step inject extract) c =- Fold step (inject c) extract+ Fold step (inject c) extract extract ------------------------------------------------------------------------------ -- Basic Folds@@ -660,8 +812,12 @@ -- {-# INLINE drain #-} drain :: Monad m => Fold m a ()-drain = foldl' (\_ _ -> ()) ()+drain = fromScanl Scanl.drain +------------------------------------------------------------------------------+-- To Containers+------------------------------------------------------------------------------+ -- | Folds the input stream to a list. -- -- /Warning!/ working on large lists accumulated as buffers in memory could be@@ -672,8 +828,27 @@ -- {-# INLINE toList #-} toList :: Monad m => Fold m a [a]-toList = foldr' (:) []+toList = fromScanl Scanl.toList +-- $toListRev+-- This is more efficient than 'Streamly.Internal.Data.Fold.toList'. toList is+-- exactly the same as reversing the list after 'toListRev'.++-- | Buffers the input stream to a list in the reverse order of the input.+--+-- Definition:+--+-- >>> toListRev = Fold.foldl' (flip (:)) []+--+-- /Warning!/ working on large lists accumulated as buffers in memory could be+-- very inefficient, consider using "Streamly.Array" instead.+--++-- xn : ... : x2 : x1 : []+{-# INLINE toListRev #-}+toListRev :: Monad m => Fold m a [a]+toListRev = foldl' (flip (:)) []+ -- | Buffers the input stream to a pure stream in the reverse order of the -- input. --@@ -687,7 +862,7 @@ -- xn : ... : x2 : x1 : [] {-# INLINE toStreamKRev #-} toStreamKRev :: Monad m => Fold m a (K.StreamK n a)-toStreamKRev = foldl' (flip K.cons) K.nil+toStreamKRev = fromScanl Scanl.toStreamKRev -- | A fold that buffers its input to a pure stream. --@@ -697,8 +872,45 @@ -- /Internal/ {-# INLINE toStreamK #-} toStreamK :: Monad m => Fold m a (K.StreamK n a)-toStreamK = foldr K.cons K.nil+toStreamK = fromScanl Scanl.toStreamK +-- | Like 'length', except with a more general 'Num' return value+--+-- Definition:+--+-- >>> genericLength = fmap getSum $ Fold.foldMap (Sum . const 1)+-- >>> genericLength = Fold.foldl' (\n _ -> n + 1) 0+--+-- /Pre-release/+{-# INLINE genericLength #-}+genericLength :: (Monad m, Num b) => Fold m a b+genericLength = fromScanl Scanl.genericLength++-- | Determine the length of the input stream.+--+-- Definition:+--+-- >>> length = Fold.genericLength+-- >>> length = fmap getSum $ Fold.foldMap (Sum . const 1)+--+{-# INLINE length #-}+length :: Monad m => Fold m a Int+length = fromScanl Scanl.length++-- | Returns the latest element of the input stream, if any.+--+-- >>> latest = Fold.foldl1' (\_ x -> x)+-- >>> latest = fmap getLast $ Fold.foldMap (Last . Just)+--+{-# INLINE latest #-}+latest :: Monad m => Fold m a (Maybe a)+latest = fromScanl Scanl.latest++{-# DEPRECATED last "Please use 'latest' instead." #-}+{-# INLINE last #-}+last :: Monad m => Fold m a (Maybe a)+last = latest+ ------------------------------------------------------------------------------ -- Instances ------------------------------------------------------------------------------@@ -706,7 +918,8 @@ -- | Maps a function on the output of the fold (the type @b@). instance Functor m => Functor (Fold m a) where {-# INLINE fmap #-}- fmap f (Fold step1 initial1 extract) = Fold step initial (fmap2 f extract)+ fmap f (Fold step1 initial1 extract final) =+ Fold step initial (fmap2 f extract) (fmap2 f final) where @@ -741,7 +954,7 @@ -- {-# INLINE fromPure #-} fromPure :: Applicative m => b -> Fold m a b-fromPure b = Fold undefined (pure $ Done b) pure+fromPure b = Fold undefined (pure $ Done b) pure pure -- | Make a fold that yields the result of the supplied effectful action -- without consuming any further input.@@ -750,7 +963,7 @@ -- {-# INLINE fromEffect #-} fromEffect :: Applicative m => m b -> Fold m a b-fromEffect b = Fold undefined (Done <$> b) pure+fromEffect b = Fold undefined (Done <$> b) pure pure {-# ANN type SeqFoldState Fuse #-} data SeqFoldState sl f sr = SeqFoldL !sl | SeqFoldR !f !sr@@ -775,16 +988,20 @@ -- complexity, because each composition adds a new branch that each subsequent -- fold's input element has to traverse, therefore, it cannot scale to a large -- number of compositions. After around 100 compositions the performance starts--- dipping rapidly compared to a CPS style implementation. When you need--- scaling use parser monad instead.+-- dipping rapidly compared to a CPS style implementation. --+-- For larger number of compositions you can convert the fold to a parser and+-- use ParserK.+-- -- /Time: O(n^2) where n is the number of compositions./ -- {-# INLINE splitWith #-} splitWith :: Monad m => (a -> b -> c) -> Fold m x a -> Fold m x b -> Fold m x c-splitWith func (Fold stepL initialL extractL) (Fold stepR initialR extractR) =- Fold step initial extract+splitWith func+ (Fold stepL initialL _ finalL)+ (Fold stepR initialR _ finalR) =+ Fold step initial extract final where @@ -801,13 +1018,18 @@ step (SeqFoldL st) a = runL (stepL st a) step (SeqFoldR f st) a = runR (stepR st a) f - extract (SeqFoldR f sR) = fmap f (extractR sR)- extract (SeqFoldL sL) = do- rL <- extractL sL+ -- XXX splitWith should not be used for scanning+ -- It would rarely make sense and resource tracking and cleanup would be+ -- expensive. especially when multiple splitWith are chained.+ extract _ = error "splitWith: cannot be used for scanning"++ final (SeqFoldR f sR) = fmap f (finalR sR)+ final (SeqFoldL sL) = do+ rL <- finalL sL res <- initialR fmap (func rL) $ case res of- Partial sR -> extractR sR+ Partial sR -> finalR sR Done rR -> return rR {-# DEPRECATED serialWith "Please use \"splitWith\" instead" #-}@@ -827,8 +1049,8 @@ -- {-# INLINE split_ #-} split_ :: Monad m => Fold m x a -> Fold m x b -> Fold m x b-split_ (Fold stepL initialL _) (Fold stepR initialR extractR) =- Fold step initial extract+split_ (Fold stepL initialL _ finalL) (Fold stepR initialR _ finalR) =+ Fold step initial extract final where @@ -851,11 +1073,16 @@ resR <- stepR st a return $ first SeqFoldR_ resR - extract (SeqFoldR_ sR) = extractR sR- extract (SeqFoldL_ _) = do+ -- XXX split_ should not be used for scanning+ -- See splitWith for more details.+ extract _ = error "split_: cannot be used for scanning"++ final (SeqFoldR_ sR) = finalR sR+ final (SeqFoldL_ sL) = do+ _ <- finalL sL res <- initialR case res of- Partial sR -> extractR sR+ Partial sR -> finalR sR Done rR -> return rR -- | 'Applicative' form of 'splitWith'. Split the input serially over two@@ -903,8 +1130,10 @@ -- {-# INLINE teeWith #-} teeWith :: Monad m => (a -> b -> c) -> Fold m x a -> Fold m x b -> Fold m x c-teeWith f (Fold stepL initialL extractL) (Fold stepR initialR extractR) =- Fold step initial extract+teeWith f+ (Fold stepL initialL extractL finalL)+ (Fold stepR initialR extractR finalR) =+ Fold step initial extract final where @@ -931,6 +1160,10 @@ extract (TeeLeft bR sL) = (`f` bR) <$> extractL sL extract (TeeRight bL sR) = f bL <$> extractR sR + final (TeeBoth sL sR) = f <$> finalL sL <*> finalR sR+ final (TeeLeft bR sL) = (`f` bR) <$> finalL sL+ final (TeeRight bL sR) = f bL <$> finalR sR+ {-# ANN type TeeFstState Fuse #-} data TeeFstState sL sR b = TeeFstBoth !sL !sR@@ -943,8 +1176,10 @@ {-# INLINE teeWithFst #-} teeWithFst :: Monad m => (b -> c -> d) -> Fold m a b -> Fold m a c -> Fold m a d-teeWithFst f (Fold stepL initialL extractL) (Fold stepR initialR extractR) =- Fold step initial extract+teeWithFst f+ (Fold stepL initialL extractL finalL)+ (Fold stepR initialR extractR finalR) =+ Fold step initial extract final where @@ -963,7 +1198,7 @@ Done bl -> do Done . f bl <$> case resR of- Partial sr -> extractR sr+ Partial sr -> finalR sr Done br -> return br initial = runBoth initialL initialR@@ -974,6 +1209,9 @@ extract (TeeFstBoth sL sR) = f <$> extractL sL <*> extractR sR extract (TeeFstLeft bR sL) = (`f` bR) <$> extractL sL + final (TeeFstBoth sL sR) = f <$> finalL sL <*> finalR sR+ final (TeeFstLeft bR sL) = (`f` bR) <$> finalL sL+ -- | Like 'teeWith' but terminates as soon as any one of the two folds -- terminates. --@@ -982,8 +1220,10 @@ {-# INLINE teeWithMin #-} teeWithMin :: Monad m => (b -> c -> d) -> Fold m a b -> Fold m a c -> Fold m a d-teeWithMin f (Fold stepL initialL extractL) (Fold stepR initialR extractR) =- Fold step initial extract+teeWithMin f+ (Fold stepL initialL extractL finalL)+ (Fold stepR initialR extractR finalR) =+ Fold step initial extract final where @@ -995,12 +1235,12 @@ Partial sl -> do case resR of Partial sr -> return $ Partial $ Tuple' sl sr- Done br -> Done . (`f` br) <$> extractL sl+ Done br -> Done . (`f` br) <$> finalL sl Done bl -> do Done . f bl <$> case resR of- Partial sr -> extractR sr+ Partial sr -> finalR sr Done br -> return br initial = runBoth initialL initialR@@ -1009,6 +1249,8 @@ extract (Tuple' sL sR) = f <$> extractL sL <*> extractR sR + final (Tuple' sL sR) = f <$> finalL sL <*> finalR sR+ -- | Shortest alternative. Apply both folds in parallel but choose the result -- from the one which consumed least input i.e. take the shortest succeeding -- fold.@@ -1020,8 +1262,8 @@ -- {-# INLINE shortest #-} shortest :: Monad m => Fold m x a -> Fold m x b -> Fold m x (Either a b)-shortest (Fold stepL initialL extractL) (Fold stepR initialR _) =- Fold step initial extract+shortest (Fold stepL initialL extractL finalL) (Fold stepR initialR _ finalR) =+ Fold step initial extract final where @@ -1029,10 +1271,16 @@ runBoth actionL actionR = do resL <- actionL resR <- actionR- return $- case resL of- Partial sL -> bimap (Tuple' sL) Right resR- Done bL -> Done $ Left bL+ case resL of+ Partial sL ->+ case resR of+ Partial sR -> return $ Partial $ Tuple' sL sR+ Done bR -> finalL sL >> return (Done (Right bR))+ Done bL -> do+ case resR of+ Partial sR -> void (finalR sR)+ Done _ -> return ()+ return (Done (Left bL)) initial = runBoth initialL initialR @@ -1040,6 +1288,8 @@ extract (Tuple' sL _) = Left <$> extractL sL + final (Tuple' sL sR) = Left <$> finalL sL <* finalR sR+ {-# ANN type LongestState Fuse #-} data LongestState sL sR = LongestBoth !sL !sR@@ -1057,8 +1307,10 @@ -- {-# INLINE longest #-} longest :: Monad m => Fold m x a -> Fold m x b -> Fold m x (Either a b)-longest (Fold stepL initialL extractL) (Fold stepR initialR extractR) =- Fold step initial extract+longest+ (Fold stepL initialL _ finalL)+ (Fold stepR initialR _ finalR) =+ Fold step initial extract final where @@ -1081,17 +1333,23 @@ step (LongestLeft sL) a = bimap LongestLeft Left <$> stepL sL a step (LongestRight sR) a = bimap LongestRight Right <$> stepR sR a - left sL = Left <$> extractL sL- extract (LongestLeft sL) = left sL- extract (LongestRight sR) = Right <$> extractR sR- extract (LongestBoth sL _) = left sL+ -- XXX Scan with this may not make sense as we cannot determine the longest+ -- until one of them have exhausted.+ extract _ = error $ "longest: scan is not allowed as longest cannot be "+ ++ "determined until one fold has exhausted." -data ConcatMapState m sa a c- = B !sa- | forall s. C (s -> a -> m (Step s c)) !s (s -> m c)+ final (LongestLeft sL) = Left <$> finalL sL+ final (LongestRight sR) = Right <$> finalR sR+ final (LongestBoth sL sR) = Left <$> finalL sL <* finalR sR +data ConcatMapState m sa a b c+ = B !sa (sa -> m b)+ | forall s. C (s -> a -> m (Step s c)) !s (s -> m c) (s -> m c)+ -- | Map a 'Fold' returning function on the result of a 'Fold' and run the--- returned fold. This operation can be used to express data dependencies+-- returned fold. This is akin to an n-ary version of 'splitWith' where the+-- next fold for splitting the input is decided dynamically using the previous+-- result. This operation can be used to express data dependencies -- between fold operations. -- -- Let's say the first element in the stream is a count of the following@@ -1111,43 +1369,47 @@ -- {-# INLINE concatMap #-} concatMap :: Monad m => (b -> Fold m a c) -> Fold m a b -> Fold m a c-concatMap f (Fold stepa initiala extracta) = Fold stepc initialc extractc+concatMap f (Fold stepa initiala _ finala) =+ Fold stepc initialc extractc finalc where initialc = do r <- initiala case r of- Partial s -> return $ Partial (B s)+ Partial s -> return $ Partial (B s finala) Done b -> initInnerFold (f b) - stepc (B s) a = do+ stepc (B s fin) a = do r <- stepa s a case r of- Partial s1 -> return $ Partial (B s1)+ Partial s1 -> return $ Partial (B s1 fin) Done b -> initInnerFold (f b) - stepc (C stepInner s extractInner) a = do+ stepc (C stepInner s extractInner fin) a = do r <- stepInner s a return $ case r of- Partial sc -> Partial (C stepInner sc extractInner)+ Partial sc -> Partial (C stepInner sc extractInner fin) Done c -> Done c - extractc (B s) = do- r <- extracta s- initExtract (f r)- extractc (C _ sInner extractInner) = extractInner sInner+ -- XXX Cannot use for scanning+ extractc _ = error "concatMap: cannot be used for scanning" - initInnerFold (Fold step i e) = do+ initInnerFold (Fold step i e fin) = do r <- i return $ case r of- Partial s -> Partial (C step s e)+ Partial s -> Partial (C step s e fin) Done c -> Done c - initExtract (Fold _ i e) = do+ initFinalize (Fold _ i _ fin) = do r <- i case r of- Partial s -> e s+ Partial s -> fin s Done c -> return c + finalc (B s fin) = do+ r <- fin s+ initFinalize (f r)+ finalc (C _ sInner _ fin) = fin sInner+ ------------------------------------------------------------------------------ -- Mapping on input ------------------------------------------------------------------------------@@ -1166,7 +1428,7 @@ -- {-# INLINE lmap #-} lmap :: (a -> b) -> Fold m b r -> Fold m a r-lmap f (Fold step begin done) = Fold step' begin done+lmap f (Fold step begin done final) = Fold step' begin done final where step' x a = step x (f a) @@ -1174,20 +1436,27 @@ -- {-# INLINE lmapM #-} lmapM :: Monad m => (a -> m b) -> Fold m b r -> Fold m a r-lmapM f (Fold step begin done) = Fold step' begin done+lmapM f (Fold step begin done final) = Fold step' begin done final where step' x a = f a >>= step x +------------------------------------------------------------------------------+-- Scanning+------------------------------------------------------------------------------+ -- | Postscan the input of a 'Fold' to change it in a stateful manner using -- another 'Fold'. -- -- @postscan scanner collector@ -- -- /Pre-release/+{-# DEPRECATED postscan "Please use 'postscanl' instead." #-} {-# INLINE postscan #-} postscan :: Monad m => Fold m a b -> Fold m b c -> Fold m a c-postscan (Fold stepL initialL extractL) (Fold stepR initialR extractR) =- Fold step initial extract+postscan+ (Fold stepL initialL extractL finalL)+ (Fold stepR initialR extractR finalR) =+ Fold step initial extract final where @@ -1198,30 +1467,255 @@ Done bL -> do rR <- stepR sR bL case rR of- Partial sR1 -> Done <$> extractR sR1+ Partial sR1 -> Done <$> finalR sR1 Done bR -> return $ Done bR Partial sL -> do !b <- extractL sL rR <- stepR sR b- return- $ case rR of- Partial sR1 -> Partial (sL, sR1)- Done bR -> Done bR+ case rR of+ Partial sR1 -> return $ Partial (sL, sR1)+ Done bR -> finalL sL >> return (Done bR) initial = do+ rR <- initialR+ case rR of+ Partial sR -> do+ rL <- initialL+ case rL of+ Done _ -> Done <$> finalR sR+ Partial sL -> return $ Partial (sL, sR)+ Done b -> return $ Done b++ -- XXX should use Tuple'+ step (sL, sR) x = runStep (stepL sL x) sR++ extract = extractR . snd++ final (sL, sR) = finalL sL *> finalR sR++{-+{-# INLINE runScanWith #-}+runScanWith :: Monad m => Bool -> Scan m a b -> Fold m b c -> Fold m a c+runScanWith isMany+ (Scan stepL initialL)+ (Fold stepR initialR extractR finalR) =+ Fold step initial extract final++ where++ step (sL, sR) x = do+ rL <- stepL sL x+ case rL of+ StreamD.Yield b sL1 -> do+ rR <- stepR sR b+ case rR of+ Partial sR1 -> return $ Partial (sL1, sR1)+ Done bR -> return (Done bR)+ StreamD.Skip sL1 -> return $ Partial (sL1, sR)+ -- XXX We have dropped the input.+ -- XXX Need same behavior for Stop in Fold so that the driver can+ -- consistently assume it is dropped.+ StreamD.Stop ->+ if isMany+ then return $ Partial (initialL, sR)+ else Done <$> finalR sR++ initial = do r <- initialR- rL <- initialL case r of- Partial sR ->+ Partial sR -> return $ Partial (initialL, sR)+ Done b -> return $ Done b++ extract = extractR . snd++ final = finalR . snd++-- | Scan the input of a 'Fold' to change it in a stateful manner using a+-- 'Scan'. The scan stops as soon as the fold terminates.+--+-- /Pre-release/+{-# INLINE runScan #-}+runScan :: Monad m => Scan m a b -> Fold m b c -> Fold m a c+runScan = runScanWith False+-}++-- | @postscanl scanner collector@ postscans the input of the @collector@ fold+-- to change it in a stateful manner using 'scanner'.+--+-- /Pre-release/+{-# INLINE postscanl #-}+postscanl :: Monad m => Scanl m a b -> Fold m b c -> Fold m a c+postscanl+ (Scanl stepL initialL extractL finalL)+ (Fold stepR initialR _ finalR) =+ Fold step initial undefined final++ where++ {-# INLINE runStep #-}+ runStep actionL sR = do+ rL <- actionL+ case rL of+ Done bL -> do+ rR <- stepR sR bL+ case rR of+ Partial sR1 -> Done <$> finalR sR1+ Done bR -> return $ Done bR+ Partial sL -> do+ !b <- extractL sL+ rR <- stepR sR b+ case rR of+ Partial sR1 -> return $ Partial (sL, sR1)+ Done bR -> finalL sL >> return (Done bR)++ initial = do+ rR <- initialR+ case rR of+ Partial sR -> do+ rL <- initialL case rL of- Done _ -> Done <$> extractR sR+ Done _ -> Done <$> finalR sR Partial sL -> return $ Partial (sL, sR) Done b -> return $ Done b + -- XXX should use Tuple' step (sL, sR) x = runStep (stepL sL x) sR + final (sL, sR) = finalL sL *> finalR sR++-- | Use a 'Maybe' returning left scan for filtering the input of a fold.+--+-- >>> scanlMaybe p f = Fold.postscanl p (Fold.catMaybes f)+--+-- /Pre-release/+{-# INLINE postscanlMaybe #-}+postscanlMaybe :: Monad m => Scanl m a (Maybe b) -> Fold m b c -> Fold m a c+postscanlMaybe f1 f2 = postscanl f1 (catMaybes f2)++{-# INLINE scanWith #-}+scanWith :: Monad m => Bool -> Fold m a b -> Fold m b c -> Fold m a c+scanWith isMany+ (Fold stepL initialL extractL finalL)+ (Fold stepR initialR extractR finalR) =+ Fold step initial extract final++ where++ {-# INLINE runStep #-}+ runStep actionL sR = do+ rL <- actionL+ case rL of+ Done bL -> do+ rR <- stepR sR bL+ case rR of+ Partial sR1 ->+ if isMany+ -- XXX recursive call. If initialL returns Done then it+ -- will not terminate. In that case we should return+ -- error in the beginning itself. And we should remove+ -- this recursion, assuming it won't return Done.+ then runStep initialL sR1+ else Done <$> finalR sR1+ Done bR -> return $ Done bR+ Partial sL -> do+ !b <- extractL sL+ rR <- stepR sR b+ case rR of+ Partial sR1 -> return $ Partial (sL, sR1)+ Done bR -> finalL sL >> return (Done bR)++ initial = do+ r <- initialR+ case r of+ Partial sR -> runStep initialL sR+ Done b -> return $ Done b++ step (sL, sR) x = runStep (stepL sL x) sR+ extract = extractR . snd + final (sL, sR) = finalL sL *> finalR sR++-- | Scan the input of a 'Fold' to change it in a stateful manner using another+-- 'Fold'. The scan stops as soon as the fold terminates.+--+-- /Pre-release/+{-# DEPRECATED scan "Please use 'scanl' instead." #-}+{-# INLINE scan #-}+scan :: Monad m => Fold m a b -> Fold m b c -> Fold m a c+scan = scanWith False++-- XXX This does not fuse beacuse of the recursive step. Need to investigate.+--+-- | Scan the input of a 'Fold' to change it in a stateful manner using another+-- 'Fold'. The scan restarts with a fresh state if the fold terminates.+--+-- /Pre-release/+{-# DEPRECATED scanMany "Please use 'scanlMany' instead." #-}+{-# INLINE scanMany #-}+scanMany :: Monad m => Fold m a b -> Fold m b c -> Fold m a c+scanMany = scanWith True++{-# INLINE scanlWith #-}+scanlWith :: Monad m => Bool -> Scanl m a b -> Fold m b c -> Fold m a c+scanlWith isMany+ (Scanl stepL initialL extractL finalL)+ (Fold stepR initialR _ finalR) =+ Fold step initial undefined final++ where++ {-# INLINE runStep #-}+ runStep actionL sR = do+ rL <- actionL+ case rL of+ Done bL -> do+ rR <- stepR sR bL+ case rR of+ Partial sR1 ->+ if isMany+ -- XXX recursive call. If initialL returns Done then it+ -- will not terminate. In that case we should return+ -- error in the beginning itself. And we should remove+ -- this recursion, assuming it won't return Done.+ then runStep initialL sR1+ else Done <$> finalR sR1+ Done bR -> return $ Done bR+ Partial sL -> do+ !b <- extractL sL+ rR <- stepR sR b+ case rR of+ Partial sR1 -> return $ Partial (sL, sR1)+ Done bR -> finalL sL >> return (Done bR)++ initial = do+ r <- initialR+ case r of+ Partial sR -> runStep initialL sR+ Done b -> return $ Done b++ step (sL, sR) x = runStep (stepL sL x) sR++ final (sL, sR) = finalL sL *> finalR sR++-- | Scan the input of a 'Fold' to change it in a stateful manner using a+-- 'Scanl'. The scan stops as soon as the fold terminates.+--+-- /Pre-release/+{-# INLINE scanl #-}+scanl :: Monad m => Scanl m a b -> Fold m b c -> Fold m a c+scanl = scanlWith False++-- XXX This does not fuse beacuse of the recursive step. Need to investigate.++-- | Scan the input of a 'Fold' to change it in a stateful manner using a+-- 'Scanl'. The scan restarts with a fresh state if it terminates.+--+-- /Pre-release/+{-# INLINE scanlMany #-}+scanlMany :: Monad m => Scanl m a b -> Fold m b c -> Fold m a c+scanlMany = scanlWith True+ ------------------------------------------------------------------------------ -- Filtering ------------------------------------------------------------------------------@@ -1234,7 +1728,7 @@ -- {-# INLINE_NORMAL catMaybes #-} catMaybes :: Monad m => Fold m a b -> Fold m (Maybe a) b-catMaybes (Fold step initial extract) = Fold step1 initial extract+catMaybes (Fold step initial extract final) = Fold step1 initial extract final where @@ -1245,9 +1739,10 @@ -- | Use a 'Maybe' returning fold as a filtering scan. ----- >>> scanMaybe p f = Fold.postscan p (Fold.catMaybes f)+-- >> scanMaybe p f = Fold.postscan p (Fold.catMaybes f) -- -- /Pre-release/+{-# DEPRECATED scanMaybe "Please use 'postscanlMaybe' instead." #-} {-# INLINE scanMaybe #-} scanMaybe :: Monad m => Fold m a (Maybe b) -> Fold m b c -> Fold m a c scanMaybe f1 f2 = postscan f1 (catMaybes f2)@@ -1267,14 +1762,13 @@ -- >>> Stream.fold (Fold.filter (> 5) Fold.sum) $ Stream.fromList [1..10] -- 40 ----- >>> filter p = Fold.scanMaybe (Fold.filtering p) -- >>> filter p = Fold.filterM (return . p) -- >>> filter p = Fold.mapMaybe (\x -> if p x then Just x else Nothing) -- {-# INLINE filter #-} filter :: Monad m => (a -> Bool) -> Fold m a r -> Fold m a r -- filter p = scanMaybe (filtering p)-filter f (Fold step begin done) = Fold step' begin done+filter f (Fold step begin extract final) = Fold step' begin extract final where step' x a = if f a then step x a else return $ Partial x @@ -1285,7 +1779,7 @@ -- {-# INLINE filterM #-} filterM :: Monad m => (a -> m Bool) -> Fold m a r -> Fold m a r-filterM f (Fold step begin done) = Fold step' begin done+filterM f (Fold step begin extract final) = Fold step' begin extract final where step' x a = do use <- f a@@ -1334,36 +1828,11 @@ {-# INLINE taking #-} taking :: Monad m => Int -> Fold m a (Maybe a)-taking n = foldt' step initial extract-- where-- initial =- if n <= 0- then Done Nothing- else Partial (Tuple'Fused n Nothing)-- step (Tuple'Fused i _) a =- if i > 1- then Partial (Tuple'Fused (i - 1) (Just a))- else Done (Just a)-- extract (Tuple'Fused _ r) = r+taking = fromScanl . Scanl.taking {-# INLINE dropping #-} dropping :: Monad m => Int -> Fold m a (Maybe a)-dropping n = foldt' step initial extract-- where-- initial = Partial (Tuple'Fused n Nothing)-- step (Tuple'Fused i _) a =- if i > 0- then Partial (Tuple'Fused (i - 1) Nothing)- else Partial (Tuple'Fused i (Just a))-- extract (Tuple'Fused _ r) = r+dropping = fromScanl . Scanl.dropping -- | Take at most @n@ input elements and fold them using the supplied fold. A -- negative count is treated as 0.@@ -1374,7 +1843,7 @@ {-# INLINE take #-} take :: Monad m => Int -> Fold m a b -> Fold m a b -- take n = scanMaybe (taking n)-take n (Fold fstep finitial fextract) = Fold step initial extract+take n (Fold fstep finitial fextract ffinal) = Fold step initial extract final where @@ -1386,7 +1855,7 @@ s1 = Tuple'Fused i1 s if i1 < n then return $ Partial s1- else Done <$> fextract s+ else Done <$> ffinal s Done b -> return $ Done b initial = finitial >>= next (-1)@@ -1395,6 +1864,100 @@ extract (Tuple'Fused _ r) = fextract r + final (Tuple'Fused _ r) = ffinal r++-- Note: Keep this consistent with S.splitOn. In fact we should eliminate+-- S.splitOn in favor of the fold.+--+-- XXX Use Fold.many instead once it is fixed.+-- > Stream.splitOnSuffix p f = Stream.foldMany (Fold.takeEndBy_ p f)++-- | Like 'takeEndBy' but drops the element on which the predicate succeeds.+--+-- Example:+--+-- >>> input = Stream.fromList "hello\nthere\n"+-- >>> line = Fold.takeEndBy_ (== '\n') Fold.toList+-- >>> Stream.fold line input+-- "hello"+--+-- >>> Stream.fold Fold.toList $ Stream.foldMany line input+-- ["hello","there"]+--+{-# INLINE takeEndBy_ #-}+takeEndBy_ :: Monad m => (a -> Bool) -> Fold m a b -> Fold m a b+-- takeEndBy_ predicate = scanMaybe (takingEndBy_ predicate)+takeEndBy_ predicate (Fold fstep finitial fextract ffinal) =+ Fold step finitial fextract ffinal++ where++ step s a =+ if not (predicate a)+ then fstep s a+ else Done <$> ffinal s++-- Note:+-- > Stream.splitWithSuffix p f = Stream.foldMany (Fold.takeEndBy p f)++-- | Take the input, stop when the predicate succeeds taking the succeeding+-- element as well.+--+-- Example:+--+-- >>> input = Stream.fromList "hello\nthere\n"+-- >>> line = Fold.takeEndBy (== '\n') Fold.toList+-- >>> Stream.fold line input+-- "hello\n"+--+-- >>> Stream.fold Fold.toList $ Stream.foldMany line input+-- ["hello\n","there\n"]+--+{-# INLINE takeEndBy #-}+takeEndBy :: Monad m => (a -> Bool) -> Fold m a b -> Fold m a b+-- takeEndBy predicate = scanMaybe (takingEndBy predicate)+takeEndBy predicate (Fold fstep finitial fextract ffinal) =+ Fold step finitial fextract ffinal++ where++ step s a = do+ res <- fstep s a+ if not (predicate a)+ then return res+ else do+ case res of+ Partial s1 -> Done <$> ffinal s1+ Done b -> return $ Done b++-- Fusible if-then-else++-- | Evaluate a condition, if True then use the first fold otherwise use the+-- second fold.+{-# INLINE ifThen #-}+ifThen :: Monad m => m Bool -> Fold m a b -> Fold m a b -> Fold m a b+ifThen predicate+ (Fold step1 initial1 extract1 final1)+ (Fold step2 initial2 extract2 final2)+ = Fold step initial extract final++ where++ initial = do+ r <- predicate+ if r+ then first Left' <$> initial1+ else first Right' <$> initial2++ step (Left' s) x = first Left' <$> step1 s x+ step (Right' s) x = first Right' <$> step2 s x++ extract (Left' s) = extract1 s+ extract (Right' s) = extract2 s++ final (Left' s) = final1 s+ final (Right' s) = final2 s+ ------------------------------------------------------------------------------ -- Nesting ------------------------------------------------------------------------------@@ -1412,8 +1975,8 @@ -- /Pre-release/ {-# INLINE duplicate #-} duplicate :: Monad m => Fold m a b -> Fold m a (Fold m a b)-duplicate (Fold step1 initial1 extract1) =- Fold step initial (\s -> pure $ Fold step1 (pure $ Partial s) extract1)+duplicate (Fold step1 initial1 extract1 final1) =+ Fold step initial extract final where @@ -1421,6 +1984,11 @@ step s a = second fromPure <$> step1 s a + -- Scanning may be problematic due to multiple finalizations.+ extract = error "duplicate: scanning may be problematic"++ final s = pure $ Fold step1 (pure $ Partial s) extract1 final1+ -- If there were a finalize/flushing action in the stream type that would be -- equivalent to running initialize in Fold. But we do not have a flushing -- action in streams.@@ -1432,9 +2000,9 @@ -- /Pre-release/ {-# INLINE reduce #-} reduce :: Monad m => Fold m a b -> m (Fold m a b)-reduce (Fold step initial extract) = do+reduce (Fold step initial extract final) = do i <- initial- return $ Fold step (return i) extract+ return $ Fold step (return i) extract final -- This is the dual of Stream @cons@. @@ -1444,7 +2012,8 @@ -- /Pre-release/ {-# INLINE snoclM #-} snoclM :: Monad m => Fold m a b -> m a -> Fold m a b-snoclM (Fold fstep finitial fextract) action = Fold fstep initial fextract+snoclM (Fold fstep finitial fextract ffinal) action =+ Fold fstep initial fextract ffinal where @@ -1464,14 +2033,15 @@ -- Example: -- -- >>> import qualified Data.Foldable as Foldable--- >>> Fold.extractM $ Foldable.foldl Fold.snocl Fold.toList [1..3]+-- >>> Fold.finalM $ Foldable.foldl Fold.snocl Fold.toList [1..3] -- [1,2,3] -- -- /Pre-release/ {-# INLINE snocl #-} snocl :: Monad m => Fold m a b -> a -> Fold m a b -- snocl f = snoclM f . return-snocl (Fold fstep finitial fextract) a = Fold fstep initial fextract+snocl (Fold fstep finitial fextract ffinal) a =+ Fold fstep initial fextract ffinal where @@ -1491,12 +2061,12 @@ -- /Pre-release/ {-# INLINE snocM #-} snocM :: Monad m => Fold m a b -> m a -> m (Fold m a b)-snocM (Fold step initial extract) action = do+snocM (Fold step initial extract final) action = do res <- initial r <- case res of Partial fs -> action >>= step fs Done _ -> return res- return $ Fold step (return r) extract+ return $ Fold step (return r) extract final -- Definitions: --@@ -1515,12 +2085,12 @@ -- /Pre-release/ {-# INLINE snoc #-} snoc :: Monad m => Fold m a b -> a -> m (Fold m a b)-snoc (Fold step initial extract) a = do+snoc (Fold step initial extract final) a = do res <- initial r <- case res of Partial fs -> step fs a Done _ -> return res- return $ Fold step (return r) extract+ return $ Fold step (return r) extract final -- | Append a singleton value to the fold. --@@ -1540,31 +2110,48 @@ -- -- >>> extractM = Fold.drive Stream.nil --+-- /Pre-release/+{-# DEPRECATED extractM "Please use finalM instead" #-}+{-# INLINE extractM #-}+extractM :: Monad m => Fold m a b -> m b+extractM (Fold _ initial extract _) = do+ res <- initial+ case res of+ Partial fs -> extract fs+ Done b -> return b++-- | Finalize a fold and extract the accumulated result of the fold.+--+-- Definition:+--+-- >>> finalM = Fold.drive Stream.nil+-- -- Example: ----- >>> Fold.extractM Fold.toList+-- >>> Fold.finalM Fold.toList -- [] -- -- /Pre-release/-{-# INLINE extractM #-}-extractM :: Monad m => Fold m a b -> m b-extractM (Fold _ initial extract) = do+{-# INLINE finalM #-}+finalM :: Monad m => Fold m a b -> m b+finalM (Fold _ initial _ final) = do res <- initial case res of- Partial fs -> extract fs+ Partial fs -> final fs Done b -> return b -- | Close a fold so that it does not accept any more input. {-# INLINE close #-} close :: Monad m => Fold m a b -> Fold m a b-close (Fold _ initial1 extract1) = Fold undefined initial undefined+close (Fold _ initial1 _ final1) =+ Fold undefined initial undefined undefined where initial = do res <- initial1 case res of- Partial s -> Done <$> extract1 s+ Partial s -> Done <$> final1 s Done b -> return $ Done b -- Corresponds to the null check for streams.@@ -1574,7 +2161,7 @@ -- /Pre-release/ {-# INLINE isClosed #-} isClosed :: Monad m => Fold m a b -> m Bool-isClosed (Fold _ initial _) = do+isClosed (Fold _ initial _ _) = do res <- initial return $ case res of Partial _ -> False@@ -1608,8 +2195,10 @@ -- {-# INLINE many #-} many :: Monad m => Fold m a b -> Fold m b c -> Fold m a c-many (Fold sstep sinitial sextract) (Fold cstep cinitial cextract) =- Fold step initial extract+many+ (Fold sstep sinitial sextract sfinal)+ (Fold cstep cinitial cextract cfinal) =+ Fold step initial extract final where @@ -1658,6 +2247,13 @@ Partial s -> cextract s Done b -> return b + final (ManyFirst ss cs) = sfinal ss *> cfinal cs+ final (ManyLoop ss cs) = do+ cres <- sfinal ss >>= cstep cs+ case cres of+ Partial s -> cfinal s+ Done b -> return b+ -- | Like many, but the "first" fold emits an output at the end even if no -- input is received. --@@ -1667,8 +2263,10 @@ -- {-# INLINE manyPost #-} manyPost :: Monad m => Fold m a b -> Fold m b c -> Fold m a c-manyPost (Fold sstep sinitial sextract) (Fold cstep cinitial cextract) =- Fold step initial extract+manyPost+ (Fold sstep sinitial sextract sfinal)+ (Fold cstep cinitial cextract cfinal) =+ Fold step initial extract final where @@ -1704,6 +2302,12 @@ Partial s -> cextract s Done b -> return b + final (Tuple' ss cs) = do+ cres <- sfinal ss >>= cstep cs+ case cres of+ Partial s -> cfinal s+ Done b -> return b+ -- | @groupsOf n split collect@ repeatedly applies the @split@ fold to chunks -- of @n@ items in the input stream and supplies the result to the @collect@ -- fold.@@ -1732,7 +2336,10 @@ -- {-# INLINE refoldMany #-} refoldMany :: Monad m => Fold m a b -> Refold m x b c -> Refold m x a c-refoldMany (Fold sstep sinitial sextract) (Refold cstep cinject cextract) =+refoldMany+ (Fold sstep sinitial sextract _sfinal)+ -- XXX We will need a "final" in refold as well+ (Refold cstep cinject cextract) = Refold step inject extract where@@ -1783,7 +2390,9 @@ -- /Internal/ {-# INLINE refoldMany1 #-} refoldMany1 :: Monad m => Refold m x a b -> Fold m b c -> Refold m x a c-refoldMany1 (Refold sstep sinject sextract) (Fold cstep cinitial cextract) =+refoldMany1+ (Refold sstep sinject sextract)+ (Fold cstep cinitial cextract _cfinal) = Refold step inject extract where@@ -1834,7 +2443,7 @@ {-# INLINE refold #-} refold :: Monad m => Refold m b a c -> Fold m a b -> Fold m a c refold (Refold step inject extract) f =- Fold step (extractM f >>= inject) extract+ Fold step (extractM f >>= inject) extract extract ------------------------------------------------------------------------------ -- morphInner@@ -1844,8 +2453,8 @@ -- -- /Pre-release/ morphInner :: (forall x. m x -> n x) -> Fold m a b -> Fold n a b-morphInner f (Fold step initial extract) =- Fold (\x a -> f $ step x a) (f initial) (f . extract)+morphInner f (Fold step initial extract final) =+ Fold (\x a -> f $ step x a) (f initial) (f . extract) (f . final) -- | Adapt a pure fold to any monad. --
src/Streamly/Internal/Data/Fold/Window.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} -- | -- Module : Streamly.Internal.Data.Fold.Window -- Copyright : (c) 2020 Composewell Technologies@@ -18,10 +19,13 @@ -- For more advanced statistical measures see the @streamly-statistics@ -- package. --- XXX A window fold can be driven either using the Ring.slidingWindow+-- XXX A window fold can be driven either using the RingArray.slidingWindow -- combinator or by zipping nthLast fold and last fold. +-- XXX Deprecate all the functions in this module. These should be scans only.+ module Streamly.Internal.Data.Fold.Window+ {-# DEPRECATED "Please use Streamly.Internal.Data.Scanl instead." #-} ( -- * Incremental Folds -- | Folds of type @Fold m (a, Maybe a) b@ are incremental sliding window@@ -36,47 +40,46 @@ -- window folds by keeping the second element of the input tuple as -- @Nothing@. --- lmap+ windowLmap , cumulative - , rollingMap- , rollingMapM+ , windowRollingMap+ , windowRollingMapM -- ** Sums- , length- , sum- , sumInt- , powerSum- , powerSumFrac+ , windowLength+ , windowSum+ , windowSumInt+ , windowPowerSum+ , windowPowerSumFrac -- ** Location- , minimum- , maximum- , range- , mean+ , windowMinimum+ , windowMaximum+ , windowRange+ , windowMean ) where import Control.Monad.IO.Class (MonadIO (liftIO)) import Data.Bifunctor(bimap)-import Foreign.Storable (Storable, peek)+import Data.Proxy (Proxy(..))+import Streamly.Internal.Data.RingArray (RingArray(..))+import Streamly.Internal.Data.Unbox (Unbox(..)) import Streamly.Internal.Data.Fold.Type (Fold(..), Step(..)) import Streamly.Internal.Data.Tuple.Strict (Tuple'(..), Tuple3Fused' (Tuple3Fused')) import qualified Streamly.Internal.Data.Fold.Type as Fold-import qualified Streamly.Internal.Data.Ring.Unboxed as Ring+import qualified Streamly.Internal.Data.MutArray.Type as MutArray+import qualified Streamly.Internal.Data.RingArray as RingArray+-- import qualified Streamly.Internal.Data.Scanl.Type as Scanl import Prelude hiding (length, sum, minimum, maximum) --- $setup--- >>> import Data.Bifunctor(bimap)--- >>> import qualified Streamly.Data.Fold as Fold--- >>> import qualified Streamly.Internal.Data.Fold.Window as FoldW--- >>> import qualified Streamly.Internal.Data.Ring.Unboxed as Ring--- >>> import qualified Streamly.Data.Stream as Stream--- >>> import Prelude hiding (length, sum, minimum, maximum)+#include "ArrayMacros.h"+#include "DocTestDataFold.hs" ------------------------------------------------------------------------------- -- Utilities@@ -85,11 +88,12 @@ -- | Map a function on the incoming as well as outgoing element of a rolling -- window fold. --+-- >>> :set -fno-warn-deprecations -- >>> lmap f = Fold.lmap (bimap f (f <$>)) ---{-# INLINE lmap #-}-lmap :: (c -> a) -> Fold m (a, Maybe a) b -> Fold m (c, Maybe c) b-lmap f = Fold.lmap (bimap f (f <$>))+{-# INLINE windowLmap #-}+windowLmap :: (c -> a) -> Fold m (a, Maybe a) b -> Fold m (c, Maybe c) b+windowLmap f = Fold.lmap (bimap f (f <$>)) -- | Convert an incremental fold to a cumulative fold using the entire input -- stream as a single window.@@ -105,10 +109,10 @@ -- | Apply an effectful function on the latest and the oldest element of the -- window.-{-# INLINE rollingMapM #-}-rollingMapM :: Monad m =>+{-# INLINE windowRollingMapM #-}+windowRollingMapM :: Monad m => (Maybe a -> a -> m (Maybe b)) -> Fold m (a, Maybe a) (Maybe b)-rollingMapM f = Fold.foldlM' f1 initial+windowRollingMapM f = Fold.foldlM' f1 initial where @@ -118,12 +122,12 @@ -- | Apply a pure function on the latest and the oldest element of the window. ----- >>> rollingMap f = FoldW.rollingMapM (\x y -> return $ f x y)+-- >>> windowRollingMap f = Fold.windowRollingMapM (\x y -> return $ f x y) ---{-# INLINE rollingMap #-}-rollingMap :: Monad m =>+{-# INLINE windowRollingMap #-}+windowRollingMap :: Monad m => (Maybe a -> a -> Maybe b) -> Fold m (a, Maybe a) (Maybe b)-rollingMap f = Fold.foldl' f1 initial+windowRollingMap f = Fold.foldl' f1 initial where @@ -136,7 +140,7 @@ ------------------------------------------------------------------------------- -- XXX Overflow.---+ -- | The sum of all the elements in a rolling window. The input elements are -- required to be intergal numbers. --@@ -146,9 +150,9 @@ -- -- /Internal/ ---{-# INLINE sumInt #-}-sumInt :: forall m a. (Monad m, Integral a) => Fold m (a, Maybe a) a-sumInt = Fold step initial extract+{-# INLINE windowSumInt #-}+windowSumInt :: forall m a. (Monad m, Integral a) => Fold m (a, Maybe a) a+windowSumInt = Fold step initial extract extract where @@ -164,14 +168,14 @@ extract = return -- XXX Overflow.---+ -- | Sum of all the elements in a rolling window: -- -- \(S = \sum_{i=1}^n x_{i}\) -- -- This is the first power sum. ----- >>> sum = powerSum 1+-- >>> windowSum = Fold.windowPowerSum 1 -- -- Uses Kahan-Babuska-Neumaier style summation for numerical stability of -- floating precision arithmetic.@@ -180,9 +184,9 @@ -- -- /Time/: \(\mathcal{O}(n)\) ---{-# INLINE sum #-}-sum :: forall m a. (Monad m, Num a) => Fold m (a, Maybe a) a-sum = Fold step initial extract+{-# INLINE windowSum #-}+windowSum :: forall m a. (Monad m, Num a) => Fold m (a, Maybe a) a+windowSum = Fold step initial extract extract where @@ -217,11 +221,11 @@ -- -- This is the \(0\)th power sum. ----- >>> length = powerSum 0+-- >>> length = Fold.windowPowerSum 0 ---{-# INLINE length #-}-length :: (Monad m, Num b) => Fold m (a, Maybe a) b-length = Fold.foldl' step 0+{-# INLINE windowLength #-}+windowLength :: (Monad m, Num b) => Fold m (a, Maybe a) b+windowLength = Fold.foldl' step 0 where @@ -232,29 +236,40 @@ -- -- \(S_k = \sum_{i=1}^n x_{i}^k\) ----- >>> powerSum k = lmap (^ k) sum+-- >>> windowPowerSum k = Fold.windowLmap (^ k) Fold.windowSum -- -- /Space/: \(\mathcal{O}(1)\) -- -- /Time/: \(\mathcal{O}(n)\)-{-# INLINE powerSum #-}-powerSum :: (Monad m, Num a) => Int -> Fold m (a, Maybe a) a-powerSum k = lmap (^ k) sum+{-# INLINE windowPowerSum #-}+windowPowerSum :: (Monad m, Num a) => Int -> Fold m (a, Maybe a) a+windowPowerSum k = windowLmap (^ k) windowSum -- | Like 'powerSum' but powers can be negative or fractional. This is slower -- than 'powerSum' for positive intergal powers. ----- >>> powerSumFrac p = lmap (** p) sum+-- >>> windowPowerSumFrac p = Fold.windowLmap (** p) Fold.windowSum ---{-# INLINE powerSumFrac #-}-powerSumFrac :: (Monad m, Floating a) => a -> Fold m (a, Maybe a) a-powerSumFrac p = lmap (** p) sum+{-# INLINE windowPowerSumFrac #-}+windowPowerSumFrac :: (Monad m, Floating a) => a -> Fold m (a, Maybe a) a+windowPowerSumFrac p = windowLmap (** p) windowSum ------------------------------------------------------------------------------- -- Location ------------------------------------------------------------------------------- --- XXX Remove MonadIO constraint+{-# INLINE ringRange #-}+ringRange :: (MonadIO m, Unbox a, Ord a) => RingArray a -> m (Maybe (a, a))+-- Ideally this should perform the same as the implementation below, but it is+-- 2x worse, need to investigate why.+-- ringRange = RingArray.fold (Fold.fromScanl Scanl.range)+ringRange rb@RingArray{..} = do+ if ringSize == 0+ then return Nothing+ else do+ x <- liftIO $ peekAt 0 ringContents+ let accum (mn, mx) a = return (min mn a, max mx a)+ in fmap Just $ RingArray.foldlM' accum (x, x) rb -- | Determine the maximum and minimum in a rolling window. --@@ -265,44 +280,40 @@ -- -- /Time/: \(\mathcal{O}(n*w)\) where \(w\) is the window size. ---{-# INLINE range #-}-range :: (MonadIO m, Storable a, Ord a) => Int -> Fold m a (Maybe (a, a))-range n = Fold step initial extract+{-# INLINE windowRange #-}+windowRange :: forall m a. (MonadIO m, Unbox a, Ord a) => Int -> Fold m a (Maybe (a, a))+-- windowRange =+ -- Fold.fromScanl . RingArray.scanFoldRingsBy (Fold.fromScanl Scanl.range)+-- Ideally this should perform the same as the implementation below which is+-- just expanded form of this. Some inlining/exitify optimization makes this+-- perform much worse. Need to investigate and fix that.+-- windowRange = Fold.fromScanl . RingArray.scanCustomFoldRingsBy ringRange+windowRange n = Fold step initial extract extract where - -- XXX Use Ring unfold and then fold for composing maximum and minimum to- -- get the range.- initial = if n <= 0- then error "range: window size must be > 0"- else- let f (a, b) = Partial $ Tuple3Fused' a b (0 :: Int)- in fmap f $ liftIO $ Ring.new n-- step (Tuple3Fused' rb rh i) a = do- rh1 <- liftIO $ Ring.unsafeInsert rb rh a- return $ Partial $ Tuple3Fused' rb rh1 (i + 1)+ then error "ringsOf: window size must be > 0"+ else do+ arr :: MutArray.MutArray a <- liftIO $ MutArray.emptyOf n+ return $ Partial $ Tuple3Fused' (MutArray.arrContents arr) 0 0 - -- XXX We need better Ring array APIs so that we can unfold the ring to a- -- stream and fold the stream using a fold of our choice.- --- -- We could just scan the stream to get a stream of ring buffers and then- -- map required folds over those, but we need to be careful that all those- -- rings refer to the same mutable ring, therefore, downstream needs to- -- process those strictly before it can change.- foldFunc i- | i < n = Ring.unsafeFoldRingM- | otherwise = Ring.unsafeFoldRingFullM+ step (Tuple3Fused' mba rh i) a = do+ RingArray _ _ rh1 <- RingArray.replace_ (RingArray mba (n * SIZE_OF(a)) rh) a+ return $ Partial $ Tuple3Fused' mba rh1 (i + 1) - extract (Tuple3Fused' rb rh i) =- if i == 0- then return Nothing- else do- x <- liftIO $ peek rh- let accum (mn, mx) a = return (min mn a, max mx a)- fmap Just $ foldFunc i rh accum (x, x) rb+ -- XXX exitify optimization causes a problem here when modular folds are+ -- used. Sometimes inlining "extract" is helpful.+ -- {-# INLINE extract #-}+ extract (Tuple3Fused' mba rh i) =+ -- XXX If newest is lower than the current min than new is the min.+ -- XXX If exiting one was equal to min only then we need to find new min+ -- XXX We can supply a custom extract function to a generic window+ -- operation.+ let rs = min i n * SIZE_OF(a)+ rh1 = if i <= n then 0 else rh+ in ringRange $ RingArray mba rs rh1 -- | Find the minimum element in a rolling window. --@@ -316,9 +327,11 @@ -- -- /Time/: \(\mathcal{O}(n*w)\) where \(w\) is the window size. ---{-# INLINE minimum #-}-minimum :: (MonadIO m, Storable a, Ord a) => Int -> Fold m a (Maybe a)-minimum n = fmap (fmap fst) $ range n+{-# INLINE windowMinimum #-}+windowMinimum :: (MonadIO m, Unbox a, Ord a) => Int -> Fold m a (Maybe a)+windowMinimum n = fmap (fmap fst) $ windowRange n+-- windowMinimum =+ -- Fold.fromScanl . RingArray.scanFoldRingsBy (Fold.fromScanl Scanl.minimum) -- | The maximum element in a rolling window. --@@ -329,9 +342,11 @@ -- -- /Time/: \(\mathcal{O}(n*w)\) where \(w\) is the window size. ---{-# INLINE maximum #-}-maximum :: (MonadIO m, Storable a, Ord a) => Int -> Fold m a (Maybe a)-maximum n = fmap (fmap snd) $ range n+{-# INLINE windowMaximum #-}+windowMaximum :: (MonadIO m, Unbox a, Ord a) => Int -> Fold m a (Maybe a)+windowMaximum n = fmap (fmap snd) $ windowRange n+-- windowMaximum =+ -- Fold.fromScanl . RingArray.scanFoldRingsBy (Fold.fromScanl Scanl.maximum) -- | Arithmetic mean of elements in a sliding window: --@@ -341,11 +356,11 @@ -- sliding window and Cumulative Moving Avergae (CMA) when used on the entire -- stream. ----- >>> mean = Fold.teeWith (/) sum length+-- >>> mean = Fold.teeWith (/) Fold.windowSum Fold.windowLength -- -- /Space/: \(\mathcal{O}(1)\) -- -- /Time/: \(\mathcal{O}(n)\)-{-# INLINE mean #-}-mean :: forall m a. (Monad m, Fractional a) => Fold m (a, Maybe a) a-mean = Fold.teeWith (/) sum length+{-# INLINE windowMean #-}+windowMean :: forall m a. (Monad m, Fractional a) => Fold m (a, Maybe a) a+windowMean = Fold.teeWith (/) windowSum windowLength
src/Streamly/Internal/Data/IOFinalizer.hs view
@@ -66,6 +66,9 @@ -- never runs again. Note, the finalizing action runs with async exceptions -- masked. --+-- If this function is called multiple times, the action is guaranteed to run+-- once and only once.+-- -- /Pre-release/ runIOFinalizer :: MonadIO m => IOFinalizer -> m () runIOFinalizer (IOFinalizer ref) = liftIO $ do@@ -83,9 +86,16 @@ -- | Run an action clearing the finalizer atomically wrt async exceptions. The -- action is run with async exceptions masked. --+-- This function can be called at most once after setting the finalizer. If the+-- finalizer is not set it is considered a bug.+-- -- /Pre-release/ clearingIOFinalizer :: MonadIO m => IOFinalizer -> IO a -> m a clearingIOFinalizer (IOFinalizer ref) action = do liftIO $ mask_ $ do- writeIORef ref Nothing- action+ res <- readIORef ref+ case res of+ Just _ -> do+ writeIORef ref Nothing+ action+ Nothing -> error "clearingIOFinalizer: finalizer not set"
+ src/Streamly/Internal/Data/IORef.hs view
@@ -0,0 +1,114 @@+-- |+-- Module : Streamly.Internal.Data.IORef+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- A mutable variable in a mutation capable monad (IO) holding a 'Unboxed'+-- 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 'IORef' 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.Data.IORef+ (+ IORef++ -- Construction+ , newIORef++ -- Write+ , writeIORef+ , modifyIORef'++ -- Read+ , readIORef+ , pollGenericIORef+ , pollIORefInt+ )+where++#include "inline.hs"+#include "deprecation.h"++import Control.Monad.IO.Class (MonadIO(..))+#if __GLASGOW_HASKELL__ >= 810+import Data.Kind (Type)+#endif+import Data.Proxy (Proxy(..))+import Streamly.Internal.Data.MutByteArray.Type (MutByteArray)+import Streamly.Internal.Data.Unbox (Unbox(..), sizeOf)++import qualified Streamly.Internal.Data.MutByteArray.Type as MBA+import qualified Streamly.Internal.Data.Stream.Type as D++-- | An 'IORef' holds a single 'Unbox'-able value.+#if __GLASGOW_HASKELL__ >= 810+type IORef :: Type -> Type+#endif+newtype IORef a = IORef MutByteArray++-- | Create a new 'IORef'.+--+-- /Pre-release/+{-# INLINE newIORef #-}+newIORef :: forall a. Unbox a => a -> IO (IORef a)+newIORef x = do+ var <- MBA.new (sizeOf (Proxy :: Proxy a))+ pokeAt 0 var x+ return $ IORef var++-- | Write a value to an 'IORef'.+--+-- /Pre-release/+{-# INLINE writeIORef #-}+writeIORef :: Unbox a => IORef a -> a -> IO ()+writeIORef (IORef var) = pokeAt 0 var++-- | Read a value from an 'IORef'.+--+-- /Pre-release/+{-# INLINE readIORef #-}+readIORef :: Unbox a => IORef a -> IO a+readIORef (IORef var) = peekAt 0 var++-- | Modify the value of an 'IORef' using a function with strict application.+--+-- /Pre-release/+{-# INLINE modifyIORef' #-}+modifyIORef' :: Unbox a => IORef a -> (a -> a) -> IO ()+modifyIORef' var g = do+ x <- readIORef var+ writeIORef var (g x)++-- | Internal, do not use.+{-# INLINE_NORMAL pollGenericIORef #-}+pollGenericIORef :: (MonadIO m, Unbox a) => IORef a -> D.Stream m a+pollGenericIORef var = D.Stream step ()++ where++ {-# INLINE_LATE step #-}+ step _ () = liftIO (readIORef var) >>= \x -> return $ D.Yield x ()++-- | Generate a stream by continuously reading the IORef.+--+-- This operation reads the IORef without any synchronization. It can be+-- assumed to be atomic because the size fits into machine register size. We+-- are assuming that compiler uses single instructions to access the memory. It+-- may read stale values though until caches are synchronised in a+-- multiprocessor architecture.+--+-- /Pre-release/+{-# INLINE_NORMAL pollIORefInt #-}+pollIORefInt :: MonadIO m => IORef Int -> D.Stream m Int+pollIORefInt = pollGenericIORef
− src/Streamly/Internal/Data/IORef/Unboxed.hs
@@ -1,100 +0,0 @@--- |--- Module : Streamly.Internal.Data.IORef.Unboxed--- Copyright : (c) 2019 Composewell Technologies------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ A mutable variable in a mutation capable monad (IO) holding a 'Unboxed'--- 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 'IORef' 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.Data.IORef.Unboxed- (- IORef-- -- * Construction- , newIORef-- -- * Write- , writeIORef- , modifyIORef'-- -- * Read- , readIORef- , toStreamD- )-where--#include "inline.hs"--import Data.Proxy (Proxy(..))-import Control.Monad.IO.Class (MonadIO(..))-import Streamly.Internal.Data.Unboxed- ( MutableByteArray(..)- , Unbox- , sizeOf- , peekWith- , pokeWith- , newUnpinnedBytes- )--import qualified Streamly.Internal.Data.Stream.StreamD.Type as D---- | An 'IORef' holds a single 'Unbox'-able value.-newtype IORef a = IORef MutableByteArray---- | Create a new 'IORef'.------ /Pre-release/-{-# INLINE newIORef #-}-newIORef :: forall a. Unbox a => a -> IO (IORef a)-newIORef x = do- var <- newUnpinnedBytes (sizeOf (Proxy :: Proxy a))- pokeWith var 0 x- return $ IORef var---- | Write a value to an 'IORef'.------ /Pre-release/-{-# INLINE writeIORef #-}-writeIORef :: Unbox a => IORef a -> a -> IO ()-writeIORef (IORef var) = pokeWith var 0---- | Read a value from an 'IORef'.------ /Pre-release/-{-# INLINE readIORef #-}-readIORef :: Unbox a => IORef a -> IO a-readIORef (IORef var) = peekWith var 0---- | Modify the value of an 'IORef' using a function with strict application.------ /Pre-release/-{-# INLINE modifyIORef' #-}-modifyIORef' :: Unbox a => IORef a -> (a -> a) -> IO ()-modifyIORef' var g = do- x <- readIORef var- writeIORef var (g x)---- | Generate a stream by continuously reading the IORef.------ /Pre-release/-{-# INLINE_NORMAL toStreamD #-}-toStreamD :: (MonadIO m, Unbox a) => IORef a -> D.Stream m a-toStreamD var = D.Stream step ()-- where-- {-# INLINE_LATE step #-}- step _ () = liftIO (readIORef var) >>= \x -> return $ D.Yield x ()
src/Streamly/Internal/Data/IsMap.hs view
@@ -1,3 +1,7 @@+{-# LANGUAGE TypeFamilies #-}+-- Must come after TypeFamilies, otherwise it is re-enabled.+-- MonoLocalBinds enabled by TypeFamilies causes perf regressions in general.+{-# LANGUAGE NoMonoLocalBinds #-} -- | -- Module : Streamly.Internal.Data.IsMap -- Copyright : (c) 2022 Composewell Technologies@@ -28,6 +32,8 @@ mapDelete :: Key f -> f a -> f a mapUnion :: f a -> f a -> f a mapNull :: f a -> Bool+ mapTraverseWithKey ::+ Applicative t => (Key f -> a -> t b) -> f a -> t (f b) instance Ord k => IsMap (Map k) where type Key (Map k) = k@@ -39,6 +45,7 @@ mapDelete = Map.delete mapUnion = Map.union mapNull = Map.null+ mapTraverseWithKey = Map.traverseWithKey instance IsMap IntMap.IntMap where type Key IntMap.IntMap = Int@@ -50,3 +57,4 @@ mapDelete = IntMap.delete mapUnion = IntMap.union mapNull = IntMap.null+ mapTraverseWithKey = IntMap.traverseWithKey
− src/Streamly/Internal/Data/List.hs
@@ -1,169 +0,0 @@-{-# LANGUAGE UndecidableInstances #-}---- |--- Module : Streamly.Internal.Data.List--- Copyright : (c) 2018 Composewell Technologies------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : pre-release--- Portability : GHC------ Lists are just a special case of monadic streams. The stream type @Stream--- Identity a@ can be used as a replacement for @[a]@. The 'List' type in this--- module is just a newtype wrapper around @Stream Identity@ for better type--- inference when using the 'OverloadedLists' GHC extension. @List a@ provides--- better performance compared to @[a]@. Standard list, string and list--- comprehension syntax can be used with the 'List' type by enabling--- 'OverloadedLists', 'OverloadedStrings' and 'MonadComprehensions' GHC--- extensions. There would be a slight difference in the 'Show' and 'Read'--- strings of streamly list as compared to regular lists.------ Conversion to stream types is free, any stream combinator can be used on--- lists by converting them to streams. However, for convenience, this module--- provides combinators that work directly on the 'List' type.--------- @--- List $ S.map (+ 1) $ toStream (1 \`Cons\` Nil)--- @------ To convert a 'List' to regular lists, you can use any of the following:------ * @toList . toStream@ and @toStream . fromList@--- * 'Data.Foldable.toList' from "Data.Foldable"--- * 'GHC.Exts.toList' and 'GHC.Exts.fromList' from 'IsList' in "GHC.Exts"------ If you have made use of 'Nil' and 'Cons' constructors in the code and you--- want to replace streamly lists with standard lists, all you need to do is--- import these definitions:------ @--- type List = []--- pattern Nil <- [] where Nil = []--- pattern Cons x xs = x : xs--- infixr 5 `Cons`--- {-\# COMPLETE Cons, Nil #-}--- @------ See <src/docs/streamly-vs-lists.md> for more details and--- <src/test/PureStreams.hs> for comprehensive usage examples.----module Streamly.Internal.Data.List- (- List (Nil, Cons)-- , toStream- , fromStream-- -- XXX we may want to use rebindable syntax for variants instead of using- -- different types (applicative do and apWith).- , ZipList (..)- , fromZipList- , toZipList- )-where--import Control.Arrow (second)-import Data.Functor.Identity (Identity, runIdentity)-import GHC.Exts (IsList(..), IsString(..))-import Streamly.Internal.Data.Stream.Cross (CrossStream(..))-import Streamly.Internal.Data.Stream.Type (Stream)-import Streamly.Internal.Data.Stream.Zip (ZipStream(..))-import Text.Read (readPrec)--import qualified Streamly.Internal.Data.Stream.StreamK.Type as K-import qualified Streamly.Internal.Data.Stream.Type as Stream---- XXX Rename to PureStream.---- | @List a@ is a replacement for @[a]@.------ /Pre-release/-newtype List a = List { toCrossStream :: CrossStream Identity a }- deriving- ( Eq, Ord- , Semigroup, Monoid, Functor, Foldable- , Applicative, Traversable, Monad, IsList)--toStream :: List a -> Stream Identity a-toStream = unCrossStream . toCrossStream--fromStream :: Stream Identity a -> List a-fromStream xs = List (CrossStream xs)--instance (a ~ Char) => IsString (List a) where- {-# INLINE fromString #-}- fromString = List . fromList--instance Show a => Show (List a) where- show (List x) = show $ unCrossStream x--instance Read a => Read (List a) where- readPrec = fromStream <$> readPrec----------------------------------------------------------------------------------- Patterns----------------------------------------------------------------------------------- Note: When using the OverloadedLists extension we should be able to pattern--- match using the regular list contructors. OverloadedLists uses 'toList' to--- perform the pattern match, it should not be too bad as it works lazily in--- the Identity monad. We need these patterns only when not using that--- extension.---- | An empty list constructor and pattern that matches an empty 'List'.--- Corresponds to '[]' for Haskell lists.----pattern Nil :: List a-pattern Nil <- (runIdentity . K.null . Stream.toStreamK . toStream -> True)-- where-- Nil = List $ CrossStream (Stream.fromStreamK K.nil)--infixr 5 `Cons`---- | A list constructor and pattern that deconstructs a 'List' into its head--- and tail. Corresponds to ':' for Haskell lists.----pattern Cons :: a -> List a -> List a-pattern Cons x xs <-- (fmap (second (List . CrossStream . Stream.fromStreamK))- . runIdentity . K.uncons . Stream.toStreamK . toStream- -> Just (x, xs)- )-- where-- Cons x xs = List $ CrossStream $ Stream.cons x (toStream xs)--{-# COMPLETE Nil, Cons #-}----------------------------------------------------------------------------------- ZipList----------------------------------------------------------------------------------- | Just like 'List' except that it has a zipping 'Applicative' instance--- and no 'Monad' instance.----newtype ZipList a = ZipList { toZipStream :: ZipStream Identity a }- deriving- ( Show, Read, Eq, Ord- , Semigroup, Monoid, Functor, Foldable- , Applicative, Traversable, IsList- )--instance (a ~ Char) => IsString (ZipList a) where- {-# INLINE fromString #-}- fromString = ZipList . fromList---- | Convert a 'ZipList' to a regular 'List'----fromZipList :: ZipList a -> List a-fromZipList (ZipList zs) = List $ CrossStream (unZipStream zs)---- | Convert a regular 'List' to a 'ZipList'----toZipList :: List a -> ZipList a-toZipList = ZipList . ZipStream . toStream
+ src/Streamly/Internal/Data/MutArray.hs view
@@ -0,0 +1,425 @@+-- |+-- Module : Streamly.Internal.Data.MutArray+-- Copyright : (c) 2020 Composewell Technologies+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC++-- XXX To detect array overflow issues we can have a debug mode in RTS where we+-- allocate one additional page beyond a large allocation and unmap that page+-- so that we get segfault if it is accessed. Also any unpinned large+-- allocations can be kept unmapped for a while after being freed in case those+-- are being used by someone, also we can aggressively move such pages to+-- detect problems more quickly.+--+module Streamly.Internal.Data.MutArray+ (+ -- * MutArray.Type module+ module Streamly.Internal.Data.MutArray.Type+ -- * MutArray module+ , indexerFromLen+ , splitterFromLen+ -- , splitFromLen+ -- , splitChunksOf+ , compactMax+ , compactMax'+ , compactSepByByte_+ , compactEndByByte_+ , compactEndByLn_+ , createOfLast++ -- XXX Do not expose these yet, we should perhaps expose only the Get/Put+ -- monads instead? Decide after implementing the monads.++ -- * Serialization+ , serialize+ , deserialize+ , serializePtrN+ , deserializePtrN++ -- * Deprecated+ , slicerFromLen+ , sliceIndexerFromLen+ , genSlicesFromLen+ , getSlicesFromLen+ , compactLE+ , pinnedCompactLE+ , compactOnByte+ , compactOnByteSuffix+ , IORef+ , newIORef+ , writeIORef+ , modifyIORef'+ , readIORef+ , pollIntIORef+ )+where++#include "assert.hs"+#include "deprecation.h"+#include "inline.hs"+#include "ArrayMacros.h"++import Control.Monad.IO.Class (MonadIO(..))+import Data.Word (Word8)+import Foreign.Ptr (Ptr)+import Streamly.Internal.Data.MutByteArray.Type (PinnedState(..))+import Streamly.Internal.Data.Serialize.Type (Serialize)+import Streamly.Internal.Data.Stream.Type (Stream)+import Streamly.Internal.Data.Unbox (Unbox)+import Streamly.Internal.Data.Unfold.Type (Unfold(..))+import Streamly.Internal.Data.Fold.Type (Fold)++import qualified Streamly.Internal.Data.IORef as IORef+import qualified Streamly.Internal.Data.RingArray as RingArray+import qualified Streamly.Internal.Data.Serialize.Type as Serialize+import qualified Streamly.Internal.Data.Stream.Nesting as Stream+import qualified Streamly.Internal.Data.Stream.Type as Stream+import qualified Streamly.Internal.Data.Fold.Type as Fold+-- import qualified Streamly.Internal.Data.Stream.Transform as Stream+import qualified Streamly.Internal.Data.Unfold as Unfold++import Prelude hiding (foldr, length, read)+import Streamly.Internal.Data.MutArray.Type++-- | Generate a stream of array slice descriptors ((index, len)) of specified+-- length from an array, starting from the supplied array index. The last slice+-- may be shorter than the requested length depending on the array length.+--+-- /Pre-release/+{-# INLINE indexerFromLen #-}+indexerFromLen, sliceIndexerFromLen :: forall m a. (Monad m, Unbox a)+ => Int -- ^ from index+ -> Int -- ^ length of the slice+ -> Unfold m (MutArray a) (Int, Int)+indexerFromLen from len =+ let fromThenTo n = (from, from + len, n - 1)+ mkSlice n i = return (i, min len (n - i))+ in Unfold.lmap length+ $ Unfold.mapM (uncurry mkSlice) . Unfold.carry+ $ Unfold.lmap fromThenTo Unfold.enumerateFromThenTo+RENAME(sliceIndexerFromLen,indexerFromLen)++{-# DEPRECATED genSlicesFromLen "Please use indexerFromLen instead." #-}+genSlicesFromLen :: forall m a. (Monad m, Unbox a)+ => Int -- ^ from index+ -> Int -- ^ length of the slice+ -> Unfold m (MutArray a) (Int, Int)+genSlicesFromLen = indexerFromLen++-- | Generate a stream of slices of specified length from an array, starting+-- from the supplied array index. The last slice may be shorter than the+-- requested length depending on the array length.+--+-- /Pre-release/+{-# INLINE splitterFromLen #-}+splitterFromLen, slicerFromLen :: forall m a. (Monad m, Unbox a)+ => Int -- ^ from index+ -> Int -- ^ length of the slice+ -> Unfold m (MutArray a) (MutArray a)+splitterFromLen from len =+ let mkSlice arr (i, n) = return $ unsafeSliceOffLen i n arr+ in Unfold.mapM (uncurry mkSlice)+ $ Unfold.carry (indexerFromLen from len)+RENAME(slicerFromLen,splitterFromLen)++{-# DEPRECATED getSlicesFromLen "Please use splitterFromLen instead." #-}+getSlicesFromLen :: forall m a. (Monad m, Unbox a)+ => Int -- ^ from index+ -> Int -- ^ length of the slice+ -> Unfold m (MutArray a) (MutArray a)+getSlicesFromLen = splitterFromLen++--------------------------------------------------------------------------------+-- Serialization/Deserialization using Serialize+--------------------------------------------------------------------------------++{-# INLINE unsafeSerialize #-}+unsafeSerialize :: (MonadIO m, Serialize a) =>+ MutArray Word8 -> a -> m (MutArray Word8)+unsafeSerialize (MutArray mbarr start end bound) a = do+#ifdef DEBUG+ let len = Serialize.addSizeTo 0 a+ assertM(bound - end >= len)+#endif+ off <- liftIO $ Serialize.serializeAt end mbarr a+ pure $ MutArray mbarr start off bound++{-# NOINLINE serializeRealloc #-}+serializeRealloc :: forall m a. (MonadIO m, Serialize a) =>+ (Int -> Int)+ -> MutArray Word8+ -> a+ -> m (MutArray Word8)+serializeRealloc sizer arr x = do+ let len = Serialize.addSizeTo 0 x+ arr1 <- liftIO $ reallocBytesWith "serializeRealloc" sizer len arr+ unsafeSerialize arr1 x++{-# INLINE serializeWith #-}+serializeWith :: forall m a. (MonadIO m, Serialize a) =>+ (Int -> Int)+ -> MutArray Word8+ -> a+ -> m (MutArray Word8)+serializeWith sizer arr@(MutArray mbarr start end bound) x = do+ let len = Serialize.addSizeTo 0 x+ if (bound - end) >= len+ then do+ off <- liftIO $ Serialize.serializeAt end mbarr x+ assertM(len <= off)+ pure $ MutArray mbarr start off bound+ -- XXX this will inhibit unboxing?+ else serializeRealloc sizer arr x++-- | Serializes a (Ptr, len) pair in the same way as an array. The serialized+-- value can be de-serialized as an array or consumed as a pointer using+-- deserializePtrN.+--+-- The Ptr must be pinned or the existence of the Ptr must be ensured by the+-- user of this API.+--+-- /Unimplemented/+{-# INLINE serializePtrN #-}+serializePtrN :: -- (MonadIO m) =>+ MutArray Word8 -> Ptr a -> Int -> m (MutArray Word8)+-- assert/error out if Ptr is not pinned. unsafe prefix?+-- First serialize the length and then splice the ptr+serializePtrN _arr _ptr _len = undefined++-- | Consume a serialized array or (Ptr, length) from the MutArray using an IO+-- action that consumes the pointer directly.+--+-- WARNING! The array must be a pinned array.+--+-- /Unimplemented/+{-# INLINE deserializePtrN #-}+deserializePtrN :: -- (MonadIO m) =>+ MutArray Word8 -> (Ptr a -> Int -> m b) -> m (a, MutArray Word8)+-- assert/error out if the array is not pinned. unsafe prefix?+deserializePtrN _arr _action = undefined++-- | Serialize the supplied Haskell value at the end of the mutable array,+-- growing the array size. If there is no reserve capacity left in the array+-- the array is reallocated to double the current size.+--+-- Like 'snoc' except that the value is serialized to the byte array.+--+-- Note: If you are serializing a large number of small fields, and the types+-- are statically known, then it may be more efficient to declare a record of+-- those fields and derive an 'Serialize' instance of the entire record.+--+-- /Unstable API/+{-# INLINE serialize #-}+serialize :: forall m a. (MonadIO m, Serialize a) =>+ MutArray Word8 -> a -> m (MutArray Word8)+serialize = serializeWith f++ where++ f oldSize =+ if isPower2 oldSize+ then oldSize * 2+ else roundUpToPower2 oldSize * 2++-- | Deserialize a Haskell value from the beginning of a mutable array. The+-- deserialized value is removed from the array and the remaining array is+-- returned.+--+-- Like 'uncons' except that the value is deserialized from the byte array.+--+-- Note: If you are deserializing a large number of small fields, and the types+-- are statically known, then it may be more efficient to declare a record of+-- those fields and derive 'Serialize' instance of the entire record.+--+-- /Unstable API/+{-# INLINE deserialize #-}+deserialize :: (MonadIO m, Serialize a) =>+ MutArray Word8 -> m (a, MutArray Word8)+deserialize arr@(MutArray {..}) = do+ let lenArr = byteLength arr+ (off, val) <-+ liftIO $ Serialize.deserializeAt arrStart arrContents (arrStart + lenArr)+ assertM(off <= arrStart + lenArr)+ pure (val, MutArray arrContents off arrEnd arrBound)++-------------------------------------------------------------------------------+-- Compacting Streams of Arrays+-------------------------------------------------------------------------------++-- | @compactLE maxElems@ coalesces adjacent arrays in the input stream+-- only if the combined size would be less than or equal to @maxElems@+-- elements. Note that it won't split an array if the original array is already+-- larger than maxElems.+--+-- @maxElems@ must be greater than 0.+--+-- Generates unpinned arrays irrespective of the pinning status of input+-- arrays.+{-# INLINE compactMax #-}+compactMax, compactLE :: (MonadIO m, Unbox a) =>+ Int -> Stream m (MutArray a) -> Stream m (MutArray a)+-- XXX compactLE can be moved to MutArray/Type if we are not using the parser+-- to implement it.+compactMax = compactLeAs Unpinned+-- The parser version turns out to be a little bit slower.+-- compactLE n = Stream.catRights . Stream.parseManyD (pCompactLE n)++RENAME(compactLE,compactMax)++-- | Like 'compactBySizeLE' but generates pinned arrays.+{-# INLINE_NORMAL compactMax' #-}+compactMax', pinnedCompactLE :: forall m a. (MonadIO m, Unbox a)+ => Int -> Stream m (MutArray a) -> Stream m (MutArray a)+compactMax' = compactLeAs Pinned+-- compactMax' n = Stream.catRights . Stream.parseManyD (pPinnedCompactLE n)++{-# DEPRECATED pinnedCompactLE "Please use compactMax' instead." #-}+{-# INLINE pinnedCompactLE #-}+pinnedCompactLE = compactMax'++data SplitState s arr+ = Initial s+ | Buffering s arr+ | Splitting s arr+ | Yielding arr (SplitState s arr)+ | Finishing++-- | Split a stream of arrays on a given separator byte, dropping the separator+-- and coalescing all the arrays between two separators into a single array.+--+{-# INLINE_NORMAL _compactSepByByteCustom #-}+_compactSepByByteCustom+ :: MonadIO m+ => Word8+ -> Stream m (MutArray Word8)+ -> Stream m (MutArray Word8)+_compactSepByByteCustom byte (Stream.Stream step state) =+ Stream.Stream step' (Initial state)++ where++ {-# INLINE_LATE step' #-}+ step' gst (Initial st) = do+ r <- step gst st+ case r of+ Stream.Yield arr s -> do+ (arr1, marr2) <- breakEndByWord8_ byte arr+ return $ case marr2 of+ Nothing -> Stream.Skip (Buffering s arr1)+ Just arr2 -> Stream.Skip (Yielding arr1 (Splitting s arr2))+ Stream.Skip s -> return $ Stream.Skip (Initial s)+ Stream.Stop -> return Stream.Stop++ step' gst (Buffering st buf) = do+ r <- step gst st+ case r of+ Stream.Yield arr s -> do+ (arr1, marr2) <- breakEndByWord8_ byte arr+ -- XXX Use spliceExp instead and then rightSize?+ buf1 <- splice buf arr1+ return $ case marr2 of+ Nothing -> Stream.Skip (Buffering s buf1)+ Just x -> Stream.Skip (Yielding buf1 (Splitting s x))+ Stream.Skip s -> return $ Stream.Skip (Buffering s buf)+ Stream.Stop -> return $+ if byteLength buf == 0+ then Stream.Stop+ else Stream.Skip (Yielding buf Finishing)++ step' _ (Splitting st buf) = do+ (arr1, marr2) <- breakEndByWord8_ byte buf+ return $ case marr2 of+ Nothing -> Stream.Skip $ Buffering st arr1+ Just arr2 -> Stream.Skip $ Yielding arr1 (Splitting st arr2)++ step' _ (Yielding arr next) = return $ Stream.Yield arr next+ step' _ Finishing = return Stream.Stop++-- XXX implement predicate based version of this compactSepBy_, compactEndBy_+-- XXX the versions that use equality can be named compactSepByElem_ etc. The+-- byte/word etc versions of that can be specialized using rewrite rules.++-- | Split a stream of arrays on a given separator byte, dropping the separator+-- and coalescing all the arrays between two separators into a single array.+--+{-# INLINE compactSepByByte_ #-}+compactSepByByte_, compactOnByte+ :: (MonadIO m)+ => Word8+ -> Stream m (MutArray Word8)+ -> Stream m (MutArray Word8)+-- XXX compare perf of custom vs idiomatic version+-- compactOnByte = _compactOnByteCustom+-- XXX use spliceExp and rightSize?+compactSepByByte_ byte = Stream.splitInnerBy (breakEndByWord8_ byte) splice++RENAME(compactOnByte,compactSepByByte_)++-- | Split a stream of arrays on a given separator byte, dropping the separator+-- and coalescing all the arrays between two separators into a single array.+--+{-# INLINE compactEndByByte_ #-}+compactEndByByte_, compactOnByteSuffix+ :: (MonadIO m)+ => Word8+ -> Stream m (MutArray Word8)+ -> Stream m (MutArray Word8)+compactEndByByte_ byte =+ -- XXX use spliceExp and rightSize?+ Stream.splitInnerBySuffix+ (\arr -> byteLength arr == 0) (breakEndByWord8_ byte) splice++RENAME(compactOnByteSuffix,compactEndByByte_)++-- XXX On windows we should compact on "\r\n". We can just compact on '\n' and+-- drop the last byte in each array if it is '\r'.++-- | Compact byte arrays on newline character, dropping the newline char.+{-# INLINE compactEndByLn_ #-}+compactEndByLn_ :: MonadIO m+ => Stream m (MutArray Word8)+ -> Stream m (MutArray Word8)+compactEndByLn_ = compactEndByByte_ 10++-- | @createOfLast n@ folds a maximum of @n@ elements from the end of the input+-- stream to an 'MutArray'.+--+{-# INLINE createOfLast #-}+createOfLast :: (Unbox a, MonadIO m) => Int -> Fold m a (MutArray a)+createOfLast n =+ Fold.ifThen+ (pure (n <= 0))+ (Fold.fromPure empty)+ (Fold.rmapM RingArray.toMutArray $ RingArray.createOfLast n)++--------------------------------------------------------------------------------+-- IoRef (Deprecated)+--------------------------------------------------------------------------------++{-# DEPRECATED IORef "Use IORef from MutByteArray module." #-}+type IORef = IORef.IORef++{-# DEPRECATED pollIntIORef "Use pollIntIORef from MutByteArray module." #-}+pollIntIORef :: (MonadIO m, Unbox a) => IORef a -> Stream m a+pollIntIORef = IORef.pollGenericIORef++{-# DEPRECATED newIORef "Use newIORef from MutByteArray module." #-}+newIORef :: forall a. Unbox a => a -> IO (IORef a)+newIORef = IORef.newIORef+++{-# DEPRECATED writeIORef "Use writeIORef from MutByteArray module." #-}+writeIORef :: Unbox a => IORef a -> a -> IO ()+writeIORef = IORef.writeIORef+++{-# DEPRECATED modifyIORef' "Use modifyIORef' from MutByteArray module." #-}+modifyIORef' :: Unbox a => IORef a -> (a -> a) -> IO ()+modifyIORef' = IORef.modifyIORef'+++{-# DEPRECATED readIORef "Use readIORef from MutByteArray module." #-}+readIORef :: Unbox a => IORef a -> IO a+readIORef = IORef.readIORef
+ src/Streamly/Internal/Data/MutArray/Generic.hs view
@@ -0,0 +1,981 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE UnboxedTuples #-}+-- |+-- Module : Streamly.Internal.Data.MutArray.Generic+-- Copyright : (c) 2020 Composewell Technologies+-- License : BSD3-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+module Streamly.Internal.Data.MutArray.Generic+(+ -- * Type+ -- $arrayNotes+ MutArray (..)++ -- * Constructing and Writing+ -- ** Construction+ , nil++ -- ** Utils+ , initializeOfFilledUpto++ -- *** Uninitialized Arrays+ , emptyOf+ -- , newArrayWith++ -- *** From streams+ , unsafeCreateOf+ , createOf+ , createWith -- createOfMin/createMin/createGE?+ , create+ , fromStreamN+ , fromStream+ , fromPureStream++ -- , writeRevN+ -- , writeRev++ -- ** From containers+ , fromListN+ , fromList++ -- * Random writes+ , putIndex+ , unsafePutIndex+ , putIndices+ -- , putFromThenTo+ -- , putFrom -- start writing at the given position+ -- , putUpto -- write from beginning up to the given position+ -- , putFromTo+ -- , putFromRev+ -- , putUptoRev+ , unsafeModifyIndex+ , modifyIndex+ -- , modifyIndices+ -- , modify+ -- , swapIndices++ -- * Growing and Shrinking+ -- Arrays grow only at the end, though it is possible to grow on both sides+ -- and therefore have a cons as well as snoc. But that will require two+ -- bounds in the array representation.++ -- ** Reallocation+ , realloc+ , uninit++ -- ** Appending elements+ , snocWith+ , snoc+ -- , snocLinear+ -- , snocMay+ , unsafeSnoc++ -- ** Appending streams+ -- , writeAppendNUnsafe+ -- , writeAppendN+ -- , writeAppendWith+ -- , writeAppend++ -- ** Truncation+ -- These are not the same as slicing the array at the beginning, they may+ -- reduce the length as well as the capacity of the array.+ -- , truncateWith+ -- , truncate+ -- , truncateExp++ -- * Eliminating and Reading++ -- ** Unfolds+ , reader+ -- , readerRev+ , producerWith -- experimental+ , producer -- experimental++ -- ** To containers+ , read+ , readRev+ , toStreamK+ -- , toStreamKRev+ , toList++ -- ** Random reads+ , getIndex+ , unsafeGetIndex+ , unsafeGetIndexWith+ -- , getIndices+ -- , getFromThenTo+ -- , getIndexRev++ -- * Size+ , length++ -- * In-place Mutation Algorithms+ , dropAround+ -- , reverse+ -- , permute+ -- , partitionBy+ -- , shuffleBy+ -- , divideBy+ -- , mergeBy++ -- * Folding+ -- , foldl'+ -- , foldr+ , cmp+ , eq++ -- * Arrays of arrays+ -- We can add dimensionality parameter to the array type to get+ -- multidimensional arrays. Multidimensional arrays would just be a+ -- convenience wrapper on top of single dimensional arrays.++ -- | Operations dealing with multiple arrays, streams of arrays or+ -- multidimensional array representations.++ -- ** Construct from streams+ , chunksOf+ -- , arrayStreamKFromStreamD+ -- , writeChunks++ -- ** Eliminate to streams+ -- , flattenArrays+ -- , flattenArraysRev+ -- , fromArrayStreamK++ -- ** Construct from arrays+ -- get chunks without copying+ , unsafeSliceOffLen+ , sliceOffLen+ -- , getSlicesFromLenN+ -- , splitAt -- XXX should be able to express using sliceOffLen+ -- , breakOn++ -- ** Appending arrays+ -- , spliceCopy+ -- , spliceWith+ -- , splice+ -- , spliceExp+ , unsafePutSlice+ -- , appendSlice+ -- , appendSliceFrom++ , clone++ -- * Deprecated+ , getSlice+ , strip+ , new+ , writeNUnsafe+ , writeN+ , writeWith+ , write+ , getIndexUnsafe+ , getIndexUnsafeWith+ , putIndexUnsafe+ , modifyIndexUnsafe+ , snocUnsafe+ , getSliceUnsafe+ , putSliceUnsafe+ )+where++#include "inline.hs"+#include "deprecation.h"+#include "assert.hs"++import Control.Monad (when)+import Control.Monad.IO.Class (MonadIO(..))+import Data.Functor.Identity (Identity(..))+import GHC.Base+ ( MutableArray#+ , RealWorld+ , copyMutableArray#+ , newArray#+ , readArray#+ , writeArray#+ )+import GHC.IO (IO(..))+import GHC.Int (Int(..))+import Streamly.Internal.Data.Fold.Type (Fold(..))+import Streamly.Internal.Data.Producer.Type (Producer (..))+import Streamly.Internal.Data.Unfold.Type (Unfold(..))+import Streamly.Internal.Data.Stream.Type (Stream)+import Streamly.Internal.Data.SVar.Type (adaptState)++import qualified Streamly.Internal.Data.Fold.Type as FL+import qualified Streamly.Internal.Data.Producer as Producer+import qualified Streamly.Internal.Data.Stream.Type as D+import qualified Streamly.Internal.Data.Stream.Generate as D+import qualified Streamly.Internal.Data.Stream.Lift as D+import qualified Streamly.Internal.Data.StreamK.Type as K++import Prelude hiding (read, length, replicate)++#include "DocTestDataMutArrayGeneric.hs"++-------------------------------------------------------------------------------+-- MutArray Data Type+-------------------------------------------------------------------------------++data MutArray a =+ MutArray+ { arrContents# :: MutableArray# RealWorld a+ -- ^ The internal contents of the array representing the entire array.++ , arrStart :: {-# UNPACK #-}!Int+ -- ^ The starting index of this slice.++ , arrEnd :: {-# UNPACK #-}!Int+ -- ^ The index after the last initialized index.++ , arrBound :: {-# UNPACK #-}!Int+ -- ^ The first invalid index.+ }++{-# INLINE bottomElement #-}+bottomElement :: a+bottomElement =+ error+ $ unwords+ [ funcName+ , "This is the bottom element of the array."+ , "This is a place holder and should never be reached!"+ ]++ where++ funcName = "Streamly.Internal.Data.MutArray.Generic.bottomElement:"++-- XXX Would be nice if GHC can provide something like newUninitializedArray# so+-- that we do not have to write undefined or error in the whole array.++{-# INLINE initializeOfFilledUpto #-}+initializeOfFilledUpto :: MonadIO m => Int -> Int -> a -> m (MutArray a)+initializeOfFilledUpto n@(I# n#) end val =+ liftIO+ $ IO+ $ \s# ->+ case newArray# n# val s# of+ (# s1#, arr# #) ->+ let ma = MutArray arr# 0 end n+ in (# s1#, ma #)++-- | @emptyOf count@ allocates a zero length array that can be extended to hold+-- up to 'count' items without reallocating.+--+-- /Pre-release/+{-# INLINE emptyOf #-}+emptyOf :: MonadIO m => Int -> m (MutArray a)+emptyOf n = initializeOfFilledUpto n 0 bottomElement++{-# DEPRECATED new "Please use emptyOf instead." #-}+{-# INLINE new #-}+new :: MonadIO m => Int -> m (MutArray a)+new = emptyOf++-- XXX This could be pure?++-- |+-- Definition:+--+-- >>> nil = MutArray.emptyOf 0+{-# INLINE nil #-}+nil :: MonadIO m => m (MutArray a)+nil = new 0++-------------------------------------------------------------------------------+-- Random writes+-------------------------------------------------------------------------------++-- | Write the given element to the given index of the 'MutableArray#'. Does not+-- check if the index is out of bounds of the array.+--+-- /Pre-release/+{-# INLINE putIndexUnderlying #-}+putIndexUnderlying :: MonadIO m => Int -> MutableArray# RealWorld a -> a -> m ()+putIndexUnderlying n _arrContents# x =+ liftIO+ $ IO+ $ \s# ->+ case n of+ I# n# ->+ let s1# = writeArray# _arrContents# n# x s#+ in (# s1#, () #)++-- | Write the given element to the given index of the array. Does not check if+-- the index is out of bounds of the array.+--+-- /Pre-release/+{-# INLINE unsafePutIndex #-}+unsafePutIndex, putIndexUnsafe :: forall m a. MonadIO m => Int -> MutArray a -> a -> m ()+unsafePutIndex i arr@(MutArray {..}) x =+ assert (i >= 0 && i < length arr)+ (putIndexUnderlying (i + arrStart) arrContents# x)++invalidIndex :: String -> Int -> a+invalidIndex label i =+ error $ label ++ ": invalid array index " ++ show i++-- | /O(1)/ Write the given element at the given index in the array.+-- Performs in-place mutation of the array.+--+-- >>> putIndex ix arr val = MutArray.modifyIndex ix arr (const (val, ()))+--+-- /Pre-release/+{-# INLINE putIndex #-}+putIndex :: MonadIO m => Int -> MutArray a -> a -> m ()+putIndex i arr x =+ if i >= 0 && i < length arr+ then unsafePutIndex i arr x+ else invalidIndex "putIndex" i++-- | Write an input stream of (index, value) pairs to an array. Throws an+-- error if any index is out of bounds.+--+-- /Pre-release/+{-# INLINE putIndices #-}+putIndices :: MonadIO m+ => MutArray a -> Fold m (Int, a) ()+putIndices arr = FL.foldlM' step (return ())++ where++ step () (i, x) = putIndex i arr x++-- | Modify a given index of an array using a modifier function without checking+-- the bounds.+--+-- Unsafe because it does not check the bounds of the array.+--+-- /Pre-release/+unsafeModifyIndex, modifyIndexUnsafe :: MonadIO m => Int -> MutArray a -> (a -> (a, b)) -> m b+unsafeModifyIndex i MutArray {..} f = do+ liftIO+ $ IO+ $ \s# ->+ case i + arrStart of+ I# n# ->+ case readArray# arrContents# n# s# of+ (# s1#, a #) ->+ let (a1, b) = f a+ s2# = writeArray# arrContents# n# a1 s1#+ in (# s2#, b #)++-- | Modify a given index of an array using a modifier function.+--+-- /Pre-release/+modifyIndex :: MonadIO m => Int -> MutArray a -> (a -> (a, b)) -> m b+modifyIndex i arr f = do+ if i >= 0 && i < length arr+ then unsafeModifyIndex i arr f+ else invalidIndex "modifyIndex" i++-------------------------------------------------------------------------------+-- Resizing+-------------------------------------------------------------------------------++-- | Reallocates the array according to the new size. This is a safe function+-- that always creates a new array and copies the old array into the new one.+-- If the reallocated size is less than the original array it results in a+-- truncated version of the original array.+--+realloc :: MonadIO m => Int -> MutArray a -> m (MutArray a)+realloc n arr = do+ arr1 <- new n+ let !newLen@(I# newLen#) = min n (length arr)+ !(I# arrS#) = arrStart arr+ !(I# arr1S#) = arrStart arr1+ arrC# = arrContents# arr+ arr1C# = arrContents# arr1+ !newEnd = arrStart arr1 + newLen+ !newBound = arrStart arr1 + n+ liftIO+ $ IO+ $ \s# ->+ let s1# = copyMutableArray# arrC# arrS# arr1C# arr1S# newLen# s#+ in (# s1#, arr1 {arrEnd = newEnd, arrBound = newBound} #)++reallocWith ::+ MonadIO m => String -> (Int -> Int) -> Int -> MutArray a -> m (MutArray a)+reallocWith label sizer reqSize arr = do+ let oldSize = length arr+ newSize = sizer oldSize+ safeSize = max newSize (oldSize + reqSize)+ assert (newSize >= oldSize + reqSize || error badSize) (return ())+ realloc safeSize arr++ where++ badSize = concat+ [ label+ , ": new array size is less than required size "+ , show reqSize+ , ". Please check the sizing function passed."+ ]++-------------------------------------------------------------------------------+-- Snoc+-------------------------------------------------------------------------------++-- XXX Not sure of the behavior of writeArray# if we specify an index which is+-- out of bounds. This comment should be rewritten based on that.+-- | Really really unsafe, appends the element into the first array, may+-- cause silent data corruption or if you are lucky a segfault if the index+-- is out of bounds.+--+-- /Internal/+{-# INLINE unsafeSnoc #-}+snocUnsafe, unsafeSnoc :: MonadIO m => MutArray a -> a -> m (MutArray a)+unsafeSnoc arr@(MutArray{..}) x = do+ let newEnd = arrEnd + 1+ putIndexUnderlying arrEnd arrContents# x+ return $ arr {arrEnd = newEnd}++-- NOINLINE to move it out of the way and not pollute the instruction cache.+{-# NOINLINE snocWithRealloc #-}+snocWithRealloc :: MonadIO m => (Int -> Int) -> MutArray a -> a -> m (MutArray a)+snocWithRealloc sizer arr x = do+ arr1 <- reallocWith "snocWithRealloc" sizer 1 arr+ unsafeSnoc arr1 x++-- | @snocWith sizer arr elem@ mutates @arr@ to append @elem@. The length of+-- the array increases by 1.+--+-- If there is no reserved space available in @arr@ it is reallocated to a size+-- in bytes determined by the @sizer oldSize@ function, where @oldSize@ is the+-- original size of the array.+--+-- Note that the returned array may be a mutated version of the original array.+--+-- /Pre-release/+{-# INLINE snocWith #-}+snocWith :: MonadIO m => (Int -> Int) -> MutArray a -> a -> m (MutArray a)+snocWith sizer arr@MutArray {..} x = do+ if arrEnd < arrBound+ then unsafeSnoc arr x+ else snocWithRealloc sizer arr x++-- XXX round it to next power of 2.++-- | The array is mutated to append an additional element to it. If there is no+-- reserved space available in the array then it is reallocated to double the+-- original size.+--+-- This is useful to reduce allocations when appending unknown number of+-- elements.+--+-- Note that the returned array may be a mutated version of the original array.+--+-- >>> snoc = MutArray.snocWith (* 2)+--+-- Performs O(n * log n) copies to grow, but is liberal with memory allocation.+--+-- /Pre-release/+{-# INLINE snoc #-}+snoc :: MonadIO m => MutArray a -> a -> m (MutArray a)+snoc = snocWith (* 2)++-- | Make the uninitialized memory in the array available for use extending it+-- by the supplied length beyond the current length of the array. The array may+-- be reallocated.+--+{-# INLINE uninit #-}+uninit :: MonadIO m => MutArray a -> Int -> m (MutArray a)+uninit arr@MutArray{..} len =+ if arrEnd + len <= arrBound+ then return $ arr {arrEnd = arrEnd + len}+ else realloc (length arr + len) arr++-------------------------------------------------------------------------------+-- Random reads+-------------------------------------------------------------------------------++-- | Return the element at the specified index without checking the bounds from+-- a @MutableArray# RealWorld@.+--+-- Unsafe because it does not check the bounds of the array.+{-# INLINE unsafeGetIndexWith #-}+unsafeGetIndexWith, getIndexUnsafeWith :: MonadIO m => MutableArray# RealWorld a -> Int -> m a+unsafeGetIndexWith _arrContents# n =+ liftIO+ $ IO+ $ \s# ->+ let !(I# i#) = n+ in readArray# _arrContents# i# s#++-- | Return the element at the specified index without checking the bounds.+--+-- Unsafe because it does not check the bounds of the array.+{-# INLINE_NORMAL unsafeGetIndex #-}+unsafeGetIndex, getIndexUnsafe :: MonadIO m => Int -> MutArray a -> m a+unsafeGetIndex n MutArray {..} = unsafeGetIndexWith arrContents# (n + arrStart)++-- | /O(1)/ Lookup the element at the given index. Index starts from 0.+--+{-# INLINE getIndex #-}+getIndex :: MonadIO m => Int -> MutArray a -> m (Maybe a)+getIndex i arr =+ if i >= 0 && i < length arr+ then Just <$> unsafeGetIndex i arr+ else return Nothing++-------------------------------------------------------------------------------+-- Subarrays+-------------------------------------------------------------------------------++-- XXX We can also get immutable slices.++-- | /O(1)/ Slice an array in constant time.+--+-- Unsafe: The bounds of the slice are not checked.+--+-- /Unsafe/+--+-- /Pre-release/+{-# INLINE unsafeSliceOffLen #-}+unsafeSliceOffLen, getSliceUnsafe+ :: Int -- ^ from index+ -> Int -- ^ length of the slice+ -> MutArray a+ -> MutArray a+unsafeSliceOffLen index len arr@MutArray {..} =+ assert (index >= 0 && len >= 0 && index + len <= length arr)+ $ arr {arrStart = newStart, arrEnd = newEnd}+ where+ newStart = arrStart + index+ newEnd = newStart + len++-- | /O(1)/ Slice an array in constant time. Throws an error if the slice+-- extends out of the array bounds.+--+-- /Pre-release/+{-# INLINE sliceOffLen #-}+sliceOffLen, getSlice+ :: Int -- ^ from index+ -> Int -- ^ length of the slice+ -> MutArray a+ -> MutArray a+sliceOffLen index len arr@MutArray{..} =+ if index >= 0 && len >= 0 && index + len <= length arr+ then arr {arrStart = newStart, arrEnd = newEnd}+ else error+ $ "sliceOffLen: invalid slice, index "+ ++ show index ++ " length " ++ show len+ where+ newStart = arrStart + index+ newEnd = newStart + len++-------------------------------------------------------------------------------+-- to Lists and streams+-------------------------------------------------------------------------------++-- XXX Maybe faster to create a list explicitly instead of mapM, if list fusion+-- does not work well.++-- | Convert an 'Array' into a list.+--+-- /Pre-release/+{-# INLINE toList #-}+toList :: MonadIO m => MutArray a -> m [a]+toList arr = mapM (`unsafeGetIndex` arr) [0 .. (length arr - 1)]++-- | Generates a stream from the elements of a @MutArray@.+--+-- >>> read = Stream.unfold MutArray.reader+--+{-# INLINE_NORMAL read #-}+read :: MonadIO m => MutArray a -> D.Stream m a+read arr =+ D.mapM (`unsafeGetIndex` arr) $ D.enumerateFromToIntegral 0 (length arr - 1)++-- Check equivalence with StreamK.fromStream . toStreamD and remove+{-# INLINE toStreamK #-}+toStreamK :: MonadIO m => MutArray a -> K.StreamK m a+toStreamK arr = K.unfoldrM step 0++ where++ arrLen = length arr+ step i+ | i == arrLen = return Nothing+ | otherwise = do+ x <- unsafeGetIndex i arr+ return $ Just (x, i + 1)++{-# INLINE_NORMAL readRev #-}+readRev :: MonadIO m => MutArray a -> D.Stream m a+readRev arr =+ D.mapM (`unsafeGetIndex` arr)+ $ D.enumerateFromThenToIntegral (arrLen - 1) (arrLen - 2) 0+ where+ arrLen = length arr++-------------------------------------------------------------------------------+-- Folds+-------------------------------------------------------------------------------++-- XXX deduplicate this across unboxed array and this module?++-- | The default chunk size by which the array creation routines increase the+-- size of the array when the array is grown linearly.+arrayChunkSize :: Int+arrayChunkSize = 1024++-- | Like 'createOf' but does not check the array bounds when writing. The fold+-- driver must not call the step function more than 'n' times otherwise it will+-- corrupt the memory and crash. This function exists mainly because any+-- conditional in the step function blocks fusion causing 10x performance+-- slowdown.+--+-- /Pre-release/+{-# INLINE_NORMAL unsafeCreateOf #-}+unsafeCreateOf :: MonadIO m => Int -> Fold m a (MutArray a)+unsafeCreateOf n = Fold step initial return return++ where++ initial = FL.Partial <$> new (max n 0)++ step arr x = FL.Partial <$> unsafeSnoc arr x++{-# DEPRECATED writeNUnsafe "Please use unsafeCreateOf instead." #-}+{-# INLINE writeNUnsafe #-}+writeNUnsafe :: MonadIO m => Int -> Fold m a (MutArray a)+writeNUnsafe = unsafeCreateOf++-- | @createOf n@ folds a maximum of @n@ elements from the input stream to an+-- 'Array'.+--+-- >>> createOf n = Fold.take n (MutArray.unsafeCreateOf n)+--+-- /Pre-release/+{-# INLINE_NORMAL createOf #-}+createOf :: MonadIO m => Int -> Fold m a (MutArray a)+createOf n = FL.take n $ unsafeCreateOf n++{-# DEPRECATED writeN "Please use createOf instead." #-}+{-# INLINE writeN #-}+writeN :: MonadIO m => Int -> Fold m a (MutArray a)+writeN = createOf++-- >>> f n = MutArray.writeAppendWith (* 2) (MutArray.pinnedNew n)+-- >>> writeWith n = Fold.rmapM MutArray.rightSize (f n)+-- >>> writeWith n = Fold.rmapM MutArray.fromArrayStreamK (MutArray.writeChunks n)++-- | @createWith minCount@ folds the whole input to a single array. The array+-- starts at a size big enough to hold minCount elements, the size is doubled+-- every time the array needs to be grown.+--+-- /Caution! Do not use this on infinite streams./+--+-- /Pre-release/+{-# INLINE_NORMAL createWith #-}+createWith :: MonadIO m => Int -> Fold m a (MutArray a)+-- writeWith n = FL.rmapM rightSize $ writeAppendWith (* 2) (pinnedNew n)+createWith elemCount = FL.rmapM extract $ FL.foldlM' step initial++ where++ initial = do+ when (elemCount < 0) $ error "createWith: elemCount is negative"+ new elemCount++ step arr@(MutArray _ start end bound) x+ | end == bound = do+ let oldSize = end - start+ newSize = max (oldSize * 2) 1+ arr1 <- realloc newSize arr+ unsafeSnoc arr1 x+ step arr x = unsafeSnoc arr x++ -- extract = rightSize+ extract = return++{-# DEPRECATED writeWith "Please use createWith instead." #-}+{-# INLINE writeWith #-}+writeWith :: MonadIO m => Int -> Fold m a (MutArray a)+writeWith = createWith++-- | Fold the whole input to a single array.+--+-- Same as 'createWith' using an initial array size of 'arrayChunkSize' bytes+-- rounded up to the element size.+--+-- /Caution! Do not use this on infinite streams./+--+{-# INLINE create #-}+create :: MonadIO m => Fold m a (MutArray a)+create = writeWith arrayChunkSize++{-# DEPRECATED write "Please use create instead." #-}+{-# INLINE write #-}+write :: MonadIO m => Fold m a (MutArray a)+write = create++-- | Create a 'MutArray' 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.+--+{-# INLINE fromStreamN #-}+fromStreamN :: MonadIO m => Int -> Stream m a -> m (MutArray a)+fromStreamN n = D.fold (writeN n)++{-# INLINE fromStream #-}+fromStream :: MonadIO m => Stream m a -> m (MutArray a)+fromStream = D.fold write++{-# INLINABLE fromListN #-}+fromListN :: MonadIO m => Int -> [a] -> m (MutArray a)+fromListN n xs = fromStreamN n $ D.fromList xs++{-# INLINABLE fromList #-}+fromList :: MonadIO m => [a] -> m (MutArray a)+fromList xs = fromStream $ D.fromList xs++{-# INLINABLE fromPureStream #-}+fromPureStream :: MonadIO m => Stream Identity a -> m (MutArray a)+fromPureStream xs =+ D.fold write $ D.morphInner (return . runIdentity) xs++-------------------------------------------------------------------------------+-- Chunking+-------------------------------------------------------------------------------++data GroupState s a start end bound+ = GroupStart s+ | GroupBuffer s (MutableArray# RealWorld a) start end bound+ | GroupYield+ (MutableArray# RealWorld a)+ start+ end+ bound+ (GroupState s a start end bound)+ | GroupFinish++-- | @chunksOf n stream@ groups the input stream into a stream of+-- arrays of size n.+--+-- @chunksOf n = foldMany (MutArray.writeN n)@+--+-- /Pre-release/+{-# INLINE_NORMAL chunksOf #-}+chunksOf :: forall m a. MonadIO m+ => Int -> D.Stream m a -> D.Stream m (MutArray a)+-- XXX the idiomatic implementation leads to large regression in the D.reverse'+-- benchmark. It seems it has difficulty producing optimized code when+-- converting to StreamK. Investigate GHC optimizations.+-- chunksOf n = D.foldMany (writeN n)+chunksOf n (D.Stream step state) =+ D.Stream step' (GroupStart state)++ where++ -- start is always 0+ -- end and len are always equal++ {-# INLINE_LATE step' #-}+ step' _ (GroupStart st) = do+ when (n <= 0) $+ -- XXX we can pass the module string from the higher level API+ error $ "Streamly.Internal.Data.Array.Generic.Mut.Type.chunksOf: "+ ++ "the size of arrays [" ++ show n+ ++ "] must be a natural number"+ (MutArray contents start end bound :: MutArray a) <- new n+ return $ D.Skip (GroupBuffer st contents start end bound)++ step' gst (GroupBuffer st contents start end bound) = do+ r <- step (adaptState gst) st+ case r of+ D.Yield x s -> do+ putIndexUnderlying end contents x+ let end1 = end + 1+ return $+ if end1 >= bound+ then D.Skip+ (GroupYield+ contents start end1 bound (GroupStart s))+ else D.Skip (GroupBuffer s contents start end1 bound)+ D.Skip s ->+ return $ D.Skip (GroupBuffer s contents start end bound)+ D.Stop ->+ return+ $ D.Skip (GroupYield contents start end bound GroupFinish)++ step' _ (GroupYield contents start end bound next) =+ return $ D.Yield (MutArray contents start end bound) next++ step' _ GroupFinish = return D.Stop++-------------------------------------------------------------------------------+-- Unfolds+-------------------------------------------------------------------------------++-- | Resumable unfold of an array.+--+{-# INLINE_NORMAL producerWith #-}+producerWith :: Monad m => (forall b. IO b -> m b) -> Producer m (MutArray a) a+producerWith liftio = Producer step inject extract++ where++ {-# INLINE inject #-}+ inject arr = return (arr, 0)++ {-# INLINE extract #-}+ extract (arr, i) =+ return $ arr {arrStart = arrStart arr + i}++ {-# INLINE_LATE step #-}+ step (arr, i)+ | i == length arr = return D.Stop+ step (arr, i) = do+ x <- liftio $ unsafeGetIndex i arr+ return $ D.Yield x (arr, i + 1)++-- | Resumable unfold of an array.+--+{-# INLINE_NORMAL producer #-}+producer :: MonadIO m => Producer m (MutArray a) a+producer = producerWith liftIO++-- | Unfold an array into a stream.+--+{-# INLINE_NORMAL reader #-}+reader :: MonadIO m => Unfold m (MutArray a) a+reader = Producer.simplify producer++--------------------------------------------------------------------------------+-- Appending arrays+--------------------------------------------------------------------------------++-- | Put a sub range of a source array into a subrange of a destination array.+-- This is not safe as it does not check the bounds.+{-# INLINE unsafePutSlice #-}+unsafePutSlice, putSliceUnsafe :: MonadIO m =>+ MutArray a -> Int -> MutArray a -> Int -> Int -> m ()+unsafePutSlice src srcStart dst dstStart len = liftIO $ do+ assertM(len <= length dst)+ assertM(len <= length src)+ let !(I# srcStart#) = srcStart + arrStart src+ !(I# dstStart#) = dstStart + arrStart dst+ !(I# len#) = len+ let arrS# = arrContents# src+ arrD# = arrContents# dst+ IO $ \s# -> (# copyMutableArray#+ arrS# srcStart# arrD# dstStart# len# s#+ , () #)++{-# INLINE clone #-}+clone :: MonadIO m => MutArray a -> m (MutArray a)+clone src = do+ let len = length src+ dst <- new len+ unsafePutSlice src 0 dst 0 len+ return dst++-------------------------------------------------------------------------------+-- Size+-------------------------------------------------------------------------------++{-# INLINE length #-}+length :: MutArray a -> Int+length arr = arrEnd arr - arrStart arr++-------------------------------------------------------------------------------+-- Equality+-------------------------------------------------------------------------------++-- | Compare the length of the arrays. If the length is equal, compare the+-- lexicographical ordering of two underlying byte arrays otherwise return the+-- result of length comparison.+--+-- /Pre-release/+{-# INLINE cmp #-}+cmp :: (MonadIO m, Ord a) => MutArray a -> MutArray a -> m Ordering+cmp a1 a2 =+ case compare lenA1 lenA2 of+ EQ -> loop (lenA1 - 1)+ x -> return x++ where++ lenA1 = length a1+ lenA2 = length a2++ loop i+ | i < 0 = return EQ+ | otherwise = do+ v1 <- unsafeGetIndex i a1+ v2 <- unsafeGetIndex i a2+ case compare v1 v2 of+ EQ -> loop (i - 1)+ x -> return x++{-# INLINE eq #-}+eq :: (MonadIO m, Eq a) => MutArray a -> MutArray a -> m Bool+eq a1 a2 =+ if lenA1 == lenA2+ then loop (lenA1 - 1)+ else return False++ where++ lenA1 = length a1+ lenA2 = length a2++ loop i+ | i < 0 = return True+ | otherwise = do+ v1 <- unsafeGetIndex i a1+ v2 <- unsafeGetIndex i a2+ if v1 == v2+ then loop (i - 1)+ else return False++{-# INLINE dropAround #-}+dropAround, strip :: MonadIO m => (a -> Bool) -> MutArray a -> m (MutArray a)+dropAround p arr = liftIO $ do+ let lastIndex = length arr - 1+ indexR <- getIndexR lastIndex -- last predicate failing index+ if indexR < 0+ then nil+ else do+ indexL <- getIndexL 0 -- first predicate failing index+ if indexL == 0 && indexR == lastIndex+ then return arr+ else+ let newLen = indexR - indexL + 1+ in return $ unsafeSliceOffLen indexL newLen arr++ where++ getIndexR idx+ | idx < 0 = return idx+ | otherwise = do+ r <- unsafeGetIndex idx arr+ if p r+ then getIndexR (idx - 1)+ else return idx++ getIndexL idx = do+ r <- unsafeGetIndex idx arr+ if p r+ then getIndexL (idx + 1)+ else return idx++--------------------------------------------------------------------------------+-- Renaming+--------------------------------------------------------------------------------++RENAME(strip,dropAround)+RENAME(putIndexUnsafe, unsafePutIndex)+RENAME(modifyIndexUnsafe, unsafeModifyIndex)+RENAME(getIndexUnsafe, unsafeGetIndex)+RENAME(getIndexUnsafeWith, unsafeGetIndexWith)+RENAME(getSliceUnsafe,unsafeSliceOffLen)+RENAME(putSliceUnsafe, unsafePutSlice)+RENAME(getSlice,sliceOffLen)+RENAME(snocUnsafe, unsafeSnoc)
+ src/Streamly/Internal/Data/MutArray/Lib.c view
@@ -0,0 +1,15 @@+#include <string.h>++// Find the char "c" starting from "dst+off" and up to "len" chars from+// it, returns the index of the character found, considering dst + off+// as the base pointer. If index is greater than or equal to len the+// char is not found.+size_t memchr_index(const void *dst, size_t off, int c, size_t len) {+ void *p = memchr ((char *)dst + off, c, len);++ if (p) {+ return (size_t) (p - dst - off);+ } else {+ return len;+ }+}
+ src/Streamly/Internal/Data/MutArray/Stream.hs view
@@ -0,0 +1,146 @@+{-# OPTIONS_GHC -Wno-deprecations #-}+-- |+-- Module : Streamly.Internal.Data.MutArray.Stream+-- Copyright : (c) 2019 Composewell Technologies+-- License : BSD3-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- Combinators to efficiently manipulate streams of mutable arrays.+--+-- We can either push these in the MutArray module with a "chunks" prefix or+-- keep this as a separate module and release it.+--+module Streamly.Internal.Data.MutArray.Stream+{-# DEPRECATED "Please use \"Streamly.Internal.Data.MutArray\" instead." #-}+ (+ -- * Generation+ MArray.chunksOf+ , MArray.pinnedChunksOf+ , MArray.writeChunks -- chunksWrite?+ , MArray.splitOn -- chunksSplitOn++ -- * Compaction+ , packArraysChunksOf+ , MArray.SpliceState (..)+ , lpackArraysChunksOf+ , compact -- chunksCompact+ , compactLE+ , compactEQ+ , compactGE++ -- * Elimination+ , MArray.flattenArrays -- chunksConcat+ , MArray.flattenArraysRev -- chunksConcatRev+ , MArray.fromArrayStreamK -- chunksCoalesce+ )+where++import Control.Monad.IO.Class (MonadIO(..))+import Streamly.Internal.Data.Unbox (Unbox)+import Streamly.Internal.Data.MutArray.Type (MutArray(..))+import Streamly.Internal.Data.Fold.Type (Fold(..))+import Streamly.Internal.Data.Parser (ParseError)+import Streamly.Internal.Data.Stream.Type (Stream)++import qualified Streamly.Internal.Data.MutArray as MArray+import qualified Streamly.Internal.Data.Fold.Type as FL+import qualified Streamly.Internal.Data.Parser as ParserD+import qualified Streamly.Internal.Data.Stream as D++-------------------------------------------------------------------------------+-- Compact+-------------------------------------------------------------------------------++-- XXX This can be removed once compactLEFold/compactLE are implemented.+--+-- | This mutates the first array (if it has space) to append values from the+-- second one. This would work for immutable arrays as well because an+-- immutable array never has space so a new array is allocated instead of+-- mutating it.+--+-- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a+-- maximum specified size. Note that if a single array is bigger than the+-- specified size we do not split it to fit. When we coalesce multiple arrays+-- if the size would exceed the specified size we do not coalesce therefore the+-- actual array size may be less than the specified chunk size.+--+-- @since 0.7.0+{-# INLINE packArraysChunksOf #-}+packArraysChunksOf :: (MonadIO m, Unbox a)+ => Int -> D.Stream m (MutArray a) -> D.Stream m (MutArray a)+packArraysChunksOf = MArray.compactLE++-- XXX Remove this once compactLEFold is implemented+-- lpackArraysChunksOf = Fold.many compactLEFold+--+{-# INLINE lpackArraysChunksOf #-}+lpackArraysChunksOf :: (MonadIO m, Unbox a)+ => Int -> Fold m (MutArray a) () -> Fold m (MutArray a) ()+lpackArraysChunksOf = MArray.lCompactGE++-- XXX Same as compactLE, to be removed once that is implemented.+--+-- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a+-- maximum specified size in bytes.+--+-- /Internal/+{-# INLINE compact #-}+compact :: (MonadIO m, Unbox a)+ => Int -> Stream m (MutArray a) -> Stream m (MutArray a)+compact = packArraysChunksOf++-- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a+-- maximum specified size. Note that if a single array is bigger than the+-- specified size we do not split it to fit. When we coalesce multiple arrays+-- if the size would exceed the specified size we do not coalesce therefore the+-- actual array size may be less than the specified chunk size.+--+-- /Internal/+{-# INLINE compactLEParserD #-}+compactLEParserD ::+ forall m a. (MonadIO m, Unbox a)+ => Int -> ParserD.Parser (MutArray a) m (MutArray a)+compactLEParserD = MArray.pCompactLE++-- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a+-- minimum specified size. Note that if all the arrays in the stream together+-- are smaller than the specified size the resulting array will be smaller than+-- the specified size. When we coalesce multiple arrays if the size would exceed+-- the specified size we stop coalescing further.+--+-- /Internal/+{-# INLINE compactGEFold #-}+compactGEFold ::+ forall m a. (MonadIO m, Unbox a)+ => Int -> FL.Fold m (MutArray a) (MutArray a)+compactGEFold = MArray.fCompactGE++-- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a+-- maximum specified size in bytes.+--+-- /Internal/+compactLE :: (MonadIO m, Unbox a) =>+ Int -> Stream m (MutArray a) -> Stream m (Either ParseError (MutArray a))+compactLE n = D.parseManyD (compactLEParserD n)++-- | Like 'compactLE' but generates arrays of exactly equal to the size+-- specified except for the last array in the stream which could be shorter.+--+-- /Unimplemented/+{-# INLINE compactEQ #-}+compactEQ :: -- (MonadIO m, Unbox a) =>+ Int -> Stream m (MutArray a) -> Stream m (MutArray a)+compactEQ _n _xs = undefined+ -- IsStream.fromStreamD $ D.foldMany (compactEQFold n) (IsStream.toStreamD xs)++-- | Like 'compactLE' but generates arrays of size greater than or equal to the+-- specified except for the last array in the stream which could be shorter.+--+-- /Internal/+{-# INLINE compactGE #-}+compactGE ::+ (MonadIO m, Unbox a)+ => Int -> Stream m (MutArray a) -> Stream m (MutArray a)+compactGE n = D.foldMany (compactGEFold n)
+ src/Streamly/Internal/Data/MutArray/Type.hs view
@@ -0,0 +1,4221 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE UnliftedFFITypes #-}+-- |+-- Module : Streamly.Internal.Data.MutArray.Type+-- Copyright : (c) 2020 Composewell Technologies+-- License : BSD3-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- Pinned and unpinned mutable array for 'Unboxed' types. Fulfils the following+-- goals:+--+-- * Random access (array)+-- * Efficient storage (unboxed)+-- * Performance (unboxed access)+-- * Performance - in-place operations (mutable)+-- * Performance - GC (pinned, mutable)+-- * interfacing with OS (pinned)+--+-- Stream and Fold APIs allow easy, efficient and convenient operations on+-- arrays.+--+-- Mutable arrays and file system files are quite similar, they can grow and+-- their content is mutable. Therefore, both have similar APIs as well. We+-- strive to keep the API consistent for both. Ideally, you should be able to+-- replace one with another with little changes to the code.++module Streamly.Internal.Data.MutArray.Type+ (+ -- ** Type+ -- $arrayNotes+ MutArray (..)+ , fromMutByteArray+ , toMutByteArray++ -- ** Conversion+ -- *** Pinned and Unpinned+ , pin+ , unpin+ , isPinned++ -- ** Casting+ , cast+ , unsafeCast+ , asBytes+ , unsafeAsPtr -- XXX asPtr+ , asCString+ , asCWString++ -- ** Construction+ , empty+ -- , singleton++ -- *** New+ -- | New arrays are always empty arrays with some reserve capacity to+ -- extend the length without reallocating.+ , emptyOf+ , emptyWithAligned -- XXX emptyAlignAtWith+ , emptyOf'++ -- *** Slicing+ -- | Get a subarray without copying++ -- Element agnostic.+ , unsafeSliceOffLen+ , sliceOffLen++ -- Counting from the beginning+ -- We use the name "break" for splitting into two parts. And the word+ -- "split" for splitting into possibly more than two.+ , unsafeBreakAt+ , breakAt -- called splitAt in lists+ -- , take+ -- , drop+ -- , uncons+ -- , tail++ -- Counting from the end+ -- , revBreakAt+ -- , takeEnd+ -- , dropEnd+ -- , unsnoc+ -- , init++ -- Element aware+ -- search from the beginning+ , breakEndByWord8_+ , breakEndBy+ , breakEndBy_+ -- , breakBeginBy -- called break in lists+ -- , breakSpan -- called span in lists+ -- , breakBeginBySeq -- called breakOn in text+ -- , breakSepBy_+ , dropWhile+ -- , takeWhile+ -- , stripPrefix++ -- search from the end+ , revBreakEndBy+ , revBreakEndBy_+ -- , revBreakBeginBy -- called breakOnEnd in text+ , revDropWhile -- dropWhileEnd+ -- , takeWhileEnd+ -- , stripSuffix++ , dropAround++ -- *** Stream Folds+ -- | Note: create is just appending to an empty array. So keep the names+ -- consistent with append operations.+ , ArrayUnsafe (..)++ -- With allocator, of capacity+ , unsafeCreateWithOf+ , createWithOf -- create alloc with++ , unsafeCreateOf+ , createOf+ , createMinOf+ , create -- XXX should we change the min to one elem or one Word?+ -- , createGrowBy++ -- Reverse variants+ , revCreateOf+ -- , revCreate++ -- Pinned variants++ , unsafeCreateOf'+ , createOf'+ , create'++ -- *** From containers+ -- | These can be implemented by appending a stream to an empty array.+ , clone -- XXX fromMutArray or copyMutArray+ , clone'+ , fromListN+ , fromListN'+ , fromList+ , fromList'+ , fromListRevN+ , fromListRev+ , fromStreamN+ , fromStream+ , fromPureStreamN+ , fromPureStream+ , fromCString#+ , fromW16CString#+ , fromPtrN+ , fromChunksK+ , fromChunksRealloced -- fromSmallChunks++ , unsafeCreateWithPtr'++ -- ** Random writes+ , putIndex+ -- , putIndexRev -- or revPutIndex+ , unsafePutIndex+ , putIndices+ -- , putFromThenTo+ -- , putFrom -- start writing at the given position+ -- , putUpto -- write from beginning up to the given position+ -- , putFromTo+ -- , putFromRev+ -- , putUptoRev+ , unsafeModifyIndex+ , modifyIndex+ , modifyIndices+ , modify+ , swapIndices+ , unsafeSwapIndices++ -- ** Reading++ -- *** Indexing+ -- , head+ , getIndex+ , unsafeGetIndex+ , unsafeGetIndexRev+ -- , getFromThenTo+ -- , last+ , getIndexRev -- getRevIndex?+ , indexReader+ , indexReaderWith++ -- -- *** Searching+ -- See the Data.Array module as well+ -- , binarySearch+ -- , findIndicesOf+ -- , getIndicesOf+ -- , indexFinder+ -- , findIndexOf+ -- , find+ -- , elem++ -- *** To Streams+ , read+ , readRev+ , toStreamWith+ , toStreamRevWith+ , toStreamK+ , toStreamKWith+ , toStreamKRev+ , toStreamKRevWith++ -- *** To Containers+ , toList++ -- *** Unfolds+ -- experimental+ , producerWith+ , producer++ , reader+ , readerRevWith+ , readerRev++ -- ** Size and Capacity+ -- *** Size+ -- , null+ -- , compareLength+ , length+ , byteLength++ -- *** Capacity Reporting+ , capacity+ , free+ , byteCapacity+ , bytesFree++ -- *** Capacity Management+ -- There are two ways of growing an array:+ --+ -- * grow: double, align to next power of 2 if large, never shrink+ -- * growBy: align to block size if large, never shrink++ , blockSize+ , arrayChunkBytes+ , allocBytesToElemCount+ , reallocBytes+ , reallocBytesWith++ -- , grow -- double the used capacity and align to power of 2+ , growTo+ , growBy+ , growExp+ , rightSize+ , vacate++ -- ** Folding+ , foldl'+ , foldr+ , fold+ , foldRev -- XXX revFold+ , byteCmp+ , byteEq++ -- ** In-place Mutation Algorithms+ , reverse+ , permute+ , partitionBy+ , shuffleBy+ , divideBy+ , mergeBy+ , bubble+ , rangeBy+ -- , filter++ -- ** Growing and Shrinking+ -- | Arrays grow only at the end, though technically it is possible to+ -- grow on both sides and therefore we can have a cons as well as snoc. But+ -- cons is not implemented yet.++ -- *** Appending elements+ -- | snoc is the fundamental operation for growing arrays. Streaming folds,+ -- appending streams can be implemented in terms of snoc.++ -- XXX snoc 128/256/512 bit data using SIMD.+ , snocWith -- XXX snocGrowWith+ , snoc+ , snocGrowBy+ , snocMay+ , unsafeSnoc++ -- , revSnoc -- cons+ -- , revSnocGrowBy -- consGrowBy++ -- *** Folds for appending streams+ -- | Fundamentally these are a sequence of snoc operations.+ -- Folds are named "append" whereas joining two arrays is named as "splice".++ , appendWith -- XXX replace by pure appendGrowWith++ , unsafeAppendMax -- can be renamed to unsafeAppendN later+ , appendMax -- can be renamed to appendN later+ -- , appendMin -- like createMinOf, supplies a min hint to reduce allocs+ -- , appendGrowWith+ , append2 -- to be renamed to append later+ , appendGrowBy++ -- , revAppend+ -- , revAppendN+ -- , revAppendGrowBy++ -- *** Appending streams+ -- | Fundamentally these are a sequence of snoc operations. These are+ -- convenience operations implemented in terms of folds.+ , unsafeAppendPtrN+ , appendPtrN+ , appendCString+ , appendCString#+ -- , appendStreamGrowWith+ , appendStream+ , appendStreamN+ -- , appendStreamGrowBy++ -- *** Splicing arrays+ -- | TODO: We can replace memcpy with stream copy using Word64. Arrays are+ -- aligned on 64-bit boundaries on 64-bit CPUs. A fast way to copy an+ -- array is to unsafeCast it to Word64, read it as a stream, write the+ -- stream to Word64 array and unsafeCast it again. We can use SIMD+ -- read/write as well.++ , spliceCopy -- XXX freeze and splice instead?+ , splice+ , spliceWith -- XXX spliceGrowWith+ , spliceExp -- XXX spliceGrowExp+ -- , spliceN+ -- , spliceGrowBy+ , unsafeSplice+ -- , putSlice+ -- , appendSlice+ -- , appendSliceFrom++ -- XXX Do not expose these yet, we should perhaps expose only the Peek/Poke+ -- monads instead? Decide after implementing the monads.++ -- ** Serialization using Unbox+ -- | Fixed length serialization.+ -- Serialization operations are essentially a combination of serialization+ -- using Unbox/Serialize type class, followed by snoc. TODO: use SIMD for+ -- snoc.+ , poke+ , pokeMay+ -- , pokeGrowBy+ , unsafePokeSkip -- XXX unsafePoke_+ -- , revPoke++ -- ** Deserialization using Unbox+ -- Fixed length deserialization.+ , peek+ , unsafePeek+ , unsafePeekSkip -- XXX unsafePeek_+ -- , revPeek++ -- Arrays of arrays+ -- We can add dimensionality parameter to the array type to get+ -- multidimensional arrays. Multidimensional arrays would just be a+ -- convenience wrapper on top of single dimensional arrays.++ -- ** Streams of Arrays+ -- *** Chunk+ -- | Group a stream into arrays.+ , chunksOf+ , chunksOf' -- chunksOf'+ -- , timedChunksOf -- see the Streamly.Data.Stream.Prelude module+ , buildChunks+ , chunksEndBy+ , chunksEndBy'+ , chunksEndByLn+ , chunksEndByLn'+ -- , chunksBeginBySeq -- for parsing streams with headers++ -- *** Split+ -- | Split an array into a stream of slices.++ -- Note: some splitting APIs are in MutArray.hs+ , splitEndBy_+ , splitEndBy+ -- , splitSepBy_+ -- , splitSepBySeq+ -- , splitGroupBy+ -- , splitWordsBy++ -- *** Concat+ -- | Append the arrays in a stream to form a stream of elements.+ , concat+ -- , concatSepBy+ -- , concatEndBy+ -- , concatEndByLn -- unlines - concat a byte chunk stream using newline byte separator+ -- , concatWordsBy+ , concatWith -- internal+ , concatRev+ , concatRevWith -- internal++ -- *** Compact+ -- | Coalesce arrays together in a stream of arrays to form a stream of+ -- larger arrays.+ , SpliceState (..)+ , compactLeAs -- internal++ -- Creation folds/parsers+ , createCompactMax+ , createCompactMax'+ , createCompactMin+ , createCompactMin'++ -- Stream compaction+ , compactMin+ -- , compactMin'+ , compactExact+ -- , compactExact'++ -- Scans+ , scanCompactMin+ , scanCompactMin'++ -- ** Utilities+ , isPower2+ , roundUpToPower2++ -- * Deprecated+ , getSlice+ , strip+ , breakOn+ , splitAt+ , realloc+ , createOfWith+ , peekUncons+ , peekUnconsUnsafe+ , pokeAppend+ , pokeAppendMay+ , castUnsafe+ , newArrayWith+ , getSliceUnsafe+ , putIndexUnsafe+ , modifyIndexUnsafe+ , getIndexUnsafe+ , snocUnsafe+ , spliceUnsafe+ , pokeSkipUnsafe+ , peekSkipUnsafe+ , asPtrUnsafe+ , writeChunks+ , flattenArrays+ , flattenArraysRev+ , fromArrayStreamK+ , fromStreamDN+ , fromStreamD+ , cmp+ , getIndices+ , getIndicesWith+ , resize+ , resizeExp+ , nil+ , new+ , pinnedNew+ , pinnedNewBytes+ , writeAppendNUnsafe+ , writeAppendN+ , writeAppendWith+ , writeAppend+ , writeNWithUnsafe+ , writeNWith+ , writeNUnsafe+ , pinnedWriteNUnsafe+ , writeN+ , pinnedWriteN+ , pinnedWriteNAligned -- XXX not required+ , writeWith+ , write+ , pinnedWrite+ , writeRevN+ , fromByteStr#+ , pCompactLE+ , pPinnedCompactLE+ , fCompactGE+ , fPinnedCompactGE+ , lPinnedCompactGE+ , lCompactGE+ , compactGE+ , pinnedEmptyOf+ , pinnedChunksOf+ , pinnedCreateOf+ , pinnedCreate+ , pinnedFromListN+ , pinnedFromList+ , pinnedClone+ , unsafePinnedCreateOf+ , splitOn+ , pinnedNewAligned+ , unsafePinnedAsPtr+ , grow+ , createWith+ , snocLinear+ , unsafeAppendN+ , appendN+ , append+ )+where++#include "assert.hs"+#include "deprecation.h"+#include "inline.hs"+#include "ArrayMacros.h"+#include "MachDeps.h"++import Control.Monad (when)+import Control.Monad.IO.Class (MonadIO(..))+import Data.Bifunctor (first)+import Data.Bits (shiftR, (.|.), (.&.))+import Data.Char (ord)+import Data.Functor.Identity (Identity(..))+import Data.Proxy (Proxy(..))+import Data.Word (Word8, Word16)+import Foreign.C.String (CString, CWString)+import Foreign.C.Types (CSize(..), CChar, CWchar)+import Foreign.Ptr (plusPtr, castPtr)+import Streamly.Internal.Data.MutByteArray.Type+ ( MutByteArray(..)+ , PinnedState(..)+ , getMutByteArray#+ , unsafePutSlice+ , blockSize+ , largeObjectThreshold+ , unsafeByteCmp+ )+import Streamly.Internal.Data.Unbox (Unbox(..))+import GHC.Base (noinline)+import GHC.Exts (Addr#, MutableByteArray#, RealWorld)+import GHC.Ptr (Ptr(..))+import GHC.Exts (byteArrayContents#, unsafeCoerce#)++import Streamly.Internal.Data.Fold.Type (Fold(..))+import Streamly.Internal.Data.Producer.Type (Producer (..))+import Streamly.Internal.Data.Scanl.Type (Scanl (..))+import Streamly.Internal.Data.Stream.Type (Stream)+import Streamly.Internal.Data.Parser.Type (Parser (..))+import Streamly.Internal.Data.StreamK.Type (StreamK)+import Streamly.Internal.Data.SVar.Type (adaptState, defState)+import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))+import Streamly.Internal.Data.Unfold.Type (Unfold(..))+import Streamly.Internal.System.IO (arrayPayloadSize, defaultChunkSize)++import qualified Streamly.Internal.Data.Fold.Type as FL+import qualified Streamly.Internal.Data.MutByteArray.Type as Unboxed+import qualified Streamly.Internal.Data.Parser.Type as Parser+-- import qualified Streamly.Internal.Data.Fold.Type as Fold+import qualified Streamly.Internal.Data.Producer as Producer+import qualified Streamly.Internal.Data.Stream.Type as D+import qualified Streamly.Internal.Data.Stream.Lift as D+import qualified Streamly.Internal.Data.Stream.Generate as D+import qualified Streamly.Internal.Data.StreamK.Type as K+import qualified Prelude++import Prelude hiding+ (Foldable(..), concat, read, unlines, splitAt, reverse, truncate, dropWhile)++#include "DocTestDataMutArray.hs"++-------------------------------------------------------------------------------+-- Foreign helpers+-------------------------------------------------------------------------------++-- NOTE: Have to be "ccall unsafe" so that we can pass unpinned memory to+-- these. For passing unpinned memory safely we have to pass unlifted byte+-- array pointers in FFI so that neither the constructor nor the array can+-- become stale if a GC kicks in at any point before the call.++foreign import ccall unsafe "string.h memcpy" c_memcpy_pinned_src+ :: MutableByteArray# RealWorld -> Ptr Word8 -> CSize -> IO (Ptr Word8)++foreign import ccall unsafe "memchr_index" c_memchr_index+ :: MutableByteArray# RealWorld -> CSize -> Word8 -> CSize -> IO CSize++-- XXX Use cstringLength# from GHC.CString in ghc-prim+foreign import ccall unsafe "string.h strlen" c_strlen_pinned+ :: Addr# -> IO CSize++-- | Given an 'Unboxed' type (unused first arg) and a number of bytes, return+-- how many elements of that type will completely fit in those bytes.+--+{-# INLINE bytesToElemCount #-}+bytesToElemCount :: forall a. Unbox a => a -> Int -> Int+bytesToElemCount _ n = n `div` SIZE_OF(a)++-------------------------------------------------------------------------------+-- MutArray Data Type+-------------------------------------------------------------------------------++-- Note on using "IO" callbacks:+--+-- The Array APIs should use "IO" callbacks instead of lifted callbacks as the+-- lifted callbacks aren't optimized properly.+--+-- See:+-- https://github.com/composewell/streamly/issues/2820+-- https://github.com/composewell/streamly/issues/2589+++-- $arrayNotes+--+-- We can use an 'Unboxed' constraint in the MutArray type and the constraint+-- can be automatically provided to a function that pattern matches on the+-- MutArray type. However, it has huge performance cost, so we do not use it.+-- Investigate a GHC improvement possiblity.++-- | An unboxed mutable array. An array is created with a given length+-- and capacity. Length is the number of valid elements in the array. Capacity+-- is the maximum number of elements that the array can be expanded to without+-- having to reallocate the memory.+--+-- The elements in the array can be mutated in-place without changing the+-- reference (constructor). However, the length of the array cannot be mutated+-- in-place. A new array reference is generated when the length changes. When+-- the length is increased (upto the maximum reserved capacity of the array),+-- the array is not reallocated and the new reference uses the same underlying+-- memory as the old one.+--+-- Several routines in this module allow the programmer to control the capacity+-- of the array. The programmer can control the trade-off between memory usage+-- and performance impact due to reallocations when growing or shrinking the+-- array.+--+data MutArray a =+#ifdef DEVBUILD+ Unbox a =>+#endif+ -- The array is a range into arrContents. arrContents may be a superset of+ -- the slice represented by the array. All offsets are in bytes.+ MutArray+ { arrContents :: {-# UNPACK #-} !MutByteArray+ , arrStart :: {-# UNPACK #-} !Int -- ^ index into arrContents+ , arrEnd :: {-# UNPACK #-} !Int -- ^ index into arrContents+ -- Represents the first invalid index of+ -- the array.+ -- XXX rename to arrCapacity to be consistent with ring.+ , arrBound :: {-# UNPACK #-} !Int -- ^ first invalid index of arrContents.+ }++-------------------------------------------------------------------------------+-- Construction and destructuring+-------------------------------------------------------------------------------++{-# INLINE fromMutByteArray #-}+fromMutByteArray :: MonadIO m => MutByteArray -> Int -> Int -> m (MutArray a)+fromMutByteArray arr start end = do+ len <- liftIO $ Unboxed.length arr+ return $ MutArray+ { arrContents = arr+ , arrStart = start+ , arrEnd = end+ , arrBound = len+ }++{-# INLINE toMutByteArray #-}+toMutByteArray :: MutArray a -> (MutByteArray, Int, Int)+toMutByteArray MutArray{..} = (arrContents, arrStart, arrEnd)++-------------------------------------------------------------------------------+-- Pinning & Unpinning+-------------------------------------------------------------------------------++-- | Return a copy of the array in pinned memory if unpinned, else return the+-- original array.+{-# INLINE pin #-}+pin :: MutArray a -> IO (MutArray a)+pin arr@MutArray{..} =+ if Unboxed.isPinned arrContents+ then pure arr+ else clone' arr++-- | Return a copy of the array in unpinned memory if pinned, else return the+-- original array.+{-# INLINE unpin #-}+unpin :: MutArray a -> IO (MutArray a)+unpin arr@MutArray{..} =+ if Unboxed.isPinned arrContents+ then clone arr+ else pure arr++-- | Return 'True' if the array is allocated in pinned memory.+{-# INLINE isPinned #-}+isPinned :: MutArray a -> Bool+isPinned MutArray{..} = Unboxed.isPinned arrContents++-------------------------------------------------------------------------------+-- Construction+-------------------------------------------------------------------------------++-- XXX Change the names to use "new" instead of "newArray". That way we can use+-- the same names for managed file system objects as well. For unmanaged ones+-- we can use open/create etc as usual.+--+-- A new array is similar to "touch" creating a zero length file. An mmapped+-- array would be similar to a sparse file with holes. TBD: support mmapped+-- files and arrays.++-- GHC always guarantees word-aligned memory, alignment is important only when+-- we need more than that. See stg_pinnedNewAlignedByteArrayzh and+-- allocatePinned in GHC source.++-- XXX Rename to emptyAlignedWith, alignSize should be first arg.++-- | @emptyWithAligned allocator alignment count@ allocates a new array of zero+-- length and with a capacity to hold @count@ elements, using @allocator+-- size alignment@ as the memory allocator function.+--+-- Alignment must be greater than or equal to machine word size and a power of+-- 2.+--+-- Alignment is ignored if the allocator allocates unpinned memory.+--+-- /Pre-release/+{-# INLINE emptyWithAligned #-}+newArrayWith, emptyWithAligned :: forall m a. (MonadIO m, Unbox a)+ => (Int -> Int -> IO MutByteArray) -> Int -> Int -> m (MutArray a)+emptyWithAligned alloc alignSize count = liftIO $ do+ let size = max (count * SIZE_OF(a)) 0+ contents <- alloc size alignSize+ return $ MutArray+ { arrContents = contents+ , arrStart = 0+ , arrEnd = 0+ , arrBound = size+ }++-- For arrays "nil" sounds a bit odd. empty is better. The only problem with+-- empty is that it is also used by the Alternative type class. But assuming we+-- will mostly import the Array module qualified this should be fine.++-- | Create an empty array.+empty ::+#ifdef DEVBUILD+ Unbox a =>+#endif+ MutArray a+empty = MutArray Unboxed.empty 0 0 0++{-# DEPRECATED nil "Please use empty instead." #-}+nil ::+#ifdef DEVBUILD+ Unbox a =>+#endif+ MutArray a+nil = empty++{-# INLINE newBytesAs #-}+newBytesAs :: MonadIO m =>+#ifdef DEVBUILD+ Unbox a =>+#endif+ PinnedState -> Int -> m (MutArray a)+newBytesAs ps bytes = do+ contents <- liftIO $ Unboxed.newAs ps bytes+ return $ MutArray+ { arrContents = contents+ , arrStart = 0+ , arrEnd = 0+ , arrBound = bytes+ }++-- | Allocates a pinned empty array that with a reserved capacity of bytes.+-- The memory of the array is uninitialized and the allocation is aligned as+-- per the 'Unboxed' instance of the type.+--+-- > pinnedNewBytes = (unsafeCast :: Array Word8 -> a) . emptyOf'+--+-- /Pre-release/+{-# INLINE pinnedNewBytes #-}+{-# DEPRECATED pinnedNewBytes "Please use emptyOf' to create a Word8 array and cast it accordingly." #-}+pinnedNewBytes :: MonadIO m =>+#ifdef DEVBUILD+ Unbox a =>+#endif+ Int -> m (MutArray a)+pinnedNewBytes = newBytesAs Pinned++-- | Like 'emptyWithAligned' but using an allocator is a pinned memory allocator and+-- the alignment is dictated by the 'Unboxed' instance of the type.+--+-- /Internal/+{-# DEPRECATED pinnedNewAligned "Please use emptyOf' to create a Word8 array and cast it accordingly." #-}+{-# INLINE pinnedNewAligned #-}+pinnedNewAligned :: (MonadIO m, Unbox a) => Int -> Int -> m (MutArray a)+pinnedNewAligned = emptyWithAligned (\s _ -> liftIO $ Unboxed.new' s)++{-# INLINE newAs #-}+newAs :: (MonadIO m, Unbox a) => PinnedState -> Int -> m (MutArray a)+newAs ps =+ emptyWithAligned+ (\s _ -> liftIO $ Unboxed.newAs ps s)+ (error "new: alignment is not used in unpinned arrays.")++-- XXX can unaligned allocation be more efficient when alignment is not needed?++-- | Allocates a pinned array of zero length but growable to the specified+-- capacity without reallocation.+{-# INLINE emptyOf' #-}+pinnedEmptyOf, emptyOf' :: (MonadIO m, Unbox a) => Int -> m (MutArray a)+emptyOf' = newAs Pinned+RENAME_PRIME(pinnedEmptyOf,emptyOf)++{-# DEPRECATED pinnedNew "Please use emptyOf' instead." #-}+{-# INLINE pinnedNew #-}+pinnedNew :: forall m a. (MonadIO m, Unbox a) => Int -> m (MutArray a)+pinnedNew = emptyOf'++-- | Allocates an unpinned array of zero length but growable to the specified+-- capacity without reallocation.+--+{-# INLINE emptyOf #-}+emptyOf :: (MonadIO m, Unbox a) => Int -> m (MutArray a)+emptyOf = newAs Unpinned++{-# DEPRECATED new "Please use emptyOf instead." #-}+{-# INLINE new #-}+new :: (MonadIO m, Unbox a) => Int -> m (MutArray a)+new = emptyOf++-------------------------------------------------------------------------------+-- Random writes+-------------------------------------------------------------------------------++-- | Write the given element to the given index of the array. Does not check if+-- the index is out of bounds of the array.+--+-- /Pre-release/+{-# INLINE unsafePutIndex #-}+putIndexUnsafe, unsafePutIndex :: forall m a. (MonadIO m, Unbox a)+ => Int -> MutArray a -> a -> m ()+unsafePutIndex i MutArray{..} x = do+ let index = INDEX_OF(arrStart, i, a)+ assert (i >= 0 && INDEX_VALID(index, arrEnd, a)) (return ())+ liftIO $ pokeAt index arrContents x++invalidIndex :: String -> Int -> a+invalidIndex label i =+ error $ label ++ ": invalid array index " ++ show i++-- | /O(1)/ Write the given element at the given index in the array.+-- Performs in-place mutation of the array.+--+-- >>> putIndex ix arr val = MutArray.modifyIndex ix arr (const (val, ()))+-- >>> f = MutArray.putIndices+-- >>> putIndex ix arr val = Stream.fold (f arr) (Stream.fromPure (ix, val))+--+{-# INLINE putIndex #-}+putIndex :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> a -> m ()+putIndex i MutArray{..} x = do+ let index = INDEX_OF(arrStart,i,a)+ if i >= 0 && INDEX_VALID(index,arrEnd,a)+ then liftIO $ pokeAt index arrContents x+ else invalidIndex "putIndex" i++-- | Write an input stream of (index, value) pairs to an array. Throws an+-- error if any index is out of bounds.+--+-- /Pre-release/+{-# INLINE putIndices #-}+putIndices :: forall m a. (MonadIO m, Unbox a)+ => MutArray a -> Fold m (Int, a) ()+putIndices arr = FL.foldlM' step (return ())++ where++ step () (i, x) = putIndex i arr x++-- | Modify a given index of an array using a modifier function.+--+-- Unsafe because it does not check the bounds of the array.+--+-- /Pre-release/+modifyIndexUnsafe, unsafeModifyIndex :: forall m a b. (MonadIO m, Unbox a) =>+ Int -> MutArray a -> (a -> (a, b)) -> m b+unsafeModifyIndex i MutArray{..} f = liftIO $ do+ let index = INDEX_OF(arrStart,i,a)+ assert (i >= 0 && INDEX_NEXT(index,a) <= arrEnd) (return ())+ r <- peekAt index arrContents+ let (x, res) = f r+ pokeAt index arrContents x+ return res++-- | Modify a given index of an array using a modifier function.+--+-- /Pre-release/+modifyIndex :: forall m a b. (MonadIO m, Unbox a) =>+ Int -> MutArray a -> (a -> (a, b)) -> m b+modifyIndex i MutArray{..} f = do+ let index = INDEX_OF(arrStart,i,a)+ if i >= 0 && INDEX_VALID(index,arrEnd,a)+ then liftIO $ do+ r <- peekAt index arrContents+ let (x, res) = f r+ pokeAt index arrContents x+ return res+ else invalidIndex "modifyIndex" i++-- | Modify the array indices generated by the supplied stream.+--+-- /Pre-release/+{-# INLINE modifyIndices #-}+modifyIndices :: forall m a . (MonadIO m, Unbox a)+ => MutArray a -> (Int -> a -> a) -> Fold m Int ()+modifyIndices arr f = FL.foldlM' step initial++ where++ initial = return ()++ step () i =+ let f1 x = (f i x, ())+ in modifyIndex i arr f1++-- | Modify each element of an array using the supplied modifier function.+--+-- This is an in-place equivalent of an immutable map operation.+--+-- /Pre-release/+modify :: forall m a. (MonadIO m, Unbox a)+ => MutArray a -> (a -> a) -> m ()+modify MutArray{..} f = liftIO $+ go arrStart++ where++ go i =+ when (INDEX_VALID(i,arrEnd,a)) $ do+ r <- peekAt i arrContents+ pokeAt i arrContents (f r)+ go (INDEX_NEXT(i,a))++-- XXX We could specify the number of bytes to swap instead of Proxy. Need+-- to ensure that the memory does not overlap.+{-# INLINE swapArrayByteIndices #-}+swapArrayByteIndices ::+ forall a. Unbox a+ => Proxy a+ -> MutByteArray+ -> Int+ -> Int+ -> IO ()+swapArrayByteIndices _ arrContents i1 i2 = do+ r1 <- peekAt i1 arrContents+ r2 <- peekAt i2 arrContents+ pokeAt i1 arrContents (r2 :: a)+ pokeAt i2 arrContents (r1 :: a)++-- | Swap the elements at two indices without validating the indices.+--+-- /Unsafe/: This could result in memory corruption if indices are not valid.+--+-- /Pre-release/+{-# INLINE unsafeSwapIndices #-}+unsafeSwapIndices :: forall m a. (MonadIO m, Unbox a)+ => Int -> Int -> MutArray a -> m ()+unsafeSwapIndices i1 i2 MutArray{..} = liftIO $ do+ let t1 = INDEX_OF(arrStart,i1,a)+ t2 = INDEX_OF(arrStart,i2,a)+ swapArrayByteIndices (Proxy :: Proxy a) arrContents t1 t2++-- | Swap the elements at two indices.+--+-- /Pre-release/+swapIndices :: forall m a. (MonadIO m, Unbox a)+ => Int -> Int -> MutArray a -> m ()+swapIndices i1 i2 MutArray{..} = liftIO $ do+ let t1 = INDEX_OF(arrStart,i1,a)+ t2 = INDEX_OF(arrStart,i2,a)+ when (i1 < 0 || INDEX_INVALID(t1,arrEnd,a))+ $ invalidIndex "swapIndices" i1+ when (i2 < 0 || INDEX_INVALID(t2,arrEnd,a))+ $ invalidIndex "swapIndices" i2+ swapArrayByteIndices (Proxy :: Proxy a) arrContents t1 t2++-------------------------------------------------------------------------------+-- Rounding+-------------------------------------------------------------------------------++-- XXX Should be done only when we are using the GHC allocator.+-- | Round up an array larger than 'largeObjectThreshold' to use the whole+-- block.+{-# INLINE roundUpLargeArray #-}+roundUpLargeArray :: Int -> Int+roundUpLargeArray size =+ if size >= largeObjectThreshold+ then+ assert+ (blockSize /= 0 && ((blockSize .&. (blockSize - 1)) == 0))+ ((size + blockSize - 1) .&. negate blockSize)+ else size++{-# INLINE isPower2 #-}+isPower2 :: Int -> Bool+isPower2 n = n .&. (n - 1) == 0++{-# INLINE roundUpToPower2 #-}+roundUpToPower2 :: Int -> Int+roundUpToPower2 n =+#if WORD_SIZE_IN_BITS == 64+ 1 + z6+#else+ 1 + z5+#endif++ where++ z0 = n - 1+ z1 = z0 .|. z0 `shiftR` 1+ z2 = z1 .|. z1 `shiftR` 2+ z3 = z2 .|. z2 `shiftR` 4+ z4 = z3 .|. z3 `shiftR` 8+ z5 = z4 .|. z4 `shiftR` 16+ z6 = z5 .|. z5 `shiftR` 32++-- | @allocBytesToBytes elem allocatedBytes@ returns the array size in bytes+-- such that the real allocation is less than or equal to @allocatedBytes@,+-- unless @allocatedBytes@ is less than the size of one array element in which+-- case it returns one element's size.+--+{-# INLINE allocBytesToBytes #-}+allocBytesToBytes :: forall a. Unbox a => a -> Int -> Int+allocBytesToBytes _ n = max (arrayPayloadSize n) (SIZE_OF(a))++-- | Given an 'Unboxed' type (unused first arg) and real allocation size+-- (including overhead), return how many elements of that type will completely+-- fit in it, returns at least 1.+--+{-# INLINE allocBytesToElemCount #-}+allocBytesToElemCount :: Unbox a => a -> Int -> Int+allocBytesToElemCount x bytes =+ let n = bytesToElemCount x (allocBytesToBytes x bytes)+ in assert (n >= 1) n++-- | The default chunk size by which the array creation routines increase the+-- size of the array when the array is grown linearly.+arrayChunkBytes :: Int+arrayChunkBytes = 1024++-------------------------------------------------------------------------------+-- Resizing+-------------------------------------------------------------------------------++-- | Round the second argument down to multiples of the first argument.+{-# INLINE roundDownTo #-}+roundDownTo :: Int -> Int -> Int+roundDownTo elemSize size = size - (size `mod` elemSize)++-- NOTE: we are passing elemSize explicitly to avoid an Unboxed constraint.+-- Since this is not inlined, Unboxed constraint leads to dictionary passing+-- which complicates some inspection tests.+--+{-# NOINLINE reallocExplicitAs #-}+reallocExplicitAs :: PinnedState -> Int -> Int -> MutArray a -> IO (MutArray a)+reallocExplicitAs ps elemSize newCapacityInBytes MutArray{..} = do+ assertM(arrEnd <= arrBound)++ let newCapMaxInBytes = roundUpLargeArray newCapacityInBytes+ oldSizeInBytes = arrEnd - arrStart+ -- XXX Should we round up instead?+ newCapInBytes = roundDownTo elemSize newCapMaxInBytes+ newLenInBytes = min oldSizeInBytes newCapInBytes++ assert (oldSizeInBytes `mod` elemSize == 0) (return ())+ assert (newLenInBytes >= 0) (return ())+ assert (newLenInBytes `mod` elemSize == 0) (return ())++ contents <-+ Unboxed.reallocSliceAs+ ps newCapInBytes arrContents arrStart newLenInBytes++ return $ MutArray+ { arrStart = 0+ , arrContents = contents+ , arrEnd = newLenInBytes+ , arrBound = newCapInBytes+ }++-- XXX We may also need reallocAs to allocate as pinned/unpinned explicitly. In+-- fact clone/clone' can be implemented using reallocAs.++-- | @realloc newCapacity array@ reallocates the array to the specified+-- capacity in bytes.+--+-- If the new size is less than the original array the array gets truncated.+-- If the new size is not a multiple of array element size then it is rounded+-- down to multiples of array size. If the new size is more than+-- 'largeObjectThreshold' then it is rounded up to the block size (4K).+--+-- If the original array is pinned, the newly allocated array is also pinned.+{-# INLINABLE reallocBytes #-}+realloc, reallocBytes :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m (MutArray a)+reallocBytes bytes arr =+ let ps =+ if isPinned arr+ then Pinned+ else Unpinned+ in liftIO $ reallocExplicitAs ps (SIZE_OF(a)) bytes arr++-- | @reallocBytesWith label capSizer minIncrBytes array@. The label is used+-- in error messages and the capSizer is used to determine the capacity of the+-- new array in bytes given the current byte length of the array.+reallocBytesWith :: forall m a. (MonadIO m , Unbox a) =>+ String+ -> (Int -> Int)+ -> Int+ -> MutArray a+ -> m (MutArray a)+reallocBytesWith label capSizer minIncrBytes arr = do+ let oldSizeBytes = arrEnd arr - arrStart arr+ newCapBytes = capSizer oldSizeBytes+ newSizeBytes = oldSizeBytes + minIncrBytes+ safeCapBytes = max newCapBytes newSizeBytes+ assertM(safeCapBytes >= newSizeBytes || error (badSize newSizeBytes))++ realloc safeCapBytes arr++ where++ badSize newSize =+ Prelude.concat+ [ label+ , ": new array size (in bytes) is less than required size "+ , show newSize+ , ". Please check the sizing function passed."+ ]++-- | @growTo newCapacity array@ changes the total capacity of the array so that+-- it is enough to hold the specified number of elements. Nothing is done if+-- the specified capacity is less than the length of the array.+--+-- If the capacity is more than 'largeObjectThreshold' then it is rounded up to+-- the block size (4K).+--+-- Nothing is done if the requested capacity is <= 0.+--+-- /Pre-release/+{-# INLINE growTo #-}+growTo, grow :: forall m a. (MonadIO m, Unbox a) =>+ Int -> MutArray a -> m (MutArray a)+growTo nElems arr@MutArray{..} = do+ let req = SIZE_OF(a) * nElems+ cap = arrBound - arrStart+ if req < cap+ then return arr+ else realloc req arr++RENAME(grow,growTo)++-- | Like 'growTo' but specifies the required reserve (unused) capacity rather+-- than the total capacity. Increases the reserve capacity, if required, to at+-- least the given amount.+--+-- Nothing is done if the requested capacity is <= 0.+--+{-# INLINE growBy #-}+growBy :: forall m a. (MonadIO m, Unbox a) =>+ Int -> MutArray a -> m (MutArray a)+growBy nElems arr@MutArray{..} = do+ let req = arrEnd - arrStart + SIZE_OF(a) * nElems+ cap = arrBound - arrStart+ if req < cap+ then return arr+ else realloc req arr++{-# DEPRECATED resize "Please use growTo instead." #-}+{-# INLINE resize #-}+resize :: forall m a. (MonadIO m, Unbox a) =>+ Int -> MutArray a -> m (MutArray a)+resize = grow++-- | Like 'growTo' but if the requested byte capacity is more than+-- 'largeObjectThreshold' then it is rounded up to the closest power of 2.+--+-- Nothing is done if the requested capacity is <= 0.+--+-- /Pre-release/+{-# INLINE growExp #-}+growExp :: forall m a. (MonadIO m, Unbox a) =>+ Int -> MutArray a -> m (MutArray a)+growExp nElems arr@MutArray{..} = do+ let req = roundUpLargeArray (SIZE_OF(a) * nElems)+ req1 =+ if req > largeObjectThreshold+ then roundUpToPower2 req+ else req+ cap = arrBound - arrStart+ if req1 < cap+ then return arr+ else realloc req1 arr++{-# DEPRECATED resizeExp "Please use growExp instead." #-}+{-# INLINE resizeExp #-}+resizeExp :: forall m a. (MonadIO m, Unbox a) =>+ Int -> MutArray a -> m (MutArray a)+resizeExp = growExp++-- | Resize the allocated memory to drop any reserved free space at the end of+-- the array and reallocate it to reduce wastage.+--+-- Up to 25% wastage is allowed to avoid reallocations. If the capacity is+-- more than 'largeObjectThreshold' then free space up to the 'blockSize' is+-- retained.+--+-- /Pre-release/+{-# INLINE rightSize #-}+rightSize :: forall m a. (MonadIO m, Unbox a) => MutArray a -> m (MutArray a)+rightSize arr@MutArray{..} = do+ assert (arrEnd <= arrBound) (return ())+ let start = arrStart+ len = arrEnd - start+ cap = arrBound - start+ target = roundUpLargeArray len+ waste = arrBound - arrEnd+ assert (target >= len) (return ())+ assert (len `mod` SIZE_OF(a) == 0) (return ())+ -- We trade off some wastage (25%) to avoid reallocations and copying.+ if target < cap && len < 3 * waste+ then realloc target arr+ else return arr++-- | Reset the array end position to start, thus truncating the array to 0+-- length, making it empty. The capacity of the array remains unchanged. The+-- array refers to the same memory as before.+{-# INLINE vacate #-}+vacate :: MutArray a -> MutArray a+vacate MutArray{..} = MutArray arrContents arrStart arrStart arrBound++-------------------------------------------------------------------------------+-- Snoc+-------------------------------------------------------------------------------++-- XXX We can possibly use a smallMutableByteArray to hold the start, end,+-- bound pointers. Using fully mutable handle will ensure that we do not have+-- multiple references to the same array of different lengths lying around and+-- potentially misused. In that case "snoc" need not return a new array (snoc+-- :: MutArray a -> a -> m ()), it will just modify the old reference. The array+-- length will be mutable. This means the length function would also be+-- monadic. Mutable arrays would behave more like files that grow in that+-- case.++-- | Snoc using a 'Ptr'. Low level reusable function.+--+-- /Internal/+{-# INLINE snocNewEnd #-}+snocNewEnd :: (MonadIO m, Unbox a) => Int -> MutArray a -> a -> m (MutArray a)+snocNewEnd newEnd arr@MutArray{..} x = liftIO $ do+ assert (newEnd <= arrBound) (return ())+ pokeAt arrEnd arrContents x+ return $ arr {arrEnd = newEnd}++-- | Really really unsafe, appends the element into the first array, may+-- cause silent data corruption or if you are lucky a segfault if the first+-- array does not have enough space to append the element.+--+-- /Internal/+{-# INLINE unsafeSnoc #-}+snocUnsafe, unsafeSnoc :: forall m a. (MonadIO m, Unbox a) =>+ MutArray a -> a -> m (MutArray a)+unsafeSnoc arr@MutArray{..} = snocNewEnd (INDEX_NEXT(arrEnd,a)) arr++-- | Like 'snoc' but does not reallocate when pre-allocated array capacity+-- becomes full.+--+-- /Internal/+{-# INLINE snocMay #-}+snocMay :: forall m a. (MonadIO m, Unbox a) =>+ MutArray a -> a -> m (Maybe (MutArray a))+snocMay arr@MutArray{..} x = do+ let newEnd = INDEX_NEXT(arrEnd,a)+ if newEnd <= arrBound+ then Just <$> snocNewEnd newEnd arr x+ else return Nothing++-- | Increments the capacity such that there is at least one unused slot even+-- if the sizer returns a size less than or equal to current size.++-- NOINLINE to move it out of the way and not pollute the instruction cache.+{-# NOINLINE snocWithRealloc #-}+snocWithRealloc :: forall m a. (MonadIO m, Unbox a) =>+ (Int -> Int)+ -> MutArray a+ -> a+ -> m (MutArray a)+snocWithRealloc sizer arr x = do+ arr1 <- reallocBytesWith "snocWith" sizer (SIZE_OF(a)) arr+ unsafeSnoc arr1 x++-- XXX sizer should use elements instead of bytes? That may increase the cost+-- but sizing is not a frequent operation.++-- | @snocWith sizer arr elem@ mutates @arr@ to append @elem@. The used length+-- of the array increases by 1.+--+-- If there is no reserved space available in @arr@ it is reallocated to a size+-- in bytes determined by the @sizer oldSizeBytes@ function, where+-- @oldSizeBytes@ is the original size of the array in bytes. The sizer+-- function should return a capacity more than or equal to the current used+-- size. If the capacity returned is less than or equal to the current used+-- size, the array is still grown by one element.+--+-- If the new array size is more than 'largeObjectThreshold' then it is rounded+-- up to 'blockSize'.+--+-- Note that the returned array may be a mutated version of the original array.+--+-- /Pre-release/+{-# INLINE snocWith #-}+snocWith :: forall m a. (MonadIO m, Unbox a) =>+ (Int -> Int)+ -> MutArray a+ -> a+ -> m (MutArray a)+snocWith sizer arr x = do+ let newEnd = INDEX_NEXT(arrEnd arr,a)+ if newEnd <= arrBound arr+ then snocNewEnd newEnd arr x+ else snocWithRealloc sizer arr x++-- | The array is mutated to append an additional element to it. If there+-- is no reserved space available in the array then it is reallocated to grow+-- it by 'arrayChunkBytes' rounded up to 'blockSize' when the size becomes more+-- than 'largeObjectThreshold'.+--+-- Note that the returned array may be a mutated version of the original array.+--+-- Performs O(n^2) copies to grow but is thrifty on memory.+--+-- /Pre-release/+{-# DEPRECATED snocLinear "Please use snocGrowBy instead. snocLinear ~ snocGrowBy (1024 / sizeOf (Proxy :: Proxy a) + 1)" #-}+{-# INLINE snocLinear #-}+snocLinear :: forall m a. (MonadIO m, Unbox a) => MutArray a -> a -> m (MutArray a)+snocLinear = snocWith (+ allocBytesToBytes (undefined :: a) arrayChunkBytes)++-- | The array is mutated to append an additional element to it.+--+-- If there is no reserved space available in the array then it is reallocated+-- to grow it by adding space for the requested number of elements, the new+-- size is rounded up to 'blockSize' when the size becomes more than+-- 'largeObjectThreshold'. If the size specified is <= 0 then the array is+-- grown by one element.+--+-- Note that the returned array may be a mutated version of the original array.+--+-- Performs O(n^2) copies to grow but is thrifty on memory compared to 'snoc'.+--+-- /Pre-release/+{-# INLINE snocGrowBy #-}+snocGrowBy :: forall m a. (MonadIO m, Unbox a) =>+ Int -> MutArray a -> a -> m (MutArray a)+snocGrowBy n = snocWith (+ (n * SIZE_OF(a)))++-- | The array is mutated to append an additional element to it. If there is no+-- reserved space available in the array then it is reallocated to double the+-- original size and aligned to a power of 2.+--+-- This is useful to reduce allocations when appending unknown number of+-- elements.+--+-- Note that the returned array may be a mutated version of the original array.+--+-- Performs only O(n * log n) copies to grow, but is liberal with memory+-- allocation compared to 'snocGrowBy'.+--+{-# INLINE snoc #-}+snoc :: forall m a. (MonadIO m, Unbox a) => MutArray a -> a -> m (MutArray a)+snoc = snocWith f++ where++ f oldSize =+ if isPower2 oldSize+ then oldSize * 2+ else roundUpToPower2 oldSize * 2++-------------------------------------------------------------------------------+-- Serialization/Deserialization using Unbox+-------------------------------------------------------------------------------++{-# INLINE pokeNewEnd #-}+pokeNewEnd :: (MonadIO m, Unbox a) =>+ Int -> MutArray Word8 -> a -> m (MutArray Word8)+pokeNewEnd newEnd arr@MutArray{..} x = liftIO $ do+ assert (newEnd <= arrBound) (return ())+ liftIO $ pokeAt arrEnd arrContents x+ return $ arr {arrEnd = newEnd}++-- | Really really unsafe, unboxes a Haskell type and appends the resulting+-- bytes to the byte array, may cause silent data corruption or if you are+-- lucky a segfault if the array does not have enough space to append the+-- element.+--+-- /Internal/+{-# INLINE unsafePoke #-}+unsafePoke :: forall m a. (MonadIO m, Unbox a) =>+ MutArray Word8 -> a -> m (MutArray Word8)+unsafePoke arr@MutArray{..} = pokeNewEnd (arrEnd + SIZE_OF(a)) arr++-- | Skip the specified number of bytes in the array. The data in the skipped+-- region remains uninitialzed.+{-# INLINE unsafePokeSkip #-}+pokeSkipUnsafe, unsafePokeSkip :: Int -> MutArray Word8 -> MutArray Word8+unsafePokeSkip n arr@MutArray{..} = do+ let newEnd = arrEnd + n+ in assert (newEnd <= arrBound) (arr {arrEnd = newEnd})++-- | Like 'poke' but does not grow the array when pre-allocated array+-- capacity becomes full.+--+-- /Internal/+{-# INLINE pokeMay #-}+pokeAppendMay, pokeMay :: forall m a. (MonadIO m, Unbox a) =>+ MutArray Word8 -> a -> m (Maybe (MutArray Word8))+pokeMay arr@MutArray{..} x = liftIO $ do+ let newEnd = arrEnd + SIZE_OF(a)+ if newEnd <= arrBound+ then Just <$> pokeNewEnd newEnd arr x+ else return Nothing++{-# NOINLINE pokeWithRealloc #-}+pokeWithRealloc :: forall m a. (MonadIO m, Unbox a) =>+ (Int -> Int)+ -> MutArray Word8+ -> a+ -> m (MutArray Word8)+pokeWithRealloc sizer arr x = do+ arr1 <- liftIO $ reallocBytesWith "pokeWithRealloc" sizer (SIZE_OF(a)) arr+ unsafePoke arr1 x++{-# INLINE pokeWith #-}+pokeWith :: forall m a. (MonadIO m, Unbox a) =>+ (Int -> Int)+ -> MutArray Word8+ -> a+ -> m (MutArray Word8)+pokeWith allocSize arr x = liftIO $ do+ let newEnd = arrEnd arr + SIZE_OF(a)+ if newEnd <= arrBound arr+ then pokeNewEnd newEnd arr x+ else pokeWithRealloc allocSize arr x++-- | Unbox a Haskell type and append the resulting bytes to a mutable byte+-- array. The array is grown exponentially when more space is needed.+--+-- Like 'snoc' except that the value is unboxed to the byte array.+--+-- Note: If you are serializing a large number of small fields, and the types+-- are statically known, then it may be more efficient to declare a record of+-- those fields and derive an 'Unbox' instance of the entire record.+--+{-# INLINE poke #-}+pokeAppend, poke :: forall m a. (MonadIO m, Unbox a) =>+ MutArray Word8 -> a -> m (MutArray Word8)+poke = pokeWith f++ where++ f oldSize =+ if isPower2 oldSize+ then oldSize * 2+ else roundUpToPower2 oldSize * 2++-- | Really really unsafe, create a Haskell value from an unboxed byte array,+-- does not check if the array is big enough, may return garbage or if you are+-- lucky may cause a segfault.+--+-- /Internal/+{-# INLINE unsafePeek #-}+peekUnconsUnsafe, unsafePeek :: forall m a. (MonadIO m, Unbox a) =>+ MutArray Word8 -> m (a, MutArray Word8)+unsafePeek MutArray{..} = do+ let start1 = arrStart + SIZE_OF(a)+ assert (start1 <= arrEnd) (return ())+ liftIO $ do+ r <- peekAt arrStart arrContents+ return (r, MutArray arrContents start1 arrEnd arrBound)++-- | Discard the specified number of bytes at the beginning of the array.+{-# INLINE unsafePeekSkip #-}+peekSkipUnsafe, unsafePeekSkip :: Int -> MutArray Word8 -> MutArray Word8+unsafePeekSkip n MutArray{..} =+ let start1 = arrStart + n+ in assert (start1 <= arrEnd) (MutArray arrContents start1 arrEnd arrBound)++-- | Create a Haskell value from its unboxed representation from the head of a+-- byte array, return the value and the remaining array.+--+-- Like 'uncons' except that the value is deserialized from the byte array.+--+-- Note: If you are deserializing a large number of small fields, and the types+-- are statically known, then it may be more efficient to declare a record of+-- those fields and derive an 'Unbox' instance of the entire record.+{-# INLINE peek #-}+peekUncons, peek :: forall m a. (MonadIO m, Unbox a) =>+ MutArray Word8 -> m (Maybe a, MutArray Word8)+peek arr@MutArray{..} = do+ let start1 = arrStart + SIZE_OF(a)+ if start1 > arrEnd+ then return (Nothing, arr)+ else liftIO $ do+ r <- peekAt arrStart arrContents+ return (Just r, MutArray arrContents start1 arrEnd arrBound)++-------------------------------------------------------------------------------+-- Random reads+-------------------------------------------------------------------------------++-- XXX Can this be deduplicated with array/foreign++-- | Return the element at the specified index without checking the bounds.+--+-- Unsafe because it does not check the bounds of the array.+{-# INLINE_NORMAL unsafeGetIndex #-}+getIndexUnsafe, unsafeGetIndex :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m a+unsafeGetIndex i MutArray{..} = do+ let index = INDEX_OF(arrStart,i,a)+ assert (i >= 0 && INDEX_VALID(index,arrEnd,a)) (return ())+ liftIO $ peekAt index arrContents++-- | /O(1)/ Lookup the element at the given index. Index starts from 0.+--+{-# INLINE getIndex #-}+getIndex :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m (Maybe a)+getIndex i MutArray{..} = do+ let index = INDEX_OF(arrStart,i,a)+ if i >= 0 && INDEX_VALID(index,arrEnd,a)+ then liftIO $ Just <$> peekAt index arrContents+ else return Nothing++{-# INLINE_NORMAL unsafeGetIndexRev #-}+unsafeGetIndexRev :: forall m a. (MonadIO m, Unbox a) =>+ Int -> MutArray a -> m a+unsafeGetIndexRev i MutArray{..} = do+ let index = RINDEX_OF(arrEnd,i,a)+ assert (i >= 0 && INDEX_VALID(index,arrEnd,a)) (return ())+ liftIO $ peekAt index arrContents++-- | /O(1)/ Lookup the element at the given index from the end of the array.+-- Index starts from 0.+--+-- Slightly faster than computing the forward index and using getIndex.+--+{-# INLINE getIndexRev #-}+getIndexRev :: forall m a. (MonadIO m, Unbox a) => Int -> MutArray a -> m a+getIndexRev i MutArray{..} = do+ let index = RINDEX_OF(arrEnd,i,a)+ if i >= 0 && index >= arrStart+ then liftIO $ peekAt index arrContents+ else invalidIndex "getIndexRev" i++data GetIndicesState contents start end st =+ GetIndicesState contents start end st++{-# INLINE indexReaderWith #-}+indexReaderWith :: (Monad m, Unbox a) =>+ (forall b. IO b -> m b) -> D.Stream m Int -> Unfold m (MutArray a) a+indexReaderWith liftio (D.Stream stepi sti) = Unfold step inject++ where++ inject (MutArray contents start end _) =+ return $ GetIndicesState contents start end sti++ {-# INLINE_LATE step #-}+ step (GetIndicesState contents start end st) = do+ r <- stepi defState st+ case r of+ D.Yield i s -> do+ x <- liftio $ getIndex i (MutArray contents start end undefined)+ case x of+ Just v -> return $ D.Yield v (GetIndicesState contents start end s)+ Nothing -> error "Invalid Index"+ D.Skip s -> return $ D.Skip (GetIndicesState contents start end s)+ D.Stop -> return D.Stop++{-# DEPRECATED getIndicesWith "Please use indexReaderWith instead." #-}+{-# INLINE getIndicesWith #-}+getIndicesWith :: (Monad m, Unbox a) =>+ (forall b. IO b -> m b) -> D.Stream m Int -> Unfold m (MutArray a) a+getIndicesWith = indexReaderWith++-- | Given an unfold that generates array indices, read the elements on those+-- indices from the supplied MutArray. An error is thrown if an index is out of+-- bounds.+--+-- /Pre-release/+{-# INLINE indexReader #-}+indexReader :: (MonadIO m, Unbox a) => Stream m Int -> Unfold m (MutArray a) a+indexReader = indexReaderWith liftIO++-- XXX DO NOT REMOVE, change the signature to use Stream instead of unfold+{-# DEPRECATED getIndices "Please use indexReader instead." #-}+{-# INLINE getIndices #-}+getIndices :: (MonadIO m, Unbox a) => Stream m Int -> Unfold m (MutArray a) a+getIndices = indexReader++-------------------------------------------------------------------------------+-- Subarrays+-------------------------------------------------------------------------------++-- XXX We can also get immutable slices.+-- XXX sliceFromLen for a stream of slices starting from a given index++-- | /O(1)/ Slice an array in constant time.+--+-- Unsafe: The bounds of the slice are not checked.+--+-- /Unsafe/+--+-- /Pre-release/+{-# INLINE unsafeSliceOffLen #-}+unsafeSliceOffLen, getSliceUnsafe :: forall a. Unbox a+ => Int -- ^ from index+ -> Int -- ^ length of the slice+ -> MutArray a+ -> MutArray a+unsafeSliceOffLen index len (MutArray contents start e _) =+ let fp1 = INDEX_OF(start,index,a)+ end = fp1 + (len * SIZE_OF(a))+ in assert+ (index >= 0 && len >= 0 && end <= e)+ -- Note: In a slice we always use bound = end so that the slice+ -- user cannot overwrite elements beyond the end of the slice.+ (MutArray contents fp1 end end)++-- | /O(1)/ Get a reference to a slice from a mutable array. Throws an error if+-- the slice extends out of the array bounds.+--+-- The capacity of the slice is the same as its length i.e. it does not have+-- any unused or reserved space at the end.+--+-- The slice shares the same underlying mutable array when created. However, if+-- the slice or the original array is reallocated by growing or shrinking then+-- it will be copied to new memory and they will no longer share the same+-- memory.+--+-- /Pre-release/+{-# INLINE sliceOffLen #-}+sliceOffLen, getSlice :: forall a. Unbox a =>+ Int -- ^ from index+ -> Int -- ^ length of the slice+ -> MutArray a+ -> MutArray a+sliceOffLen index len (MutArray contents start e _) =+ let fp1 = INDEX_OF(start,index,a)+ end = fp1 + (len * SIZE_OF(a))+ in if index >= 0 && len >= 0 && end <= e+ -- Note: In a slice we always use bound = end so that the slice user+ -- cannot overwrite elements beyond the end of the slice.+ then MutArray contents fp1 end end+ else error+ $ "sliceOffLen: invalid slice, index "+ ++ show index ++ " length " ++ show len++-------------------------------------------------------------------------------+-- In-place mutation algorithms+-------------------------------------------------------------------------------++-- XXX consider the bulk update/accumulation/permutation APIs from vector.++-- | You may not need to reverse an array because you can consume it in reverse+-- using 'readerRev'. To reverse large arrays you can read in reverse and write+-- to another array. However, in-place reverse can be useful to take adavantage+-- of cache locality and when you do not want to allocate additional memory.+--+{-# INLINE reverse #-}+reverse :: forall m a. (MonadIO m, Unbox a) => MutArray a -> m ()+reverse MutArray{..} = liftIO $ do+ let l = arrStart+ h = INDEX_PREV(arrEnd,a)+ in swap l h++ where++ swap l h = do+ when (l < h) $ do+ swapArrayByteIndices (Proxy :: Proxy a) arrContents l h+ swap (INDEX_NEXT(l,a)) (INDEX_PREV(h,a))++-- | Generate the next permutation of the sequence, returns False if this is+-- the last permutation.+--+-- /Unimplemented/+{-# INLINE permute #-}+permute :: MutArray a -> m Bool+permute = undefined++-- | Partition an array into two halves using a partitioning predicate. The+-- first half retains values where the predicate is 'False' and the second half+-- retains values where the predicate is 'True'.+--+-- /Pre-release/+{-# INLINE partitionBy #-}+partitionBy :: forall m a. (MonadIO m, Unbox a)+ => (a -> Bool) -> MutArray a -> m (MutArray a, MutArray a)+partitionBy f arr@MutArray{..} = liftIO $ do+ if arrStart >= arrEnd+ then return (arr, arr)+ else do+ ptr <- go arrStart (INDEX_PREV(arrEnd,a))+ let pl = MutArray arrContents arrStart ptr ptr+ pr = MutArray arrContents ptr arrEnd arrEnd+ return (pl, pr)++ where++ -- Invariant low < high on entry, and on return as well+ moveHigh low high = do+ h <- peekAt high arrContents+ if f h+ then+ -- Correctly classified, continue the loop+ let high1 = INDEX_PREV(high,a)+ in if low == high1+ then return Nothing+ else moveHigh low high1+ else return (Just (high, h)) -- incorrectly classified++ -- Keep a low pointer starting at the start of the array (first partition)+ -- and a high pointer starting at the end of the array (second partition).+ -- Keep incrementing the low ptr and decrementing the high ptr until both+ -- are wrongly classified, at that point swap the two and continue until+ -- the two pointer cross each other.+ --+ -- Invariants when entering this loop:+ -- low <= high+ -- Both low and high are valid locations within the array+ go low high = do+ l <- peekAt low arrContents+ if f l+ then+ -- low is wrongly classified+ if low == high+ then return low+ else do -- low < high+ r <- moveHigh low high+ case r of+ Nothing -> return low+ Just (high1, h) -> do -- low < high1+ pokeAt low arrContents h+ pokeAt high1 arrContents l+ let low1 = INDEX_NEXT(low,a)+ high2 = INDEX_PREV(high1,a)+ if low1 <= high2+ then go low1 high2+ else return low1 -- low1 > high2++ else do+ -- low is correctly classified+ let low1 = INDEX_NEXT(low,a)+ if low == high+ then return low1+ else go low1 high++-- | Shuffle corresponding elements from two arrays using a shuffle function.+-- If the shuffle function returns 'False' then do nothing otherwise swap the+-- elements. This can be used in a bottom up fold to shuffle or reorder the+-- elements.+--+-- /Unimplemented/+{-# INLINE shuffleBy #-}+shuffleBy :: (a -> a -> m Bool) -> MutArray a -> MutArray a -> m ()+shuffleBy = undefined++-- XXX we can also make the folds partial by stopping at a certain level.+--+-- | @divideBy level partition array@ performs a top down hierarchical+-- recursive partitioning fold of items in the container using the given+-- function as the partition function. Level indicates the level in the tree+-- where the fold would stop.+--+-- This performs a quick sort if the partition function is+-- 'partitionBy (< pivot)'.+--+-- /Unimplemented/+{-# INLINABLE divideBy #-}+divideBy ::+ Int -> (MutArray a -> m (MutArray a, MutArray a)) -> MutArray a -> m ()+divideBy = undefined++-- | @mergeBy level merge array@ performs a pairwise bottom up fold recursively+-- merging the pairs using the supplied merge function. Level indicates the+-- level in the tree where the fold would stop.+--+-- This performs a random shuffle if the merge function is random. If we+-- stop at level 0 and repeatedly apply the function then we can do a bubble+-- sort.+--+-- /Unimplemented/+mergeBy :: Int -> (MutArray a -> MutArray a -> m ()) -> MutArray a -> m ()+mergeBy = undefined++-- XXX Use vector instructions in arrays to find min/max/range faster++-- XXX If we can mutate the array then we can do pairwise processing to keep+-- min in the first slot and max in the second. Then compare adjacent mins and+-- keep the min of those in the first slot, and similarly for max. Thus+-- reducing the comparisons in binary fashion.+--+-- Or we can use mergeBy as defined above.+--+-- If we cannot mutate the array then we can (1) copy it and use the above+-- algo, or (2) stream the array and use pairwise concat.++-- | Find the minimum and maximum elements in the array using the provided+-- comparison function.+rangeBy :: (a -> a -> Ordering) -> MutArray a -> IO (Maybe (a, a))+rangeBy = undefined++-------------------------------------------------------------------------------+-- Size+-------------------------------------------------------------------------------++-- | /O(1)/ Get the byte length of the array.+--+{-# INLINE byteLength #-}+byteLength :: MutArray a -> Int+byteLength MutArray{..} =+ let len = arrEnd - arrStart+ in assert (len >= 0) len++-- Note: try to avoid the use of length in performance sensitive internal+-- routines as it involves a costly 'div' operation. Instead use the end ptr+-- in the array to check the bounds etc.++-- | /O(1)/ Get the used length of the array i.e. the number of elements in the+-- array.+--+-- Note that 'byteLength' is less expensive than this operation, as 'length'+-- involves a costly division operation.+--+{-# INLINE length #-}+length :: forall a. Unbox a => MutArray a -> Int+length arr =+ let elemSize = SIZE_OF(a)+ blen = byteLength arr+ in assert (blen `mod` elemSize == 0) (blen `div` elemSize)++-- | Get the total capacity of an array. An array may have space reserved+-- beyond the current used length of the array.+--+-- /Pre-release/+{-# INLINE byteCapacity #-}+byteCapacity :: MutArray a -> Int+byteCapacity MutArray{..} =+ let len = arrBound - arrStart+ in assert (len >= 0) len++-- | The remaining capacity in the array for appending more elements without+-- reallocation.+--+-- /Pre-release/+{-# INLINE bytesFree #-}+bytesFree :: MutArray a -> Int+bytesFree MutArray{..} =+ let n = arrBound - arrEnd+ in assert (n >= 0) n++{-# INLINE capacity #-}+capacity :: forall a. Unbox a => MutArray a -> Int+capacity arr =+ let elemSize = SIZE_OF(a)+ bcap = byteCapacity arr+ in assert (bcap `mod` elemSize == 0) (bcap `div` elemSize)++{-# INLINE free #-}+free :: forall a. Unbox a => MutArray a -> Int+free arr =+ let elemSize = SIZE_OF(a)+ bfree = bytesFree arr+ in assert (bfree `mod` elemSize == 0) (bfree `div` elemSize)++-------------------------------------------------------------------------------+-- Streams of arrays - Creation+-------------------------------------------------------------------------------++data GroupState s contents start end bound+ = GroupStart s+ | GroupBuffer s contents start end bound+ | GroupYield+ contents start end bound (GroupState s contents start end bound)+ | GroupFinish++{-# INLINE_NORMAL chunksOfAs #-}+chunksOfAs :: forall m a. (MonadIO m, Unbox a)+ => PinnedState -> Int -> D.Stream m a -> D.Stream m (MutArray a)+chunksOfAs ps n (D.Stream step state) =+ D.Stream step' (GroupStart state)++ where++ {-# INLINE_LATE step' #-}+ step' _ (GroupStart st) = do+ when (n <= 0) $+ -- XXX we can pass the module string from the higher level API+ error $ "Streamly.Internal.Data.MutArray.Mut.Type.chunksOf: "+ ++ "the size of arrays [" ++ show n+ ++ "] must be a natural number"+ (MutArray contents start end bound :: MutArray a) <- newAs ps n+ return $ D.Skip (GroupBuffer st contents start end bound)++ step' gst (GroupBuffer st contents start end bound) = do+ r <- step (adaptState gst) st+ case r of+ D.Yield x s -> do+ liftIO $ pokeAt end contents x+ let end1 = INDEX_NEXT(end,a)+ return $+ if end1 >= bound+ then D.Skip+ (GroupYield+ contents start end1 bound (GroupStart s))+ else D.Skip (GroupBuffer s contents start end1 bound)+ D.Skip s ->+ return $ D.Skip (GroupBuffer s contents start end bound)+ D.Stop ->+ return+ $ D.Skip (GroupYield contents start end bound GroupFinish)++ step' _ (GroupYield contents start end bound next) =+ return $ D.Yield (MutArray contents start end bound) next++ step' _ GroupFinish = return D.Stop++-- | @chunksOf n stream@ groups the elements in the input stream into arrays of+-- @n@ elements each.+--+-- Same as the following but may be more efficient:+--+-- >>> chunksOf n = Stream.foldMany (MutArray.createOf n)+--+-- /Pre-release/+{-# INLINE_NORMAL chunksOf #-}+chunksOf :: forall m a. (MonadIO m, Unbox a)+ => Int -> D.Stream m a -> D.Stream m (MutArray a)+-- XXX the idiomatic implementation leads to large regression in the D.reverse'+-- benchmark. It seems it has difficulty producing optimized code when+-- converting to StreamK. Investigate GHC optimizations.+-- chunksOf n = D.foldMany (createOf n)+chunksOf = chunksOfAs Unpinned++-- | Like 'chunksOf' but creates pinned arrays.+{-# INLINE_NORMAL chunksOf' #-}+pinnedChunksOf, chunksOf' :: forall m a. (MonadIO m, Unbox a)+ => Int -> D.Stream m a -> D.Stream m (MutArray a)+-- chunksOf' n = D.foldMany (createOf' n)+chunksOf' = chunksOfAs Pinned+RENAME_PRIME(pinnedChunksOf,chunksOf)++-- | Create arrays from the input stream using a predicate to find the end of+-- the chunk. When the predicate matches, the chunk ends, the matching element+-- is included in the chunk.+--+-- Definition:+--+-- >>> chunksEndBy p = Stream.foldMany (Fold.takeEndBy p MutArray.create)+--+{-# INLINE chunksEndBy #-}+chunksEndBy :: forall m a. (MonadIO m, Unbox a)+ => (a -> Bool) -> D.Stream m a -> D.Stream m (MutArray a)+chunksEndBy p = D.foldMany (FL.takeEndBy p create)++-- | Like 'chunksEndBy' but creates pinned arrays.+--+{-# INLINE chunksEndBy' #-}+chunksEndBy' :: forall m a. (MonadIO m, Unbox a)+ => (a -> Bool) -> D.Stream m a -> D.Stream m (MutArray a)+chunksEndBy' p = D.foldMany (FL.takeEndBy p create')++-- | Create chunks using newline as the separator, including it.+{-# INLINE chunksEndByLn #-}+chunksEndByLn :: (MonadIO m)+ => D.Stream m Word8 -> D.Stream m (MutArray Word8)+chunksEndByLn = chunksEndBy (== fromIntegral (ord '\n'))++-- | Like 'chunksEndByLn' but creates pinned arrays.+{-# INLINE chunksEndByLn' #-}+chunksEndByLn' :: (MonadIO m)+ => D.Stream m Word8 -> D.Stream m (MutArray Word8)+chunksEndByLn' = chunksEndBy' (== fromIntegral (ord '\n'))++-- | When we are buffering a stream of unknown size into an array we do not+-- know how much space to pre-allocate. So we start with the min size and emit+-- the array then keep on doubling the size every time. Thus we do not need to+-- guess the optimum chunk size.+--+-- We can incorporate this in chunksOfAs if the additional size parameter does+-- not impact perf.+--+{-# INLINE _chunksOfRange #-}+_chunksOfRange :: -- (MonadIO m, Unbox a) =>+ PinnedState -> Int -> Int -> D.Stream m a -> D.Stream m (MutArray a)+_chunksOfRange _ps _low _hi = undefined++-- XXX buffer to a list instead?+-- | Buffer the stream into arrays in memory.+{-# INLINE arrayStreamKFromStreamDAs #-}+arrayStreamKFromStreamDAs :: forall m a. (MonadIO m, Unbox a) =>+ PinnedState -> D.Stream m a -> m (StreamK m (MutArray a))+arrayStreamKFromStreamDAs ps =+ let n = allocBytesToElemCount (undefined :: a) defaultChunkSize+ in D.foldr K.cons K.nil . chunksOfAs ps n++-------------------------------------------------------------------------------+-- Streams of arrays - Flattening+-------------------------------------------------------------------------------++data FlattenState s contents a =+ OuterLoop s+ | InnerLoop s contents !Int !Int++{-# INLINE_NORMAL concatWith #-}+concatWith :: forall m a. (Monad m, Unbox a)+ => (forall b. IO b -> m b) -> D.Stream m (MutArray a) -> D.Stream m a+concatWith liftio (D.Stream step state) = D.Stream step' (OuterLoop state)++ where++ {-# INLINE_LATE step' #-}+ step' gst (OuterLoop st) = do+ r <- step (adaptState gst) st+ return $ case r of+ D.Yield MutArray{..} s ->+ D.Skip (InnerLoop s arrContents arrStart arrEnd)+ D.Skip s -> D.Skip (OuterLoop s)+ D.Stop -> D.Stop++ step' _ (InnerLoop st _ p end) | assert (p <= end) (p == end) =+ return $ D.Skip $ OuterLoop st++ step' _ (InnerLoop st contents p end) = do+ !x <- liftio $ peekAt p contents+ return $ D.Yield x (InnerLoop st contents (INDEX_NEXT(p,a)) end)++-- | Same as the following but may be more efficient due to better fusion:+--+-- >>> concat = Stream.unfoldEach MutArray.reader+--+{-# INLINE_NORMAL concat #-}+concat :: forall m a. (MonadIO m, Unbox a)+ => D.Stream m (MutArray a) -> D.Stream m a+concat = concatWith liftIO++{-# DEPRECATED flattenArrays "Please use \"unfoldMany reader\" instead." #-}+{-# INLINE flattenArrays #-}+flattenArrays :: forall m a. (MonadIO m, Unbox a)+ => D.Stream m (MutArray a) -> D.Stream m a+flattenArrays = concat++{-# INLINE_NORMAL concatRevWith #-}+concatRevWith :: forall m a. (Monad m, Unbox a)+ => (forall b. IO b -> m b) -> D.Stream m (MutArray a) -> D.Stream m a+concatRevWith liftio (D.Stream step state) = D.Stream step' (OuterLoop state)++ where++ {-# INLINE_LATE step' #-}+ step' gst (OuterLoop st) = do+ r <- step (adaptState gst) st+ return $ case r of+ D.Yield MutArray{..} s ->+ let p = INDEX_PREV(arrEnd,a)+ in D.Skip (InnerLoop s arrContents p arrStart)+ D.Skip s -> D.Skip (OuterLoop s)+ D.Stop -> D.Stop++ step' _ (InnerLoop st _ p start) | p < start =+ return $ D.Skip $ OuterLoop st++ step' _ (InnerLoop st contents p start) = do+ !x <- liftio $ peekAt p contents+ let cur = INDEX_PREV(p,a)+ return $ D.Yield x (InnerLoop st contents cur start)++-- | Use the "readerRev" unfold instead.+--+-- @concat = unfoldMany readerRev@+--+-- We can try this if there are any fusion issues in the unfold.+--+{-# INLINE_NORMAL concatRev #-}+concatRev :: forall m a. (MonadIO m, Unbox a)+ => D.Stream m (MutArray a) -> D.Stream m a+concatRev = concatRevWith liftIO++{-# DEPRECATED flattenArraysRev "Please use \"unfoldMany readerRev\" instead." #-}+{-# INLINE flattenArraysRev #-}+flattenArraysRev :: forall m a. (MonadIO m, Unbox a)+ => D.Stream m (MutArray a) -> D.Stream m a+flattenArraysRev = concatRev++-------------------------------------------------------------------------------+-- Unfolds+-------------------------------------------------------------------------------++data ArrayUnsafe a = ArrayUnsafe+ {-# UNPACK #-} !MutByteArray -- contents+ {-# UNPACK #-} !Int -- index 1+ {-# UNPACK #-} !Int -- index 2++toArrayUnsafe :: MutArray a -> ArrayUnsafe a+toArrayUnsafe (MutArray contents start end _) = ArrayUnsafe contents start end++fromArrayUnsafe ::+#ifdef DEVBUILD+ Unbox a =>+#endif+ ArrayUnsafe a -> MutArray a+fromArrayUnsafe (ArrayUnsafe contents start end) =+ MutArray contents start end end++{-# INLINE_NORMAL producerWith #-}+producerWith ::+ forall m a. (Monad m, Unbox a)+ => (forall b. IO b -> m b) -> Producer m (MutArray a) a+producerWith liftio = Producer step (return . toArrayUnsafe) extract+ where++ {-# INLINE_LATE step #-}+ step (ArrayUnsafe _ cur end)+ | assert (cur <= end) (cur == end) = return D.Stop+ step (ArrayUnsafe contents cur end) = do+ -- When we use a purely lazy Monad like Identity, we need to force a+ -- few actions for correctness and execution order sanity. We want+ -- the peek to occur right here and not lazily at some later point+ -- because we want the peek to be ordered with respect to the touch.+ !x <- liftio $ peekAt cur contents+ return $ D.Yield x (ArrayUnsafe contents (INDEX_NEXT(cur,a)) end)++ extract = return . fromArrayUnsafe++-- | Resumable unfold of an array.+--+{-# INLINE_NORMAL producer #-}+producer :: forall m a. (MonadIO m, Unbox a) => Producer m (MutArray a) a+producer = producerWith liftIO++-- | Unfold an array into a stream.+--+{-# INLINE_NORMAL reader #-}+reader :: forall m a. (MonadIO m, Unbox a) => Unfold m (MutArray a) a+reader = Producer.simplify producer++{-# INLINE_NORMAL readerRevWith #-}+readerRevWith ::+ forall m a. (Monad m, Unbox a)+ => (forall b. IO b -> m b) -> Unfold m (MutArray a) a+readerRevWith liftio = Unfold step inject+ where++ inject (MutArray contents start end _) =+ let p = INDEX_PREV(end,a)+ in return $ ArrayUnsafe contents start p++ {-# INLINE_LATE step #-}+ step (ArrayUnsafe _ start p) | p < start = return D.Stop+ step (ArrayUnsafe contents start p) = do+ !x <- liftio $ peekAt p contents+ return $ D.Yield x (ArrayUnsafe contents start (INDEX_PREV(p,a)))++-- | Unfold an array into a stream in reverse order.+--+{-# INLINE_NORMAL readerRev #-}+readerRev :: forall m a. (MonadIO m, Unbox a) => Unfold m (MutArray a) a+readerRev = readerRevWith liftIO++-------------------------------------------------------------------------------+-- to Lists and streams+-------------------------------------------------------------------------------++{-+-- Use foldr/build fusion to fuse with list consumers+-- This can be useful when using the IsList instance+{-# INLINE_LATE toListFB #-}+toListFB :: forall a b. Unbox a => (a -> b -> b) -> b -> MutArray a -> b+toListFB c n MutArray{..} = go arrStart+ where++ go p | assert (p <= arrEnd) (p == arrEnd) = n+ go p =+ -- 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.+ -- XXX+ let !x = unsafeInlineIO $ do+ r <- peekAt arrContents p+ return r+ in c x (go (PTR_NEXT(p,a)))+-}++-- XXX Monadic foldr/build fusion?+-- Reference: https://www.researchgate.net/publication/220676509_Monadic_augment_and_generalised_short_cut_fusion++-- | Convert a 'MutArray' into a list.+--+{-# INLINE toList #-}+toList :: forall m a. (MonadIO m, Unbox a) => MutArray a -> m [a]+toList MutArray{..} = liftIO $ go arrStart+ where++ go p | assert (p <= arrEnd) (p == arrEnd) = return []+ go p = do+ x <- peekAt p arrContents+ (:) x <$> go (INDEX_NEXT(p,a))++{-# INLINE_NORMAL toStreamWith #-}+toStreamWith ::+ forall m a. (Monad m, Unbox a)+ => (forall b. IO b -> m b) -> MutArray a -> D.Stream m a+toStreamWith liftio MutArray{..} = D.Stream step arrStart++ where++ {-# INLINE_LATE step #-}+ step _ p | assert (p <= arrEnd) (p == arrEnd) = return D.Stop+ step _ p = liftio $ do+ r <- peekAt p arrContents+ return $ D.Yield r (INDEX_NEXT(p,a))++-- | Convert a 'MutArray' into a stream.+--+-- >>> read = Stream.unfold MutArray.reader+--+{-# INLINE_NORMAL read #-}+read :: forall m a. (MonadIO m, Unbox a) => MutArray a -> D.Stream m a+read = toStreamWith liftIO++{-# INLINE toStreamKWith #-}+toStreamKWith ::+ forall m a. (Monad m, Unbox a)+ => (forall b. IO b -> m b) -> MutArray a -> StreamK m a+toStreamKWith liftio MutArray{..} = go arrStart++ where++ go p | assert (p <= arrEnd) (p == arrEnd) = K.nil+ | otherwise =+ let elemM = peekAt p arrContents+ in liftio elemM `K.consM` go (INDEX_NEXT(p,a))++{-# INLINE toStreamK #-}+toStreamK :: forall m a. (MonadIO m, Unbox a) => MutArray a -> StreamK m a+toStreamK = toStreamKWith liftIO++{-# INLINE_NORMAL toStreamRevWith #-}+toStreamRevWith ::+ forall m a. (Monad m, Unbox a)+ => (forall b. IO b -> m b) -> MutArray a -> D.Stream m a+toStreamRevWith liftio MutArray{..} =+ let p = INDEX_PREV(arrEnd,a)+ in D.Stream step p++ where++ {-# INLINE_LATE step #-}+ step _ p | p < arrStart = return D.Stop+ step _ p = liftio $ do+ r <- peekAt p arrContents+ return $ D.Yield r (INDEX_PREV(p,a))++-- | Convert a 'MutArray' into a stream in reverse order.+--+-- >>> readRev = Stream.unfold MutArray.readerRev+--+{-# INLINE_NORMAL readRev #-}+readRev :: forall m a. (MonadIO m, Unbox a) => MutArray a -> D.Stream m a+readRev = toStreamRevWith liftIO++{-# INLINE toStreamKRevWith #-}+toStreamKRevWith ::+ forall m a. (Monad m, Unbox a)+ => (forall b. IO b -> m b) -> MutArray a -> StreamK m a+toStreamKRevWith liftio MutArray {..} =+ let p = INDEX_PREV(arrEnd,a)+ in go p++ where++ go p | p < arrStart = K.nil+ | otherwise =+ let elemM = peekAt p arrContents+ in liftio elemM `K.consM` go (INDEX_PREV(p,a))++{-# INLINE toStreamKRev #-}+toStreamKRev :: forall m a. (MonadIO m, Unbox a) => MutArray a -> StreamK m a+toStreamKRev = toStreamKRevWith liftIO++-------------------------------------------------------------------------------+-- Folding+-------------------------------------------------------------------------------++-- XXX Need something like "MutArray m a" enforcing monadic action to avoid the+-- possibility of such APIs.+--+-- | Strict left fold of an array.+{-# INLINE_NORMAL foldl' #-}+foldl' :: (MonadIO m, Unbox a) => (b -> a -> b) -> b -> MutArray a -> m b+foldl' f z arr = D.foldl' f z $ read arr++-- | Right fold of an array.+{-# INLINE_NORMAL foldr #-}+foldr :: (MonadIO m, Unbox a) => (a -> b -> b) -> b -> MutArray a -> m b+foldr f z arr = D.foldr f z $ read arr++-- | Fold an array using a 'Fold'.+--+-- For example:+--+-- >>> findIndex eq = MutArray.fold (Fold.findIndex eq)+--+-- /Pre-release/+{-# INLINE fold #-}+fold :: (MonadIO m, Unbox a) => Fold m a b -> MutArray a -> m b+fold f arr = D.fold f (read arr)++-- | Fold an arary starting from end up to beginning.+--+-- For example:+--+-- >>> findIndexRev eq = MutArray.foldRev (Fold.findIndex eq)+--+foldRev :: (MonadIO m, Unbox a) => Fold m a b -> MutArray a -> m b+foldRev f arr = D.fold f (readRev arr)++-------------------------------------------------------------------------------+-- Folds for appending+-------------------------------------------------------------------------------++-- Note: Arrays may be allocated with a specific alignment at the beginning of+-- the array. If you need to maintain that alignment on reallocations then you+-- can resize the array manually before append, using an aligned resize+-- operation.++-- XXX Keep the bound intact to not lose any free space? Perf impact?++-- | @unsafeAppendN n arr@ appends up to @n@ input items to the supplied+-- array.+--+-- Unsafe: Do not drive the fold beyond @n@ elements, it will lead to memory+-- corruption or segfault.+--+-- Any free space left in the array after appending @n@ elements is lost.+--+-- /Internal/+{-# DEPRECATED unsafeAppendN "Please use unsafeAppendMax instead." #-}+{-# INLINE_NORMAL unsafeAppendN #-}+unsafeAppendN :: forall m a. (MonadIO m, Unbox a) =>+ Int+ -> m (MutArray a)+ -> Fold m a (MutArray a)+unsafeAppendN n action = fmap fromArrayUnsafe $ FL.foldlM' step initial++ where++ initial = do+ assert (n >= 0) (return ())+ arr@(MutArray _ _ end bound) <- action+ let free_ = bound - end+ needed = n * SIZE_OF(a)+ -- XXX We can also reallocate if the array has too much free space,+ -- otherwise we lose that space.+ arr1 <-+ if free_ < needed+ then noinline reallocBytesWith "unsafeAppendN" (+ needed) needed arr+ else return arr+ return $ toArrayUnsafe arr1++ step (ArrayUnsafe contents start end) x = do+ liftIO $ pokeAt end contents x+ -- We are using end as the bound, so no reserved space left.+ return $ ArrayUnsafe contents start (INDEX_NEXT(end,a))++-- | @unsafeAppendMax n arr@ appends up to @n@ input items to the supplied+-- array.+--+-- Unsafe: Do not drive the fold beyond @n@ elements, it will lead to memory+-- corruption or segfault.+--+-- /Internal/+{-# INLINE_NORMAL unsafeAppendMax #-}+unsafeAppendMax :: forall m a. (MonadIO m, Unbox a) =>+ Int+ -> MutArray a+ -> Fold m a (MutArray a)+unsafeAppendMax n arr@MutArray{..} =+ fmap final $ FL.foldlM' step initial++ where++ free_ = arrBound - arrEnd+ needed = n * SIZE_OF(a)+ bound = arrBound + needed - free_++ initial = do+ assert (n >= 0) (return ())+ arr1 <-+ if free_ < needed+ then noinline+ reallocBytesWith "unsafeAppendMax" (+ needed) needed arr+ else return arr+ return $ toArrayUnsafe arr1++ step (ArrayUnsafe contents start end) x = do+ liftIO $ pokeAt end contents x+ return $ ArrayUnsafe contents start (INDEX_NEXT(end,a))++ final (ArrayUnsafe contents start end) =+ MutArray contents start end bound++{-# DEPRECATED writeAppendNUnsafe "Please use unsafeAppendN instead." #-}+{-# INLINE writeAppendNUnsafe #-}+writeAppendNUnsafe :: forall m a. (MonadIO m, Unbox a) =>+ Int+ -> m (MutArray a)+ -> Fold m a (MutArray a)+writeAppendNUnsafe = unsafeAppendN++-- | Append @n@ elements to an existing array. Any free space left in the array+-- after appending @n@ elements is lost.+--+-- >>> appendN n initial = Fold.take n (MutArray.unsafeAppendN n initial)+--+{-# DEPRECATED appendN "Please use appendMax instead." #-}+{-# INLINE_NORMAL appendN #-}+appendN :: forall m a. (MonadIO m, Unbox a) =>+ Int -> m (MutArray a) -> Fold m a (MutArray a)+appendN n initial = FL.take n (unsafeAppendN n initial)++-- | Allocates space for n additional elements. The fold terminates after+-- appending n elements. If less than n elements are supplied then the space+-- for the remaining elements is guaranteed to be reserved.+--+-- >>> appendMax n arr = Fold.take n (MutArray.unsafeAppendMax n arr)+--+{-# INLINE_NORMAL appendMax #-}+appendMax :: forall m a. (MonadIO m, Unbox a) =>+ Int -> MutArray a -> Fold m a (MutArray a)+appendMax n initial = FL.take n (unsafeAppendMax n initial)++{-# DEPRECATED writeAppendN "Please use appendN instead." #-}+{-# INLINE writeAppendN #-}+writeAppendN :: forall m a. (MonadIO m, Unbox a) =>+ Int -> m (MutArray a) -> Fold m a (MutArray a)+writeAppendN = appendN++-- | @appendWith sizer action@ mutates the array generated by @action@ to+-- append the input stream. If there is no reserved space available in the+-- array it is reallocated to a size in bytes determined by @sizer oldSize@,+-- where @oldSize@ is the current size of the array in bytes. If the sizer+-- returns less than or equal to the current size then the size is incremented+-- by one element.+--+-- Note that the returned array may be a mutated version of original array.+--+-- >>> appendWith sizer = Fold.foldlM' (MutArray.snocWith sizer)+--+-- /Pre-release/+{-# INLINE appendWith #-}+appendWith :: forall m a. (MonadIO m, Unbox a) =>+ (Int -> Int) -> m (MutArray a) -> Fold m a (MutArray a)+appendWith sizer = FL.foldlM' (snocWith sizer)++{-# DEPRECATED writeAppendWith "Please use appendWith instead." #-}+{-# INLINE writeAppendWith #-}+writeAppendWith :: forall m a. (MonadIO m, Unbox a) =>+ (Int -> Int) -> m (MutArray a) -> Fold m a (MutArray a)+writeAppendWith = appendWith++-- | @append action@ mutates the array generated by @action@ to append the+-- input stream. If there is no reserved space available in the array it is+-- reallocated to double the size and aligned to power of 2.+--+-- Note that the returned array may be a mutated version of original array.+--+-- >>> append = Fold.foldlM' MutArray.snoc+--+{-# DEPRECATED append "Please use append2 instead." #-}+{-# INLINE append #-}+append :: forall m a. (MonadIO m, Unbox a) =>+ m (MutArray a) -> Fold m a (MutArray a)+-- append = appendWith (* 2)+append = FL.foldlM' snoc++-- | Fold @append2 arr@ mutates the array arr to append the input stream. If+-- there is no reserved space available in the array it is reallocated to+-- double the size and aligned to power of 2.+--+-- Note that the returned array may be a mutated version of original array.+--+-- >>> append2 arr = Fold.foldlM' MutArray.snoc (pure arr)+--+{-# INLINE append2 #-}+append2 :: (MonadIO m, Unbox a) => MutArray a -> Fold m a (MutArray a)+append2 arr = FL.foldlM' snoc (pure arr)++{-# DEPRECATED writeAppend "Please use append instead." #-}+{-# INLINE writeAppend #-}+writeAppend :: forall m a. (MonadIO m, Unbox a) =>+ m (MutArray a) -> Fold m a (MutArray a)+writeAppend = append++-- | @appendGrowBy arr@ mutates the array arr to append the input stream. If+-- there is no reserved space available in the array it is reallocated to add+-- space for the min number of elements supplied and align to block size if the+-- array becomes larger than 'largeObjectThreshold'.+--+-- Note that the returned array may be a mutated version of original array.+--+-- >>> appendGrowBy n arr = Fold.foldlM' (MutArray.snocGrowBy n) (pure arr)+--+{-# INLINE appendGrowBy #-}+appendGrowBy :: (MonadIO m, Unbox a) =>+ Int -> MutArray a -> Fold m a (MutArray a)+appendGrowBy n arr = FL.foldlM' (snocGrowBy n) (pure arr)++-------------------------------------------------------------------------------+-- Actions for Appending streams+-------------------------------------------------------------------------------++-- |+-- >>> appendStream arr = Stream.fold (MutArray.append (pure arr))+--+{-# INLINE appendStream #-}+appendStream :: (MonadIO m, Unbox a) =>+ MutArray a -> Stream m a -> m (MutArray a)+appendStream arr = D.fold (append (pure arr))++-- |+-- >>> appendStreamN n arr = Stream.fold (MutArray.appendMax n arr)+--+{-# INLINE appendStreamN #-}+appendStreamN :: (MonadIO m, Unbox a) =>+ Int -> MutArray a -> Stream m a -> m (MutArray a)+appendStreamN n arr = D.fold (appendMax n arr)++-- | The array is grown only by the required amount of space.+{-# INLINE appendCString# #-}+appendCString# :: MonadIO m => MutArray Word8 -> Addr# -> m (MutArray Word8)+appendCString# arr addr = do+ len <- liftIO $ c_strlen_pinned addr+ appendPtrN arr (Ptr addr) (fromIntegral len)++-- Note: in hsc code # is treated in a special way, so it is difficult to use+-- appendCString#+{-# INLINE appendCString #-}+appendCString :: MonadIO m => MutArray Word8 -> Ptr a -> m (MutArray Word8)+appendCString arr (Ptr addr) = appendCString# arr addr++-------------------------------------------------------------------------------+-- Folds for creating+-------------------------------------------------------------------------------++-- XXX Use "IO" instead of "m" in the alloc function++-- XXX We can carry bound as well in the state to make sure we do not lose the+-- remaining capacity. Need to check perf impact.++-- | Like 'unsafeCreateOf' but takes a new array allocator @alloc size@+-- function as argument.+--+-- >>> unsafeCreateWithOf alloc n = MutArray.unsafeAppendN (alloc n) n+--+-- /Pre-release/+{-# INLINE_NORMAL unsafeCreateWithOf #-}+unsafeCreateWithOf :: forall m a. (MonadIO m, Unbox a)+ => (Int -> m (MutArray a)) -> Int -> Fold m a (MutArray a)+unsafeCreateWithOf alloc n = fromArrayUnsafe <$> FL.foldlM' step initial++ where++ initial = toArrayUnsafe <$> alloc (max n 0)++ step (ArrayUnsafe contents start end) x = do+ liftIO $ pokeAt end contents x+ return+ $ ArrayUnsafe contents start (INDEX_NEXT(end,a))++{-# DEPRECATED writeNWithUnsafe "Please use unsafeCreateWithOf instead." #-}+{-# INLINE writeNWithUnsafe #-}+writeNWithUnsafe :: forall m a. (MonadIO m, Unbox a)+ => (Int -> m (MutArray a)) -> Int -> Fold m a (MutArray a)+writeNWithUnsafe = unsafeCreateWithOf++{-# INLINE_NORMAL writeNUnsafeAs #-}+writeNUnsafeAs :: forall m a. (MonadIO m, Unbox a)+ => PinnedState -> Int -> Fold m a (MutArray a)+writeNUnsafeAs ps = unsafeCreateWithOf (newAs ps)++-- | Like 'createOf' but does not check the array bounds when writing. The fold+-- driver must not call the step function more than 'n' times otherwise it will+-- corrupt the memory and crash. This function exists mainly because any+-- conditional in the step function blocks fusion causing 10x performance+-- slowdown.+--+-- >>> unsafeCreateOf = MutArray.unsafeCreateWithOf MutArray.emptyOf+--+{-# INLINE_NORMAL unsafeCreateOf #-}+unsafeCreateOf :: forall m a. (MonadIO m, Unbox a)+ => Int -> Fold m a (MutArray a)+unsafeCreateOf = writeNUnsafeAs Unpinned++{-# DEPRECATED writeNUnsafe "Please use unsafeCreateOf instead." #-}+{-# INLINE writeNUnsafe #-}+writeNUnsafe :: forall m a. (MonadIO m, Unbox a)+ => Int -> Fold m a (MutArray a)+writeNUnsafe = unsafeCreateOf++-- | Like 'unsafeCreateOf' but creates a pinned array.+{-# INLINE_NORMAL unsafeCreateOf' #-}+unsafePinnedCreateOf, unsafeCreateOf' :: forall m a. (MonadIO m, Unbox a)+ => Int -> Fold m a (MutArray a)+unsafeCreateOf' = writeNUnsafeAs Pinned+RENAME_PRIME(unsafePinnedCreateOf,unsafeCreateOf)++{-# DEPRECATED pinnedWriteNUnsafe "Please use unsafeCreateOf' instead." #-}+{-# INLINE pinnedWriteNUnsafe #-}+pinnedWriteNUnsafe :: forall m a. (MonadIO m, Unbox a)+ => Int -> Fold m a (MutArray a)+pinnedWriteNUnsafe = unsafeCreateOf'++-- XXX Use "IO" instead of "m" in the alloc function++-- | @createWithOf alloc n@ folds a maximum of @n@ elements into an array+-- allocated using the @alloc@ function.+--+-- The array capacity is guranteed to be at least @n@.+--+-- >>> createWithOf alloc n = Fold.take n (MutArray.unsafeCreateWithOf alloc n)+-- >>> createWithOf alloc n = MutArray.appendN (alloc n) n+--+{-# INLINE_NORMAL createWithOf #-}+createOfWith, createWithOf :: forall m a. (MonadIO m, Unbox a)+ => (Int -> m (MutArray a)) -> Int -> Fold m a (MutArray a)+createWithOf alloc n = FL.take n (unsafeCreateWithOf alloc n)++{-# DEPRECATED writeNWith "Please use createWithOf instead." #-}+{-# INLINE writeNWith #-}+writeNWith :: forall m a. (MonadIO m, Unbox a)+ => (Int -> m (MutArray a)) -> Int -> Fold m a (MutArray a)+writeNWith = createWithOf++{-# INLINE_NORMAL writeNAs #-}+writeNAs ::+ forall m a. (MonadIO m, Unbox a)+ => PinnedState+ -> Int+ -> Fold m a (MutArray a)+writeNAs ps = createWithOf (newAs ps)++-- | @createOf n@ folds a maximum of @n@ elements from the input stream to an+-- 'MutArray'.+--+-- The array capacity is guranteed to be at least @n@.+--+-- >>> createOf = MutArray.createWithOf MutArray.emptyOf+-- >>> createOf n = Fold.take n (MutArray.unsafeCreateOf n)+-- >>> createOf n = MutArray.appendMax n MutArray.empty+--+{-# INLINE_NORMAL createOf #-}+createOf :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+createOf = writeNAs Unpinned++{-# DEPRECATED writeN "Please use createOf instead." #-}+{-# INLINE writeN #-}+writeN :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+writeN = createOf++-- | Like 'createOf' but creates a pinned array.+{-# INLINE_NORMAL createOf' #-}+pinnedCreateOf, createOf' ::+ forall m a. (MonadIO m, Unbox a)+ => Int+ -> Fold m a (MutArray a)+createOf' = writeNAs Pinned+RENAME_PRIME(pinnedCreateOf,createOf)++{-# DEPRECATED pinnedWriteN "Please use createOf' instead." #-}+{-# INLINE pinnedWriteN #-}+pinnedWriteN ::+ forall m a. (MonadIO m, Unbox a)+ => Int+ -> Fold m a (MutArray a)+pinnedWriteN = createOf'++-- | Like unsafeCreateWithOf but writes the array in reverse order.+--+-- /Internal/+{-# INLINE_NORMAL writeRevNWithUnsafe #-}+writeRevNWithUnsafe :: forall m a. (MonadIO m, Unbox a)+ => (Int -> m (MutArray a)) -> Int -> Fold m a (MutArray a)+writeRevNWithUnsafe alloc n = fromArrayUnsafe <$> FL.foldlM' step initial++ where++ toArrayUnsafeRev (MutArray contents _ _ bound) =+ ArrayUnsafe contents bound bound++ initial = toArrayUnsafeRev <$> alloc (max n 0)++ step (ArrayUnsafe contents start end) x = do+ let ptr = INDEX_PREV(start,a)+ liftIO $ pokeAt ptr contents x+ return+ $ ArrayUnsafe contents ptr end++-- | Like createWithOf but writes the array in reverse order.+--+-- /Internal/+{-# INLINE_NORMAL writeRevNWith #-}+writeRevNWith :: forall m a. (MonadIO m, Unbox a)+ => (Int -> m (MutArray a)) -> Int -> Fold m a (MutArray a)+writeRevNWith alloc n = FL.take n (writeRevNWithUnsafe alloc n)++-- | Like 'createOf' but writes the array in reverse order.+--+-- /Pre-release/+{-# INLINE_NORMAL revCreateOf #-}+revCreateOf :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+revCreateOf = writeRevNWith new++{-# DEPRECATED writeRevN "Please use revCreateOf instead." #-}+{-# INLINE writeRevN #-}+writeRevN :: forall m a. (MonadIO m, Unbox a) => Int -> Fold m a (MutArray a)+writeRevN = revCreateOf++-- | @pinnedWriteNAligned align n@ folds a maximum of @n@ elements from the+-- input stream to a 'MutArray' aligned to the given size.+--+-- /Pre-release/+--+{-# INLINE_NORMAL pinnedWriteNAligned #-}+pinnedWriteNAligned :: forall m a. (MonadIO m, Unbox a)+ => Int -> Int -> Fold m a (MutArray a)+pinnedWriteNAligned align = createWithOf (pinnedNewAligned align)++-- XXX Buffer to a list instead?++-- | Buffer a stream into a stream of arrays.+--+-- >>> buildChunks n = Fold.many (MutArray.createOf n) Fold.toStreamK+--+-- Breaking an array into an array stream can be useful to consume a large+-- array sequentially such that memory of the array is released incrementatlly.+--+-- See also: 'arrayStreamKFromStreamD'.+--+-- /Unimplemented/+--+{-# INLINE_NORMAL buildChunks #-}+buildChunks :: (MonadIO m, Unbox a) =>+ Int -> Fold m a (StreamK n (MutArray a))+buildChunks n = FL.many (createOf n) FL.toStreamK++{-# DEPRECATED writeChunks "Please use buildChunks instead." #-}+{-# INLINE writeChunks #-}+writeChunks :: (MonadIO m, Unbox a) =>+ Int -> Fold m a (StreamK n (MutArray a))+writeChunks = buildChunks++-- | Grows by doubling+{-# INLINE_NORMAL writeWithAs #-}+writeWithAs :: forall m a. (MonadIO m, Unbox a)+ => PinnedState -> Int -> Fold m a (MutArray a)+-- writeWithAs ps n = FL.rmapM rightSize $ appendWith (* 2) (newAs ps n)+writeWithAs ps elemCount =+ FL.rmapM extract $ FL.foldlM' step initial++ where++ -- XXX create an empty Array if the count is <= 0?+ initial = do+ when (elemCount < 0) $ error "createWith: elemCount is negative"+ newAs ps elemCount++ step arr@(MutArray _ start end bound) x+ | INDEX_NEXT(end,a) > bound = do+ let oldSize = end - start+ newSize = max (oldSize * 2) 1+ arr1 <- liftIO $ reallocExplicitAs ps (SIZE_OF(a)) newSize arr+ unsafeSnoc arr1 x+ step arr x = unsafeSnoc arr x++ extract = liftIO . rightSize++-- XXX Compare createWith with fromStreamD which uses an array of streams+-- implementation. We can write this using buildChunks above if that is faster.+-- If createWith is faster then we should use that to implement+-- fromStreamD.+--+-- XXX The realloc based implementation needs to make one extra copy if we use+-- shrinkToFit. On the other hand, the stream of arrays implementation may+-- buffer the array chunk pointers in memory but it does not have to shrink as+-- we know the exact size in the end. However, memory copying does not seem to+-- be as expensive as the allocations. Therefore, we need to reduce the number+-- of allocations instead. Also, the size of allocations matters, right sizing+-- an allocation even at the cost of copying seems to help. Should be measured+-- on a big stream with heavy calls to toArray to see the effect.+--+-- XXX check if GHC's memory allocator is efficient enough. We can try the C+-- malloc to compare against.++-- | @createMinOf count@ folds the whole input to a single array. The array+-- starts at a size big enough to hold minCount elements, the size is doubled+-- every time the array needs to be grown.+--+-- The array capacity is guaranteed to be at least count.+--+-- /Caution! Do not use this on infinite streams./+--+-- >>> f n = MutArray.appendWith (* 2) (MutArray.emptyOf n)+-- >>> createWith n = Fold.rmapM MutArray.rightSize (f n)+-- >>> createWith n = Fold.rmapM MutArray.fromChunksK (MutArray.buildChunks n)+--+-- /Pre-release/+{-# INLINE_NORMAL createMinOf #-}+createMinOf, createWith :: forall m a. (MonadIO m, Unbox a)+ => Int -> Fold m a (MutArray a)+-- createWith n = FL.rmapM rightSize $ appendWith (* 2) (emptyOf n)+createMinOf = writeWithAs Unpinned++RENAME(createWith,createMinOf)++{-# DEPRECATED writeWith "Please use createMinOf instead." #-}+{-# INLINE writeWith #-}+writeWith :: forall m a. (MonadIO m, Unbox a)+ => Int -> Fold m a (MutArray a)+writeWith = createMinOf++-- | Fold the whole input to a single array.+--+-- Same as 'createMinOf using an initial array size of 'arrayChunkBytes' bytes+-- rounded up to the element size. If the array is expected to be smaller than+-- 'arrayChunkBytes' then use 'createMinOf' to avoid wasting memory.+--+-- /Caution! Do not use this on infinite streams./+--+{-# INLINE create #-}+create :: forall m a. (MonadIO m, Unbox a) => Fold m a (MutArray a)+create = createMinOf (allocBytesToElemCount (undefined :: a) arrayChunkBytes)++{-# DEPRECATED write "Please use create instead." #-}+{-# INLINE write #-}+write :: forall m a. (MonadIO m, Unbox a) => Fold m a (MutArray a)+write = create++-- | Like 'create' but creates a pinned array.+{-# INLINE create' #-}+pinnedCreate, create' :: forall m a. (MonadIO m, Unbox a) => Fold m a (MutArray a)+create' =+ writeWithAs Pinned (allocBytesToElemCount (undefined :: a) arrayChunkBytes)+RENAME_PRIME(pinnedCreate,create)++{-# DEPRECATED pinnedWrite "Please use create' instead." #-}+{-# INLINE pinnedWrite #-}+pinnedWrite :: forall m a. (MonadIO m, Unbox a) => Fold m a (MutArray a)+pinnedWrite = create'++-------------------------------------------------------------------------------+-- construct from streams, known size+-------------------------------------------------------------------------------++{-# INLINE_NORMAL fromStreamDNAs #-}+fromStreamDNAs :: forall m a. (MonadIO m, Unbox a)+ => PinnedState -> Int -> D.Stream m a -> m (MutArray a)+fromStreamDNAs ps limit str = do+ (arr :: MutArray a) <- newAs ps limit+ end <- D.foldlM'+ (fwrite (arrContents arr))+ (return $ arrEnd arr)+ $ D.take limit str+ return $ arr {arrEnd = end}++ where++ fwrite arrContents ptr x = do+ liftIO $ pokeAt ptr arrContents x+ return $ INDEX_NEXT(ptr,a)++-- | Create a MutArray of given size from a stream.+--+-- >>> fromStreamN n = Stream.fold (MutArray.createOf n)+--+{-# INLINE_NORMAL fromStreamN #-}+fromStreamN :: forall m a. (MonadIO m, Unbox a)+ => Int -> D.Stream m a -> m (MutArray a)+-- fromStreamDN n = D.fold (createOf n)+fromStreamN = fromStreamDNAs Unpinned++{-# DEPRECATED fromStreamDN "Please use fromStreamN instead." #-}+{-# INLINE fromStreamDN #-}+fromStreamDN :: forall m a. (MonadIO m, Unbox a)+ => Int -> D.Stream m a -> m (MutArray a)+fromStreamDN = fromStreamN++-- | Create a 'MutArray' from the first N elements of a list. The array is+-- allocated to size N, if the list terminates before N elements then the+-- array may hold less than N elements.+--+{-# INLINABLE fromListN #-}+fromListN :: (MonadIO m, Unbox a) => Int -> [a] -> m (MutArray a)+fromListN n xs = fromStreamN n $ D.fromList xs++-- | Like 'fromListN' but creates a pinned array.+{-# INLINABLE fromListN' #-}+pinnedFromListN, fromListN' :: (MonadIO m, Unbox a) => Int -> [a] -> m (MutArray a)+fromListN' n xs = fromStreamDNAs Pinned n $ D.fromList xs+RENAME_PRIME(pinnedFromListN,fromListN)++-- | Like fromListN but writes the array in reverse order.+--+-- /Pre-release/+{-# INLINE fromListRevN #-}+fromListRevN :: (MonadIO m, Unbox a) => Int -> [a] -> m (MutArray a)+fromListRevN n xs = D.fold (revCreateOf n) $ D.fromList xs++-- | Convert a pure stream in Identity monad to a mutable array.+{-# INLINABLE fromPureStreamN #-}+fromPureStreamN :: (MonadIO m, Unbox a) =>+ Int -> Stream Identity a -> m (MutArray a)+fromPureStreamN n = D.fold (createOf n) . D.generalizeInner++-- | Convert a pure stream in Identity monad to a mutable array.+{-# INLINABLE fromPureStream #-}+fromPureStream :: (MonadIO m, Unbox a) => Stream Identity a -> m (MutArray a)+fromPureStream = D.fold create . D.generalizeInner++-- | @fromPtrN len addr@ copies @len@ bytes from @addr@ into an array.+--+-- /Unsafe:/+--+-- The caller has to ensure that:+--+-- 1. the pointer is pinned and alive during the call.+-- 2. the pointer passed is valid up to the given length.+--+{-# INLINABLE fromPtrN #-}+fromPtrN :: MonadIO m => Int -> Ptr Word8 -> m (MutArray Word8)+fromPtrN len addr = do+ -- memcpy is better than stream copy when the size is known.+ -- XXX We can implement a stream copy in a similar way by streaming Word64+ -- first and then remaining Word8.+ (arr :: MutArray Word8) <- emptyOf len+ let mbarr = getMutByteArray# (arrContents arr)+ _ <- liftIO $ c_memcpy_pinned_src mbarr addr (fromIntegral len)+ pure (arr { arrEnd = len })++-- | @fromCString# addr@ copies a C string consisting of bytes and+-- terminated by a null byte, into a Word8 array. The null byte is not copied.+--+-- >>> MutArray.fromCString# "hello"#+--+-- /Unsafe:/+--+-- The caller has to ensure that:+--+-- 1. the @addr@ is pinned and alive during the call.+-- 2. the pointer passed is valid up to the point where null byte is found.+--+{-# INLINABLE fromCString# #-}+fromCString# :: MonadIO m => Addr# -> m (MutArray Word8)+fromCString# addr = do+ -- It is better to count the size first and allocate exact space.+ -- Also, memcpy is better than stream copy when the size is known.+ -- C strlen compares 4 bytes at a time, so is better than the stream+ -- version. https://github.com/bminor/glibc/blob/master/string/strlen.c+ -- XXX We can possibly use a stream of Word64 to do the same.+ -- fromByteStr# addr = fromPureStream (D.fromByteStr# addr)+ len <- liftIO $ c_strlen_pinned addr+ fromPtrN (fromIntegral len) (Ptr addr)++{-# DEPRECATED fromByteStr# "Please fromCString# instead." #-}+{-# INLINABLE fromByteStr# #-}+fromByteStr# :: MonadIO m => Addr# -> m (MutArray Word8)+fromByteStr# = fromCString#++-- | @fromW16CString# addr@ copies a C string consisting of 16-bit wide chars+-- and terminated by a 16-bit null char, into a Word16 array. The null+-- character is not copied.+--+-- Useful for copying UTF16 strings on Windows.+--+-- /Unsafe:/+--+-- The caller has to ensure that:+--+-- 1. the @addr@ is pinned and alive during the call.+-- 2. the pointer passed is valid up to the point where null Word16 is found.+--+{-# INLINABLE fromW16CString# #-}+fromW16CString# :: MonadIO m => Addr# -> m (MutArray Word16)+fromW16CString# addr = do+ -- XXX this can be done faster if we process one Word64 at a time+ w16len <- D.fold FL.length $ D.fromW16CString# addr+ let bytes = w16len * 2+ arr <- fromPtrN bytes (Ptr addr)+ pure $ unsafeCast arr++-------------------------------------------------------------------------------+-- convert a stream of arrays to a single array by reallocating and copying+-------------------------------------------------------------------------------++-- XXX Both of these implementations of splicing seem to perform equally well.+-- We need to perform benchmarks over a range of sizes though.++-- | Also see 'fromChunksK'.+{-# INLINE fromChunksRealloced #-}+fromChunksRealloced :: forall m a. (MonadIO m, Unbox a)+ => Stream m (MutArray a) -> m (MutArray a)+fromChunksRealloced s = do+ res <- D.uncons s+ case res of+ Just (a, strm) -> do+ arr <- D.foldlM' spliceExp (pure a) strm+ -- Reallocation is exponential so there may be 50% empty space in+ -- worst case. One more reallocation to reclaim the space.+ rightSize arr+ Nothing -> pure nil++-------------------------------------------------------------------------------+-- convert a stream of arrays to a single array by buffering arrays first+-------------------------------------------------------------------------------++{-# INLINE arrayStreamKLength #-}+arrayStreamKLength :: (Monad m, Unbox a) => StreamK m (MutArray a) -> m Int+arrayStreamKLength as = K.foldl' (+) 0 (K.map length as)++-- | Convert an array stream to an array. Note that this requires peak memory+-- that is double the size of the array stream.+--+{-# INLINE fromChunkskAs #-}+fromChunkskAs :: (Unbox a, MonadIO m) =>+ PinnedState -> StreamK m (MutArray a) -> m (MutArray a)+fromChunkskAs ps as = do+ len <- arrayStreamKLength as+ arr <- newAs ps len+ -- XXX is StreamK fold faster or StreamD fold?+ K.foldlM' unsafeSplice (pure arr) as+ -- fromStreamDN len $ D.unfoldMany reader $ D.fromStreamK as++-- XXX Need to compare this with fromChunks and fromChunkList and keep the+-- fastest or simplest one if all are equally fast.++-- | Convert an array stream to an array. Note that this requires peak memory+-- that is double the size of the array stream.+--+-- Also see 'fromChunksRealloced'.+--+{-# INLINE fromChunksK #-}+fromChunksK :: (Unbox a, MonadIO m) =>+ StreamK m (MutArray a) -> m (MutArray a)+fromChunksK = fromChunkskAs Unpinned++{-# DEPRECATED fromArrayStreamK "Please use fromChunksK instead." #-}+{-# INLINE fromArrayStreamK #-}+fromArrayStreamK :: (Unbox a, MonadIO m) =>+ StreamK m (MutArray a) -> m (MutArray a)+fromArrayStreamK = fromChunksK++{-# INLINE fromStreamDAs #-}+fromStreamDAs ::+ (MonadIO m, Unbox a) => PinnedState -> D.Stream m a -> m (MutArray a)+fromStreamDAs ps m =+ arrayStreamKFromStreamDAs Unpinned m >>= fromChunkskAs ps++-- | Create an 'Array' from a stream. This is useful when we want to create a+-- single array from a stream of unknown size. 'createOf' is at least twice+-- as efficient when the size is already known.+--+-- Note that if the input stream is too large memory allocation for the array+-- may fail. When the stream size is not known, `chunksOf` followed by+-- processing of indvidual arrays in the resulting stream should be preferred.+--+-- /Pre-release/+{-# INLINE fromStream #-}+fromStream :: (MonadIO m, Unbox a) => Stream m a -> m (MutArray a)+fromStream = fromStreamDAs Unpinned++-- fromStream (Stream m) = P.fold create m+-- CAUTION: a very large number (millions) of arrays can degrade performance+-- due to GC overhead because we need to buffer the arrays before we flatten+-- all the arrays.+--+-- XXX Compare if this is faster or "fold create".+--+-- | We could take the approach of doubling the memory allocation on each+-- overflow. This would result in more or less the same amount of copying as in+-- the chunking approach. However, if we have to shrink in the end then it may+-- result in an extra copy of the entire data.+--+-- >>> fromStreamD = StreamD.fold MutArray.create+--+{-# INLINE fromStreamD #-}+{-# DEPRECATED fromStreamD "Please use fromStream instead." #-}+fromStreamD :: (MonadIO m, Unbox a) => D.Stream m a -> m (MutArray a)+fromStreamD = fromStream++-- | Create a 'MutArray' from a list. The list must be of finite size.+--+{-# INLINE fromList #-}+fromList :: (MonadIO m, Unbox a) => [a] -> m (MutArray a)+fromList xs = fromStreamD $ D.fromList xs++-- | Like 'fromList' but creates a pinned array.+{-# INLINE fromList' #-}+pinnedFromList, fromList' :: (MonadIO m, Unbox a) => [a] -> m (MutArray a)+fromList' xs = fromStreamDAs Pinned $ D.fromList xs+RENAME_PRIME(pinnedFromList,fromList)++-- XXX We are materializing the whole list first for getting the length. Check+-- if the 'fromList' like chunked implementation would fare better.++-- | Like 'fromList' but writes the contents of the list in reverse order.+{-# INLINE fromListRev #-}+fromListRev :: (MonadIO m, Unbox a) => [a] -> m (MutArray a)+fromListRev xs = fromListRevN (Prelude.length xs) xs++-------------------------------------------------------------------------------+-- Cloning+-------------------------------------------------------------------------------++-- Arrays are aligned on 64-bit boundaries. The fastest way to copy an array is+-- to unsafeCast it to Word64, read it, write it to Word64 array and unsafeCast+-- it again. We can use SIMD read/write as well.++{-# INLINE cloneAs #-}+cloneAs ::+ ( MonadIO m+#ifdef DEVBUILD+ , Unbox a+#endif+ )+ => PinnedState -> MutArray a -> m (MutArray a)+cloneAs ps src =+ do+ let startSrc = arrStart src+ srcLen = arrEnd src - startSrc+ newArrContents <-+ Unboxed.unsafeCloneSliceAs ps startSrc srcLen (arrContents src)+ return $ MutArray newArrContents 0 srcLen srcLen++-- | Clone the elements of a MutArray. Does not clone the reserve capacity.+--+-- To clone a slice of "MutArray" you can create a slice with "unsafeSliceOffLen"+-- and then use "clone".+--+-- The new "MutArray" is unpinned in nature. Use "clone'" to clone the+-- MutArray in pinned memory.+{-# INLINE clone #-}+clone ::+ ( MonadIO m+#ifdef DEVBUILD+ , Unbox a+#endif+ )+ => MutArray a -> m (MutArray a)+clone = cloneAs Unpinned++-- Similar to "clone" but uses pinned memory.+{-# INLINE clone' #-}+pinnedClone, clone' ::+ ( MonadIO m+#ifdef DEVBUILD+ , Unbox a+#endif+ )+ => MutArray a -> m (MutArray a)+clone' = cloneAs Pinned+RENAME_PRIME(pinnedClone,clone)++-------------------------------------------------------------------------------+-- Combining+-------------------------------------------------------------------------------++-- | Copy two arrays into a newly allocated array. If the first array is pinned+-- the spliced array is also pinned.+--+-- Note: If you freeze and splice it will create a new array.+{-# INLINE spliceCopy #-}+spliceCopy :: forall m a. MonadIO m =>+#ifdef DEVBUILD+ Unbox a =>+#endif+ MutArray a -> MutArray a -> m (MutArray a)+spliceCopy arr1 arr2 = do+ let start1 = arrStart arr1+ start2 = arrStart arr2+ len1 = arrEnd arr1 - start1+ len2 = arrEnd arr2 - start2+ let len = len1 + len2+ newArrContents <-+ if Unboxed.isPinned (arrContents arr1)+ then liftIO $ Unboxed.new' len+ else liftIO $ Unboxed.new len+ unsafePutSlice (arrContents arr1) start1 newArrContents 0 len1+ unsafePutSlice (arrContents arr2) start2 newArrContents len1 len2+ return $ MutArray newArrContents 0 len len++-- | Really really unsafe, appends the second array into the first array. If+-- the first array does not have enough space it may cause silent data+-- corruption or if you are lucky a segfault.+{-# INLINE unsafeSplice #-}+spliceUnsafe, unsafeSplice :: MonadIO m =>+ MutArray a -> MutArray a -> m (MutArray a)+unsafeSplice dst src = do+ let startSrc = arrStart src+ srcLen = arrEnd src - startSrc+ endDst = arrEnd dst+ assertM(endDst + srcLen <= arrBound dst)+ unsafePutSlice+ (arrContents src) startSrc (arrContents dst) endDst srcLen+ return $ dst {arrEnd = endDst + srcLen}++-- | Append specified number of bytes from a given pointer to the MutArray.+--+-- /Unsafe:/+--+-- The caller has to ensure that:+--+-- 1. the MutArray is valid up to the given length.+-- 2. the source pointer is pinned and alive during the call.+-- 3. the pointer passed is valid up to the given length.+--+{-# INLINE unsafeAppendPtrN #-}+unsafeAppendPtrN :: MonadIO m =>+ MutArray Word8 -> Ptr Word8 -> Int -> m (MutArray Word8)+unsafeAppendPtrN arr ptr ptrLen = do+ let newEnd = arrEnd arr + ptrLen+ assertM(newEnd <= arrBound arr)+ Unboxed.unsafePutPtrN ptr (arrContents arr) (arrEnd arr) ptrLen+ return $ arr {arrEnd = newEnd}++{-# INLINE appendPtrN #-}+appendPtrN :: MonadIO m =>+ MutArray Word8 -> Ptr Word8 -> Int -> m (MutArray Word8)+appendPtrN arr ptr ptrLen = do+ arr1 <- growBy ptrLen arr+ unsafeAppendPtrN arr1 ptr ptrLen++-- | @spliceWith sizer dst src@ mutates @dst@ to append @src@. If there is no+-- reserved space available in @dst@ it is reallocated to a size determined by+-- the @sizer dstBytes srcBytes@ function, where @dstBytes@ is the size of the+-- first array and @srcBytes@ is the size of the second array, in bytes.+--+-- Note that the returned array may be a mutated version of first array.+--+-- /Pre-release/+{-# INLINE spliceWith #-}+spliceWith :: forall m a. (MonadIO m, Unbox a) =>+ (Int -> Int -> Int) -> MutArray a -> MutArray a -> m (MutArray a)+spliceWith sizer dst@(MutArray _ start end bound) src = do+{-+ let f = appendWith (`sizer` byteLength src) (return dst)+ in D.fold f (toStreamD src)+-}+ assert (end <= bound) (return ())+ let srcBytes = arrEnd src - arrStart src++ dst1 <-+ if end + srcBytes >= bound+ then do+ let dstBytes = end - start+ newSizeInBytes = sizer dstBytes srcBytes+ when (newSizeInBytes < dstBytes + srcBytes)+ $ error+ $ "splice: newSize is less than the total size "+ ++ "of arrays being appended. Please check the "+ ++ "sizer function passed."+ realloc newSizeInBytes dst+ else return dst+ unsafeSplice dst1 src++-- | The first array is extended in-place to append the second array. If there is no+-- reserved space available in the first array then a new allocation of exact+-- required size is done.+--+-- Note that the returned array may be an extended version of first array,+-- referring to the same memory as the original array.+--+-- >>> splice = MutArray.spliceWith (+)+--+-- If the original array is pinned the spliced array is also pinned.+--+-- /Pre-release/+{-# INLINE splice #-}+splice :: (MonadIO m, Unbox a) => MutArray a -> MutArray a -> m (MutArray a)+splice = spliceWith (+)++-- | Like 'append' but the growth of the array is exponential. Whenever a new+-- allocation is required the previous array size is at least doubled.+--+-- This is useful to reduce allocations when folding many arrays together.+--+-- Note that the returned array may be a mutated version of first array.+--+-- >>> spliceExp = MutArray.spliceWith (\l1 l2 -> max (l1 * 2) (l1 + l2))+--+-- /Pre-release/+{-# INLINE spliceExp #-}+spliceExp :: (MonadIO m, Unbox a) => MutArray a -> MutArray a -> m (MutArray a)+spliceExp = spliceWith (\l1 l2 -> max (l1 * 2) (l1 + l2))++-------------------------------------------------------------------------------+-- Splitting+-------------------------------------------------------------------------------++{-# INLINE splitUsing #-}+splitUsing :: (MonadIO m, Unbox a) =>+ ((a -> Bool) -> Stream m a -> Stream m (Int, Int))+ -> (a -> Bool) -> MutArray a -> Stream m (MutArray a)+splitUsing f predicate arr =+ fmap (\(i, len) -> unsafeSliceOffLen i len arr)+ $ f predicate (read arr)++-- | Generate a stream of array slices using a predicate. The array element+-- matching the predicate is dropped.+--+-- /Pre-release/+{-# INLINE splitEndBy_ #-}+splitEndBy_, splitOn :: (MonadIO m, Unbox a) =>+ (a -> Bool) -> MutArray a -> Stream m (MutArray a)+splitEndBy_ = splitUsing D.indexEndBy_++RENAME(splitOn,splitEndBy_)++-- | Generate a stream of array slices using a predicate. The array element+-- matching the predicate is included.+--+-- /Pre-release/+{-# INLINE splitEndBy #-}+splitEndBy :: (MonadIO m, Unbox a) =>+ (a -> Bool) -> MutArray a -> Stream m (MutArray a)+splitEndBy = splitUsing D.indexEndBy++-- XXX See advanceStartTill for a potential performance issue with this type of+-- code which needed to be investigated. Measure the perf of this and use+-- advanceStartTill if that turns out to be better.++{-# INLINE breakUsing #-}+breakUsing :: (MonadIO m, Unbox a) =>+ Int -> ((a -> Bool) -> Stream m a -> Stream m (Int, Int))+ -> (a -> Bool) -> MutArray a -> m (MutArray a, MutArray a)+breakUsing adj indexer predicate arr = do+ -- XXX Use MutArray.fold Fold.findIndex instead.+ r <- D.head $ indexer predicate (read arr)+ case r of+ Just (i, len) ->+ -- assert (i == 0)+ -- XXX avoid using length (div operation)+ let arrLen = length arr+ i1 = len + adj+ arr1 =+ if i1 >= arrLen+ then empty+ else unsafeSliceOffLen i1 (arrLen - i1) arr+ in return (unsafeSliceOffLen i len arr, arr1)+ Nothing -> return (arr, empty)++{-# INLINE revBreakUsing #-}+revBreakUsing :: (MonadIO m, Unbox a) =>+ Bool -> (a -> Bool) -> MutArray a -> m (MutArray a, MutArray a)+revBreakUsing withSep predicate arr = do+ let indexer = if withSep then D.indexEndBy else D.indexEndBy_+ adj = if withSep then 0 else 1+ -- XXX Use MutArray.foldRev Fold.findIndex instead.+ r <- D.head $ indexer predicate (readRev arr)+ case r of+ Just (_, len) ->+ -- assert (i == 0)+ -- XXX avoid using length (div operation)+ let arrLen = length arr+ len1 = len + adj+ arr0 =+ if len1 >= arrLen+ then empty+ else unsafeSliceOffLen 0 (arrLen - len1) arr+ arr1 = unsafeSliceOffLen (arrLen - len) len arr+ in return (arr0, arr1)+ Nothing -> return (arr, empty)++-- |+-- >>> arr <- MutArray.fromList "hello world"+-- >>> (a,b) <- MutArray.breakEndBy (== ' ') arr+-- >>> MutArray.toList a+-- "hello "+-- >>> MutArray.toList b+-- "world"+--+{-# INLINE breakEndBy #-}+breakEndBy :: (MonadIO m, Unbox a) =>+ (a -> Bool) -> MutArray a -> m (MutArray a, MutArray a)+breakEndBy = breakUsing 0 D.indexEndBy++-- | Break the array into two slices when the predicate succeeds. The array+-- element matching the predicate is dropped. If the predicate never succeeds+-- the second array is empty.+--+-- >>> arr <- MutArray.fromList "hello world"+-- >>> (a,b) <- MutArray.breakEndBy_ (== ' ') arr+-- >>> MutArray.toList a+-- "hello"+-- >>> MutArray.toList b+-- "world"+--+-- /Pre-release/+{-# INLINE breakEndBy_ #-}+breakEndBy_ :: (MonadIO m, Unbox a) =>+ (a -> Bool) -> MutArray a -> m (MutArray a, MutArray a)+breakEndBy_ = breakUsing 1 D.indexEndBy_++-- |+--+-- >>> arr <- MutArray.fromList "hello world"+-- >>> (a,b) <- MutArray.revBreakEndBy (== ' ') arr+-- >>> MutArray.toList a+-- "hello"+-- >>> MutArray.toList b+-- " world"+--+{-# INLINE revBreakEndBy #-}+revBreakEndBy :: (MonadIO m, Unbox a) =>+ (a -> Bool) -> MutArray a -> m (MutArray a, MutArray a)+revBreakEndBy = revBreakUsing True++-- |+--+-- >>> arr <- MutArray.fromList "hello world"+-- >>> (a,b) <- MutArray.revBreakEndBy_ (== ' ') arr+-- >>> MutArray.toList a+-- "hello"+-- >>> MutArray.toList b+-- "world"+--+{-# INLINE revBreakEndBy_ #-}+revBreakEndBy_ :: (MonadIO m, Unbox a) =>+ (a -> Bool) -> MutArray a -> m (MutArray a, MutArray a)+revBreakEndBy_ = revBreakUsing False++-- Note: We could return empty array instead of Nothing. But then we cannot+-- distinguish if the separator was found in the end or was not found at all.+-- XXX Do we need to distinguish that?++-- | Drops the separator byte+{-# INLINE breakEndByWord8_ #-}+breakEndByWord8_, breakOn :: MonadIO m+ => Word8 -> MutArray Word8 -> m (MutArray Word8, Maybe (MutArray Word8))+breakEndByWord8_ sep arr@MutArray{..} = liftIO $ do+ -- XXX We do not need memchr here, we can use a Haskell equivalent.+ -- Need efficient stream based primitives that work on Word64.+ let marr = getMutByteArray# arrContents+ len = fromIntegral (arrEnd - arrStart)+ sepIndex <- c_memchr_index marr (fromIntegral arrStart) sep len+ let intIndex = fromIntegral sepIndex+ return $+ if sepIndex >= len+ then (arr, Nothing)+ else+ ( MutArray+ { arrContents = arrContents+ , arrStart = arrStart+ , arrEnd = arrStart + intIndex -- exclude the separator+ , arrBound = arrStart + intIndex+ }+ , Just $ MutArray+ { arrContents = arrContents+ , arrStart = arrStart + (intIndex + 1)+ , arrEnd = arrEnd+ , arrBound = arrBound+ }+ )+RENAME(breakOn,breakEndByWord8_)++-- | Like 'breakAt' but does not check whether the index is valid.+--+-- >>> unsafeBreakAt i arr = (MutArray.unsafeSliceOffLen 0 i arr, MutArray.unsafeSliceOffLen i (MutArray.length arr - i) arr)+--+{-# INLINE unsafeBreakAt #-}+unsafeBreakAt :: forall a. Unbox a =>+ Int -> MutArray a -> (MutArray a, MutArray a)+unsafeBreakAt i MutArray{..} =+ -- (unsafeSliceOffLen 0 i arr, unsafeSliceOffLen i (length arr - i) arr)+ let off = i * SIZE_OF(a)+ p = arrStart + off+ in ( MutArray+ { arrContents = arrContents+ , arrStart = arrStart+ , arrEnd = p+ , arrBound = p+ }+ , MutArray+ { arrContents = arrContents+ , arrStart = p+ , arrEnd = arrEnd+ , arrBound = arrBound+ }+ )++-- | Create two slices of an array without copying the original array. The+-- specified index @i@ is the first index of the second slice.+--+{-# INLINE breakAt #-}+breakAt, splitAt+ :: forall a. Unbox a => Int -> MutArray a -> (MutArray a, MutArray a)+breakAt i arr =+ let maxIndex = length arr - 1+ in if i < 0+ then error "sliceAt: negative array index"+ else if i > maxIndex+ then error $ "sliceAt: specified array index " ++ show i+ ++ " is beyond the maximum index " ++ show maxIndex+ else unsafeBreakAt i arr+RENAME(splitAt,breakAt)++-------------------------------------------------------------------------------+-- Casting+-------------------------------------------------------------------------------++-- | Cast an array having elements of type @a@ into an array having elements of+-- type @b@. The array size must be a multiple of the size of type @b@+-- otherwise accessing the last element of the array may result into a crash or+-- a random value.+--+-- /Pre-release/+--+castUnsafe, unsafeCast ::+#ifdef DEVBUILD+ Unbox b =>+#endif+ MutArray a -> MutArray b+unsafeCast (MutArray contents start end bound) =+ MutArray contents start end bound++-- | Cast an @MutArray a@ into an @MutArray Word8@.+--+asBytes :: MutArray a -> MutArray Word8+asBytes = unsafeCast++-- | Cast an array having elements of type @a@ into an array having elements of+-- type @b@. The length of the array should be a multiple of the size of the+-- target element otherwise 'Nothing' is returned.+--+cast :: forall a b. Unbox b => MutArray a -> Maybe (MutArray b)+cast arr =+ let len = byteLength arr+ r = len `mod` SIZE_OF(b)+ in if r /= 0+ then Nothing+ else Just $ unsafeCast arr++-- XXX Should we just name it asPtr, the unsafety is implicit for any pointer+-- operations. And we are safe from Haskell perspective because we will be+-- pinning the memory.++-- | NOTE: this is deprecated because it can lead to accidental problems if the+-- user tries to use it to mutate the array because it does not return the new+-- array after pinning.+{-# DEPRECATED unsafePinnedAsPtr "Pin the array and then use unsafeAsPtr." #-}+{-# INLINE unsafePinnedAsPtr #-}+unsafePinnedAsPtr :: MonadIO m => MutArray a -> (Ptr a -> Int -> m b) -> m b+unsafePinnedAsPtr mutarr f = do+ let arr0 = arrContents mutarr+ arr <- liftIO $ Unboxed.pin arr0+ let !ptr = Ptr (byteArrayContents#+ (unsafeCoerce# (getMutByteArray# arr)))+ r <- f (ptr `plusPtr` arrStart mutarr) (byteLength mutarr)+ liftIO $ Unboxed.touch arr+ return r++{-# DEPRECATED asPtrUnsafe "Pin the array and then use unsafeAsPtr." #-}+{-# INLINE asPtrUnsafe #-}+asPtrUnsafe :: MonadIO m => MutArray a -> (Ptr a -> m b) -> m b+asPtrUnsafe a f = unsafePinnedAsPtr a (\p _ -> f p)++-- | @unsafeAsPtr arr f@, f is a function used as @f ptr len@ where @ptr@ is a+-- pointer to the beginning of array and @len@ is the byte-length of the array.+--+-- /Unsafe/ WARNING:+--+-- 1. The array must be pinned, otherwise it will lead to memory corruption.+-- 2. The user must not use the pointer beyond the supplied length.+--+-- /Pre-release/+--+{-# INLINE unsafeAsPtr #-}+unsafeAsPtr :: MonadIO m => MutArray a -> (Ptr a -> Int -> IO b) -> m b+unsafeAsPtr arr f =+ Unboxed.unsafeAsPtr+ (arrContents arr)+ (\ptr -> f (ptr `plusPtr` arrStart arr) (byteLength arr))++-- | @unsafeCreateWithPtr' capacity populator@ creates a pinned array of+-- @capacity@ bytes and invokes the @populator@ function to populate it.+-- @populator ptr len@ gets the pointer to the array and MUST return the amount+-- of the capacity populated in bytes.+--+-- /Unsafe/ because the populator is allowed to use the pointer only up to+-- specified length. In other words, bytes populated MUST be less than or equal+-- to the total capacity.+{-# INLINE unsafeCreateWithPtr' #-}+unsafeCreateWithPtr'+ :: MonadIO m => Int -> (Ptr Word8 -> IO Int) -> m (MutArray Word8)+unsafeCreateWithPtr' cap pop = do+ (arr :: MutArray Word8) <- emptyOf' cap+ len <- Unboxed.unsafeAsPtr (arrContents arr) pop+ when (len > cap) (error (errMsg len))+ -- arrStart == 0+ pure (arr { arrEnd = len })+++ where++ errMsg len =+ "unsafeCreateWithPtr': length > capacity, "+ ++ "length = " ++ show len ++ ", "+ ++ "capacity = " ++ show cap++asCString :: MutArray a -> (CString -> IO b) -> IO b+asCString arr act = do+ let pinned = isPinned arr+ req = byteLength arr + SIZE_OF(CChar)+ arr1 <-+ if byteCapacity arr < req || not pinned+ then reallocExplicitAs Pinned 1 req arr+ else return arr+ arr2 :: MutArray CChar <- snocUnsafe (unsafeCast arr1) (0 :: CChar)+ unsafeAsPtr arr2 $ \ptr _ -> act (castPtr ptr)++asCWString :: MutArray a -> (CWString -> IO b) -> IO b+asCWString arr act = do+ let pinned = isPinned arr+ req = byteLength arr + SIZE_OF(CWchar)+ arr1 <-+ if byteCapacity arr < req || not pinned+ then reallocExplicitAs Pinned 1 req arr+ else return arr+ arr2 :: MutArray CWchar <- snocUnsafe (unsafeCast arr1) (0 :: CWchar)+ unsafeAsPtr arr2 $ \ptr _ -> act (castPtr ptr)++-------------------------------------------------------------------------------+-- Equality+-------------------------------------------------------------------------------++-- | Byte compare two arrays. Compare the length of the arrays. If the length+-- is equal, compare the lexicographical ordering of two underlying byte arrays+-- otherwise return the result of length comparison.+--+-- /Unsafe/: Note that the 'Unbox' instance of sum types with constructors of+-- different sizes may leave some memory uninitialized which can make byte+-- comparison unreliable.+--+-- /Pre-release/+{-# INLINE byteCmp #-}+byteCmp :: MonadIO m => MutArray a -> MutArray a -> m Ordering+byteCmp arr1 arr2 = do+ let !marr1 = arrContents arr1+ !marr2 = arrContents arr2+ !len1 = byteLength arr1+ !len2 = byteLength arr2+ !st1 = arrStart arr1+ !st2 = arrStart arr2+ case compare len1 len2 of+ EQ -> do+ r <- liftIO $ unsafeByteCmp marr1 st1 marr2 st2 len1+ return $ compare r 0+ x -> return x++{-# INLINE cmp #-}+{-# DEPRECATED cmp "Please use byteCmp instead." #-}+cmp :: MonadIO m => MutArray a -> MutArray a -> m Ordering+cmp = byteCmp++-- | Byte equality of two arrays.+--+-- >>> byteEq arr1 arr2 = (==) EQ <$> MutArray.byteCmp arr1 arr2+--+-- /Unsafe/: See 'byteCmp'.+{-# INLINE byteEq #-}+byteEq :: MonadIO m => MutArray a -> MutArray a -> m Bool+byteEq arr1 arr2 = fmap (EQ ==) $ byteCmp arr1 arr2++-------------------------------------------------------------------------------+-- Compact+-------------------------------------------------------------------------------++-- Note: LE versions avoid an extra copy compared to GE. LE parser trades+-- backtracking one array in lieu of avoiding a copy. However, LE and GE both+-- can leave some memory unused. They may split the last array to fit it+-- exactly in the space.++{-# INLINE_NORMAL pCompactLeAs #-}+pCompactLeAs ::+ forall m a. (MonadIO m, Unbox a)+ => PinnedState -> Int -> Parser (MutArray a) m (MutArray a)+pCompactLeAs ps maxElems = Parser step initial extract++ where++ maxBytes = maxElems * SIZE_OF(a)++ functionName = "Streamly.Internal.Data.MutArray.pCompactLE"++ initial =+ return+ $ if maxElems <= 0+ then error+ $ functionName+ ++ ": the size of arrays ["+ ++ show maxElems ++ "] must be a natural number"+ else Parser.IPartial Nothing++ step Nothing arr =+ return+ $ let len = byteLength arr+ in if len >= maxBytes+ then Parser.SDone 1 arr+ else Parser.SPartial 1 (Just arr)+ -- XXX Split the last array to use the space more compactly.+ step (Just buf) arr =+ let len = byteLength buf + byteLength arr+ in if len > maxBytes+ then return $ Parser.SDone 0 buf+ else do+ buf1 <-+ if byteCapacity buf < maxBytes+ then liftIO $ reallocExplicitAs+ ps (SIZE_OF(a)) maxBytes buf+ else return buf+ buf2 <- unsafeSplice buf1 arr+ return $ Parser.SPartial 1 (Just buf2)++ extract Nothing = return $ Parser.FDone 0 nil+ extract (Just buf) = return $ Parser.FDone 0 buf++-- | Parser @createCompactMax maxElems@ coalesces adjacent arrays in the+-- input stream only if the combined size would be less than or equal to+-- @maxElems@ elements. Note that it won't split an array if the original array+-- is already larger than maxElems.+--+-- @maxElems@ must be greater than 0.+--+-- Generates unpinned arrays irrespective of the pinning status of input+-- arrays.+--+-- Note that a fold compacting to less than or equal to a given size is not+-- possible, as folds cannot backtrack.+--+-- /Internal/+{-# INLINE createCompactMax #-}+createCompactMax, pCompactLE ::+ forall m a. (MonadIO m, Unbox a)+ => Int -> Parser (MutArray a) m (MutArray a)+createCompactMax = pCompactLeAs Unpinned++RENAME(pCompactLE,createCompactMax)++-- | Pinned version of 'createCompactMax'.+{-# INLINE createCompactMax' #-}+createCompactMax', pPinnedCompactLE ::+ forall m a. (MonadIO m, Unbox a)+ => Int -> Parser (MutArray a) m (MutArray a)+createCompactMax' = pCompactLeAs Pinned++{-# DEPRECATED pPinnedCompactLE "Please use createCompactMax' instead." #-}+{-# INLINE pPinnedCompactLE #-}+pPinnedCompactLE = createCompactMax'++data SpliceState s arr+ = SpliceInitial s+ | SpliceBuffering s arr+ | SpliceYielding arr (SpliceState s arr)+ | SpliceFinish++-- | This mutates the first array (if it has space) to append values from the+-- second one. This would work for immutable arrays as well because an+-- immutable array never has additional space so a new array is allocated+-- instead of mutating it.+{-# INLINE_NORMAL compactLeAs #-}+compactLeAs :: forall m a. (MonadIO m, Unbox a)+ => PinnedState -> Int -> D.Stream m (MutArray a) -> D.Stream m (MutArray a)+compactLeAs ps maxElems (D.Stream step state) =+ D.Stream step' (SpliceInitial state)++ where++ maxBytes = maxElems * SIZE_OF(a)++ functionName = "Streamly.Internal.Data.MutArray.rCompactLE"++ {-# INLINE_LATE step' #-}+ step' gst (SpliceInitial st) = do+ when (maxElems <= 0) $+ -- XXX we can pass the module string from the higher level API+ error $ functionName ++ ": the size of arrays [" ++ show maxElems+ ++ "] must be a natural number"+ r <- step gst st+ case r of+ D.Yield arr s -> return $+ let len = byteLength arr+ in if len >= maxBytes+ then D.Skip (SpliceYielding arr (SpliceInitial s))+ else D.Skip (SpliceBuffering s arr)+ D.Skip s -> return $ D.Skip (SpliceInitial s)+ D.Stop -> return D.Stop++ -- XXX Split the last array to use the space more compactly.+ step' gst (SpliceBuffering st buf) = do+ r <- step gst st+ case r of+ D.Yield arr s -> do+ let len = byteLength buf + byteLength arr+ if len > maxBytes+ then return $+ D.Skip (SpliceYielding buf (SpliceBuffering s arr))+ else do+ buf1 <- if byteCapacity buf < maxBytes+ then liftIO $ reallocExplicitAs+ ps (SIZE_OF(a)) maxBytes buf+ else return buf+ buf2 <- unsafeSplice buf1 arr+ return $ D.Skip (SpliceBuffering s buf2)+ D.Skip s -> return $ D.Skip (SpliceBuffering s buf)+ D.Stop -> return $ D.Skip (SpliceYielding buf SpliceFinish)++ step' _ SpliceFinish = return D.Stop++ step' _ (SpliceYielding arr next) = return $ D.Yield arr next+++{-# INLINE_NORMAL fCompactGeAs #-}+fCompactGeAs ::+ forall m a. (MonadIO m, Unbox a)+ => PinnedState -> Int -> FL.Fold m (MutArray a) (MutArray a)+fCompactGeAs ps minElems = Fold step initial extract extract++ where++ minBytes = minElems * SIZE_OF(a)++ functionName = "Streamly.Internal.Data.MutArray.fCompactGE"++ initial =+ return+ $ if minElems < 0+ then error+ $ functionName+ ++ ": the size of arrays ["+ ++ show minElems ++ "] must be a natural number"+ else FL.Partial Nothing++ step Nothing arr =+ return+ $ let len = byteLength arr+ in if len >= minBytes+ then FL.Done arr+ else FL.Partial (Just arr)+ -- XXX Buffer arrays as a list to avoid copy and reallocations+ step (Just buf) arr = do+ let len = byteLength buf + byteLength arr+ buf1 <-+ if byteCapacity buf < len+ then liftIO $ reallocExplicitAs+ ps (SIZE_OF(a)) (max minBytes len) buf+ else return buf+ buf2 <- unsafeSplice buf1 arr+ if len >= minBytes+ then return $ FL.Done buf2+ else return $ FL.Partial (Just buf2)++ extract Nothing = return nil+ extract (Just buf) = return buf++-- | Fold @createCompactMin minElems@ coalesces adjacent arrays in the+-- input stream until the size becomes greater than or equal to @minElems@.+--+-- Generates unpinned arrays irrespective of the pinning status of input+-- arrays.+{-# INLINE createCompactMin #-}+createCompactMin, fCompactGE ::+ forall m a. (MonadIO m, Unbox a)+ => Int -> FL.Fold m (MutArray a) (MutArray a)+createCompactMin = fCompactGeAs Unpinned++RENAME(fCompactGE,createCompactMin)++-- | Pinned version of 'createCompactMin'.+{-# INLINE createCompactMin' #-}+createCompactMin', fPinnedCompactGE ::+ forall m a. (MonadIO m, Unbox a)+ => Int -> FL.Fold m (MutArray a) (MutArray a)+createCompactMin' = fCompactGeAs Pinned++{-# DEPRECATED fPinnedCompactGE "Please use createCompactMin' instead." #-}+{-# INLINE fPinnedCompactGE #-}+fPinnedCompactGE = createCompactMin'++{-# INLINE_NORMAL lCompactGeAs #-}+lCompactGeAs :: forall m a. (MonadIO m, Unbox a)+ => PinnedState -> Int -> Fold m (MutArray a) () -> Fold m (MutArray a) ()+-- The fold version turns out to be a little bit slower.+-- lCompactGeAs ps n = FL.many (fCompactGeAs ps n)+lCompactGeAs ps minElems (Fold step1 initial1 _ final1) =+ Fold step initial extract final++ where++ minBytes = minElems * SIZE_OF(a)++ functionName = "Streamly.Internal.Data.MutArray.lCompactGE"++ initial = do+ when (minElems <= 0) $+ -- XXX we can pass the module string from the higher level API+ error $ functionName ++ ": the size of arrays ["+ ++ show minElems ++ "] must be a natural number"++ r <- initial1+ return $ first (Tuple' Nothing) r++ {-# INLINE runInner #-}+ runInner len acc buf =+ if len >= minBytes+ then do+ r <- step1 acc buf+ case r of+ FL.Done _ -> return $ FL.Done ()+ FL.Partial s -> do+ _ <- final1 s+ res <- initial1+ return $ first (Tuple' Nothing) res+ else return $ FL.Partial $ Tuple' (Just buf) acc++ step (Tuple' Nothing r1) arr =+ runInner (byteLength arr) r1 arr++ -- XXX Buffer arrays as a list to avoid copy and reallocations+ step (Tuple' (Just buf) r1) arr = do+ let len = byteLength buf + byteLength arr+ buf1 <- if byteCapacity buf < len+ then liftIO $ reallocExplicitAs+ ps (SIZE_OF(a)) (max minBytes len) buf+ else return buf+ buf2 <- unsafeSplice buf1 arr+ runInner len r1 buf2++ -- XXX Several folds do extract >=> final, therefore, we need to make final+ -- return "m b" rather than using extract post it if we want extract to be+ -- partial.+ --+ -- extract forces the pending buffer to be sent to the fold which is not+ -- what we want.+ extract _ = error "lCompactGE: not designed for scanning"++ final (Tuple' Nothing r1) = final1 r1+ final (Tuple' (Just buf) r1) = do+ r <- step1 r1 buf+ case r of+ FL.Partial rr -> final1 rr+ FL.Done _ -> return ()++-- | Like 'compactGE' but for transforming folds instead of stream.+--+-- >> lCompactGE n = Fold.many (MutArray.fCompactGE n)+--+-- Generates unpinned arrays irrespective of the pinning status of input+-- arrays.+{-# DEPRECATED lCompactGE "Please use scanCompactMin instead." #-}+{-# INLINE lCompactGE #-}+lCompactGE :: forall m a. (MonadIO m, Unbox a)+ => Int -> Fold m (MutArray a) () -> Fold m (MutArray a) ()+lCompactGE = lCompactGeAs Unpinned++-- | Pinned version of 'lCompactGE'.+{-# DEPRECATED lPinnedCompactGE "Please use scanCompactMin' instead." #-}+{-# INLINE lPinnedCompactGE #-}+lPinnedCompactGE :: forall m a. (MonadIO m, Unbox a)+ => Int -> Fold m (MutArray a) () -> Fold m (MutArray a) ()+lPinnedCompactGE = lCompactGeAs Pinned++data CompactMinState arr =+ CompactMinInit | CompactMinIncomplete arr | CompactMinComplete arr++{-# INLINE_NORMAL scanCompactMinAs #-}+scanCompactMinAs :: forall m a. (MonadIO m, Unbox a)+ => PinnedState -> Int -> Scanl m (MutArray a) (Maybe (MutArray a))+scanCompactMinAs ps minElems =+ Scanl step initial extract final++ where++ minBytes = minElems * SIZE_OF(a)++ functionName = "Streamly.Internal.Data.MutArray.scanCompactMin"++ initial = do+ when (minElems <= 0) $+ -- XXX we can pass the module string from the higher level API+ error $ functionName ++ ": the size of arrays ["+ ++ show minElems ++ "] must be a natural number"++ return $ FL.Partial CompactMinInit++ {-# INLINE runInner #-}+ runInner len buf =+ if len >= minBytes+ then do+ return $ FL.Partial $ CompactMinComplete buf+ else return $ FL.Partial $ CompactMinIncomplete buf++ step CompactMinInit arr =+ runInner (byteLength arr) arr++ step (CompactMinComplete _) arr =+ runInner (byteLength arr) arr++ -- XXX Buffer arrays as a list to avoid copy and reallocations+ step (CompactMinIncomplete buf) arr = do+ let len = byteLength buf + byteLength arr+ buf1 <- if byteCapacity buf < len+ then liftIO $ reallocExplicitAs+ ps (SIZE_OF(a)) (max minBytes len) buf+ else return buf+ buf2 <- unsafeSplice buf1 arr+ runInner len buf2++ extract CompactMinInit = return Nothing+ extract (CompactMinComplete arr) = return (Just arr)+ extract (CompactMinIncomplete _) = return Nothing++ final CompactMinInit = return Nothing+ final (CompactMinComplete arr) = return (Just arr)+ final (CompactMinIncomplete arr) = return (Just arr)++-- | Like 'compactMin' but a scan.+{-# INLINE scanCompactMin #-}+scanCompactMin :: forall m a. (MonadIO m, Unbox a)+ => Int -> Scanl m (MutArray a) (Maybe (MutArray a))+scanCompactMin = scanCompactMinAs Unpinned++-- | Like 'compactMin'' but a scan.+{-# INLINE scanCompactMin' #-}+scanCompactMin' :: forall m a. (MonadIO m, Unbox a)+ => Int -> Scanl m (MutArray a) (Maybe (MutArray a))+scanCompactMin' = scanCompactMinAs Pinned++-- | @compactMin n stream@ coalesces adjacent arrays in the @stream@ until+-- the compacted array size becomes greater than or equal to @n@.+--+-- >>> compactMin n = Stream.foldMany (MutArray.createCompactMin n)+--+{-# INLINE compactMin #-}+compactMin, compactGE ::+ (MonadIO m, Unbox a)+ => Int -> Stream m (MutArray a) -> Stream m (MutArray a)+compactMin n = D.foldMany (createCompactMin n)++RENAME(compactGE,compactMin)++-- | 'compactExact n' coalesces adajacent arrays in the input stream to+-- arrays of exact size @n@.+--+-- /Unimplemented/+{-# INLINE compactExact #-}+compactExact :: -- (MonadIO m, Unbox a) =>+ Int -> Stream m (MutArray a) -> Stream m (MutArray a)+compactExact _n = undefined -- D.parseManyD (pCompactEQ n)++-------------------------------------------------------------------------------+-- In-place mutation algorithms+-------------------------------------------------------------------------------++-- XXX Can use SIMD+-- XXX findIndex can be implemented using this if fold perf is not good enough.++{-# INLINE advanceStartTill #-}+advanceStartTill :: forall a. (Unbox a) => (a -> Bool) -> MutArray a -> IO Int+advanceStartTill eq MutArray{..} = go arrStart++ where++ {-+ -- XXX This should have the same perf but it does not, investigate.+ getStart = do+ r <- liftIO $ D.head $ D.findIndices (not . eq) $ toStreamD arr+ pure $+ case r of+ Nothing -> arrEnd+ Just i -> PTR_INDEX(arrStart,i,a)+ -}++ go cur =+ if cur < arrEnd+ then do+ r <- peekAt cur arrContents+ if eq r+ then go (INDEX_NEXT(cur,a))+ else return cur+ else return cur++{-# INLINE retractEndTill #-}+retractEndTill :: forall a. (Unbox a) => (a -> Bool) -> MutArray a -> IO Int+retractEndTill eq MutArray{..} = go arrEnd++ where++ go cur = do+ if cur > arrStart+ then do+ let prev = INDEX_PREV(cur,a)+ r <- peekAt prev arrContents+ if eq r+ then go prev+ else return cur+ else return cur++-- | Strip elements which match the predicate, from the start of the array.+--+-- >>> arr <- MutArray.fromList " hello world"+-- >>> a <- MutArray.dropWhile (== ' ') arr+-- >>> MutArray.toList a+-- "hello world"+--+-- /Pre-release/+{-# INLINE dropWhile #-}+dropWhile :: forall a m. (Unbox a, MonadIO m) =>+ (a -> Bool) -> MutArray a -> m (MutArray a)+dropWhile eq arr@MutArray{..} = liftIO $ do+ st <- advanceStartTill eq arr+ -- return arr{arrStart = st}+ return $+ if st >= arrEnd+ then empty+ else arr{arrStart = st}++-- | Strip elements which match the predicate, from the end of the array.+--+-- >>> arr <- MutArray.fromList "hello world "+-- >>> a <- MutArray.revDropWhile (== ' ') arr+-- >>> MutArray.toList a+-- "hello world"+--+-- /Pre-release/+{-# INLINE revDropWhile #-}+revDropWhile :: forall a m. (Unbox a, MonadIO m) =>+ (a -> Bool) -> MutArray a -> m (MutArray a)+revDropWhile eq arr@MutArray{..} = liftIO $ do+ end <- retractEndTill eq arr+ -- return arr {arrEnd = end}+ return $+ if end <= arrStart+ then empty+ else arr{arrEnd = end}++-- | Strip elements which match the predicate, from both ends.+--+-- >>> arr <- MutArray.fromList " hello world "+-- >>> a <- MutArray.dropAround (== ' ') arr+-- >>> MutArray.toList a+-- "hello world"+--+-- /Pre-release/+{-# INLINE dropAround #-}+dropAround, strip :: forall a m. (Unbox a, MonadIO m) =>+ (a -> Bool) -> MutArray a -> m (MutArray a)+dropAround eq arr = liftIO $ dropWhile eq arr >>= revDropWhile eq+RENAME(strip,dropAround)++-- | Given an array sorted in ascending order except the last element being out+-- of order, use bubble sort to place the last element at the right place such+-- that the array remains sorted in ascending order.+--+-- /Pre-release/+{-# INLINE bubble #-}+bubble :: (MonadIO m, Unbox a) => (a -> a -> Ordering) -> MutArray a -> m ()+bubble cmp0 arr =+ when (l > 1) $ do+ x <- unsafeGetIndex (l - 1) arr+ go x (l - 2)++ where++ l = length arr++ go x i =+ if i >= 0+ then do+ x1 <- unsafeGetIndex i arr+ case x `cmp0` x1 of+ LT -> do+ unsafePutIndex (i + 1) arr x1+ go x (i - 1)+ _ -> unsafePutIndex (i + 1) arr x+ else unsafePutIndex (i + 1) arr x++--------------------------------------------------------------------------------+-- Renaming+--------------------------------------------------------------------------------++RENAME(realloc,reallocBytes)+RENAME(castUnsafe,unsafeCast)+RENAME(newArrayWith,emptyWithAligned)+RENAME(getSliceUnsafe,unsafeSliceOffLen)+RENAME(getSlice,sliceOffLen)+RENAME(putIndexUnsafe,unsafePutIndex)+RENAME(modifyIndexUnsafe,unsafeModifyIndex)+RENAME(getIndexUnsafe,unsafeGetIndex)+RENAME(snocUnsafe,unsafeSnoc)+RENAME(spliceUnsafe,unsafeSplice)+RENAME(pokeSkipUnsafe,unsafePokeSkip)+RENAME(peekSkipUnsafe,unsafePeekSkip)+RENAME(peekUncons,peek)+RENAME(peekUnconsUnsafe,unsafePeek)+RENAME(pokeAppend,poke)+RENAME(pokeAppendMay,pokeMay)++-- This renaming can be done directly without deprecations. But I'm keeping this+-- intentionally. Packdiff should be able to point out such APIs that we can+-- just remove.+RENAME(createOfWith,createWithOf)
+ src/Streamly/Internal/Data/MutByteArray.hs view
@@ -0,0 +1,241 @@+-- This is required as all the instances in this module are orphan instances.+{-# OPTIONS_GHC -fno-warn-orphans #-}++-- |+-- Module : Streamly.Internal.Data.MutByteArray+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3-3-Clause+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--++module Streamly.Internal.Data.MutByteArray+ (+ -- * MutByteArray+ module Streamly.Internal.Data.MutByteArray.Type+ -- * Unbox+ , module Streamly.Internal.Data.Unbox+ , module Streamly.Internal.Data.Unbox.TH+ -- * Serialize+ , module Streamly.Internal.Data.Serialize.Type+ -- * Serialize TH+ , module Streamly.Internal.Data.Serialize.TH+ ) where++--------------------------------------------------------------------------------+-- Imports+--------------------------------------------------------------------------------++import Data.Proxy (Proxy(..))+import Streamly.Internal.Data.Array (Array(..))+import GHC.Exts (Int(..), sizeofByteArray#, unsafeCoerce#)+import GHC.Word (Word8)++#if __GLASGOW_HASKELL__ >= 900+import GHC.Num.Integer (Integer(..))+#else+import GHC.Integer.GMP.Internals (Integer(..), BigNat(..))+#endif++import Streamly.Internal.Data.MutByteArray.Type+import Streamly.Internal.Data.Serialize.TH+import Streamly.Internal.Data.Serialize.Type+import Streamly.Internal.Data.Unbox+import Streamly.Internal.Data.Unbox.TH++--------------------------------------------------------------------------------+-- Common instances+--------------------------------------------------------------------------------++-- Note+-- ====+--+-- Even a non-functional change such as changing the order of constructors will+-- change the instance derivation.+--+-- This will not pose a problem if both, encode, and decode are done by the same+-- version of the application. There *might* be a problem if version that+-- encodes differs from the version that decodes.+--+-- We need to add some compatibility tests using different versions of+-- dependencies.+--+-- Although such chages for the most basic types won't happen we need to detect+-- if it ever happens.+--+-- Should we worry about these kind of changes and this kind of compatibility?+-- This is a problem for all types of derivations that depend on the order of+-- constructors, for example, Enum.++-- Note on Windows build+-- =====================+--+-- On Windows, having template haskell splices here fail the build with the+-- following error:+--+-- @+-- addLibrarySearchPath: C:\... (Win32 error 3): The system cannot find the path specified.+-- @+--+-- The error might be irrelavant but having these splices triggers it. We should+-- either fix the problem or avoid the use to template haskell splices in this+-- file.+--+-- Similar issue: https://github.com/haskell/cabal/issues/4741++-- $(Serialize.deriveSerialize ''Maybe)+instance Serialize a => Serialize (Maybe a) where++ {-# INLINE addSizeTo #-}+ addSizeTo acc x =+ case x of+ Nothing -> acc + 1+ Just field0 -> addSizeTo (acc + 1) field0++ {-# INLINE deserializeAt #-}+ deserializeAt initialOffset arr endOffset = do+ (i0, tag) <- deserializeAt initialOffset arr endOffset+ case tag :: Word8 of+ 0 -> pure (i0, Nothing)+ 1 -> do (i1, a0) <- deserializeAt i0 arr endOffset+ pure (i1, Just a0)+ _ -> error "Found invalid tag while peeking (Maybe a)"++ {-# INLINE serializeAt #-}+ serializeAt initialOffset arr val =+ case val of+ Nothing -> serializeAt initialOffset arr (0 :: Word8)+ Just field0 -> do+ i0 <- serializeAt initialOffset arr (1 :: Word8)+ serializeAt i0 arr field0++-- $(Serialize.deriveSerialize ''Either)+instance (Serialize a, Serialize b) => Serialize (Either a b) where++ {-# INLINE addSizeTo #-}+ addSizeTo acc x =+ case x of+ Left field0 -> addSizeTo (acc + 1) field0+ Right field0 -> addSizeTo (acc + 1) field0++ {-# INLINE deserializeAt #-}+ deserializeAt initialOffset arr endOffset = do+ (i0, tag) <- deserializeAt initialOffset arr endOffset+ case tag :: Word8 of+ 0 -> do (i1, a0) <- deserializeAt i0 arr endOffset+ pure (i1, Left a0)+ 1 -> do (i1, a0) <- deserializeAt i0 arr endOffset+ pure (i1, Right a0)+ _ -> error "Found invalid tag while peeking (Either a b)"++ {-# INLINE serializeAt #-}+ serializeAt initialOffset arr val =+ case val of+ Left field0 -> do+ i0 <- serializeAt initialOffset arr (0 :: Word8)+ serializeAt i0 arr field0+ Right field0 -> do+ i0 <- serializeAt initialOffset arr (1 :: Word8)+ serializeAt i0 arr field0++instance Serialize (Proxy a) where++ {-# INLINE addSizeTo #-}+ addSizeTo acc _ = acc + 1++ {-# INLINE deserializeAt #-}+ deserializeAt initialOffset _ _ = pure (initialOffset + 1, Proxy)++ {-# INLINE serializeAt #-}+ serializeAt initialOffset _ _ = pure (initialOffset + 1)++--------------------------------------------------------------------------------+-- Integer+--------------------------------------------------------------------------------++data LiftedInteger+ = LIS Int+ | LIP (Array Word)+ | LIN (Array Word)++-- $(Serialize.deriveSerialize ''LiftedInteger)+instance Serialize LiftedInteger where++ {-# INLINE addSizeTo #-}+ addSizeTo acc x =+ case x of+ LIS field0 -> addSizeTo (acc + 1) field0+ LIP field0 -> addSizeTo (acc + 1) field0+ LIN field0 -> addSizeTo (acc + 1) field0++ {-# INLINE deserializeAt #-}+ deserializeAt initialOffset arr endOffset = do+ (i0, tag) <- deserializeAt initialOffset arr endOffset+ case tag :: Word8 of+ 0 -> do (i1, a0) <- deserializeAt i0 arr endOffset+ pure (i1, LIS a0)+ 1 -> do (i1, a0) <- deserializeAt i0 arr endOffset+ pure (i1, LIP a0)+ 2 -> do (i1, a0) <- deserializeAt i0 arr endOffset+ pure (i1, LIN a0)+ _ -> error "Found invalid tag while peeking (LiftedInteger)"++ {-# INLINE serializeAt #-}+ serializeAt initialOffset arr val =+ case val of+ LIS field0 -> do+ i0 <- serializeAt initialOffset arr (0 :: Word8)+ serializeAt i0 arr field0+ LIP field0 -> do+ i0 <- serializeAt initialOffset arr (1 :: Word8)+ serializeAt i0 arr field0+ LIN field0 -> do+ i0 <- serializeAt initialOffset arr (2 :: Word8)+ serializeAt i0 arr field0++#if __GLASGOW_HASKELL__ >= 900++{-# INLINE liftInteger #-}+liftInteger :: Integer -> LiftedInteger+liftInteger (IS x) = LIS (I# x)+liftInteger (IP x) =+ LIP (Array (MutByteArray (unsafeCoerce# x)) 0 (I# (sizeofByteArray# x)))+liftInteger (IN x) =+ LIN (Array (MutByteArray (unsafeCoerce# x)) 0 (I# (sizeofByteArray# x)))++{-# INLINE unliftInteger #-}+unliftInteger :: LiftedInteger -> Integer+unliftInteger (LIS (I# x)) = IS x+unliftInteger (LIP (Array (MutByteArray x) _ _)) = IP (unsafeCoerce# x)+unliftInteger (LIN (Array (MutByteArray x) _ _)) = IN (unsafeCoerce# x)++#else++{-# INLINE liftInteger #-}+liftInteger :: Integer -> LiftedInteger+liftInteger (S# x) = LIS (I# x)+liftInteger (Jp# (BN# x)) =+ LIP (Array (MutByteArray (unsafeCoerce# x)) 0 (I# (sizeofByteArray# x)))+liftInteger (Jn# (BN# x)) =+ LIN (Array (MutByteArray (unsafeCoerce# x)) 0 (I# (sizeofByteArray# x)))++{-# INLINE unliftInteger #-}+unliftInteger :: LiftedInteger -> Integer+unliftInteger (LIS (I# x)) = S# x+unliftInteger (LIP (Array (MutByteArray x) _ _)) =+ Jp# (BN# (unsafeCoerce# x))+unliftInteger (LIN (Array (MutByteArray x) _ _)) =+ Jn# (BN# (unsafeCoerce# x))++#endif++instance Serialize Integer where+ {-# INLINE addSizeTo #-}+ addSizeTo i a = addSizeTo i (liftInteger a)++ {-# INLINE deserializeAt #-}+ deserializeAt off arr end =+ fmap unliftInteger <$> deserializeAt off arr end++ {-# INLINE serializeAt #-}+ serializeAt off arr val = serializeAt off arr (liftInteger val)
+ src/Streamly/Internal/Data/MutByteArray/Type.hs view
@@ -0,0 +1,447 @@+{-# LANGUAGE UnboxedTuples #-}+{-# LANGUAGE UnliftedFFITypes #-}++-- |+-- Module : Streamly.Internal.Data.MutByteArray.Type+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+module Streamly.Internal.Data.MutByteArray.Type+ (+ -- ** MutByteArray+ MutByteArray(..)+ , getMutByteArray#++ -- ** Helpers+ , touch++ -- ** Pinning+ , PinnedState(..)+ , isPinned+ , pin+ , unpin++ -- ** Allocation+ , empty+ , newAs+ , new+ , new'+ , reallocSliceAs++ -- ** Access+ , length+ , unsafeAsPtr++ -- ** Modify+ , unsafePutSlice+ , unsafePutPtrN++ -- ** Copy+ , unsafeCloneSliceAs+ , unsafeCloneSlice+ , unsafePinnedCloneSlice -- XXX unsafeCloneSlice'++ -- ** Compare+ , unsafeByteCmp++ -- ** Capacity Management+ , blockSize+ , largeObjectThreshold++ -- ** Deprecated+ , MutableByteArray+ , getMutableByteArray#+ , newBytesAs+ , sizeOfMutableByteArray+ , putSliceUnsafe+ , cloneSliceUnsafeAs+ , cloneSliceUnsafe+ , pinnedCloneSliceUnsafe+ , pinnedNewAlignedBytes+ , asPtrUnsafe+ , unsafePinnedAsPtr+ , nil+ , pinnedNew+ ) where++#include "deprecation.h"++import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad (when)+import Data.Word (Word8)+#ifdef DEBUG+import Debug.Trace (trace)+#endif+import Foreign.C.Types (CSize(..))+import GHC.Base (IO(..))+import System.IO.Unsafe (unsafePerformIO)++import GHC.Exts+import Prelude hiding (length)++--------------------------------------------------------------------------------+-- The ArrayContents type+--------------------------------------------------------------------------------++data PinnedState+ = Pinned+ | Unpinned deriving (Show, Eq)++-- XXX can use UnliftedNewtypes++-- | A lifted mutable byte array type wrapping @MutableByteArray# RealWorld@.+-- This is a low level array used to back high level unboxed arrays and+-- serialized data.+data MutByteArray = MutByteArray (MutableByteArray# RealWorld)++{-# DEPRECATED MutableByteArray "Please use MutByteArray instead" #-}+type MutableByteArray = MutByteArray++{-# INLINE getMutByteArray# #-}+getMutableByteArray#, getMutByteArray# :: MutByteArray -> MutableByteArray# RealWorld+getMutByteArray# (MutByteArray mbarr) = mbarr++-- | Return the size of the array in bytes.+{-# INLINE length #-}+sizeOfMutableByteArray, length :: MutByteArray -> IO Int+length (MutByteArray arr) =+ IO $ \s ->+ case getSizeofMutableByteArray# arr s of+ (# s1, i #) -> (# s1, I# i #)++{-# INLINE touch #-}+touch :: MutByteArray -> IO ()+touch (MutByteArray contents) =+ IO $ \s -> case touch# contents s of s' -> (# s', () #)++-- XXX Some functions in this module are "IO" and others are "m", we need to+-- make it consistent.++-- | NOTE: this is deprecated because it can lead to accidental problems if the+-- user tries to use it to mutate the array because it does not return the new+-- array after pinning.+{-# DEPRECATED unsafePinnedAsPtr "Pin the array and then use unsafeAsPtr." #-}+{-# INLINE unsafePinnedAsPtr #-}+unsafePinnedAsPtr :: MonadIO m => MutByteArray -> (Ptr a -> m b) -> m b+unsafePinnedAsPtr arr0 f = do+ arr <- liftIO $ pin arr0+ let !ptr = Ptr (byteArrayContents#+ (unsafeCoerce# (getMutByteArray# arr)))+ r <- f ptr+ liftIO $ touch arr+ return r++{-# DEPRECATED asPtrUnsafe "Pin the array and then use unsafeAsPtr." #-}+{-# INLINE asPtrUnsafe #-}+asPtrUnsafe :: MonadIO m => MutByteArray -> (Ptr a -> m b) -> m b+asPtrUnsafe = unsafePinnedAsPtr++-- | Use a @MutByteArray@ as @Ptr a@. This is useful when we want to pass+-- an array as a pointer to some operating system call or to a "safe" FFI call.+--+-- /Unsafe/ WARNING:+--+-- 1. Will lead to memory corruption if the array is not pinned. Use+-- only if the array is known to be pinned already or pin it explicitly.+--+-- 2. Ensure that the pointer is accessed within the legal bounds of the array.+-- The size of the MutByteArray must be taken into account.+--+-- /Pre-release/+--+{-# INLINE unsafeAsPtr #-}+unsafeAsPtr :: MonadIO m => MutByteArray -> (Ptr a -> IO b) -> m b+unsafeAsPtr arr f = liftIO $ do+ when (not (isPinned arr))+ $ error "unsafeAsPtr requires the array to be pinned"++ let !ptr = Ptr (byteArrayContents#+ (unsafeCoerce# (getMutByteArray# arr)))+ r <- f ptr+ -- While f is using the bare pointer, the MutByteArray may be garbage+ -- collected by the GC, tell the GC that we are still using it.+ touch arr+ return r++--------------------------------------------------------------------------------+-- Creation+--------------------------------------------------------------------------------++{-# NOINLINE empty #-}+empty :: MutByteArray+empty = unsafePerformIO $ new 0++{-# DEPRECATED nil "Please use empty instead" #-}+nil :: MutByteArray+nil = empty++-- XXX Should we use bitshifts in calculations or it gets optimized by the+-- compiler/processor itself?+--+-- | The page or block size used by the GHC allocator. Allocator allocates at+-- least a block and then allocates smaller allocations from within a block.+blockSize :: Int+blockSize = 4 * 1024++-- | Allocations larger than 'largeObjectThreshold' are in multiples of block+-- size and are always pinned. The space beyond the end of a large object up to+-- the end of the block is unused.+largeObjectThreshold :: Int+largeObjectThreshold = (blockSize * 8) `div` 10++{-# INLINE pinnedNewRaw #-}+pinnedNewRaw :: Int -> IO MutByteArray+pinnedNewRaw (I# nbytes) = IO $ \s ->+ case newPinnedByteArray# nbytes s of+ (# s', mbarr# #) ->+ let c = MutByteArray mbarr#+ in (# s', c #)++{-# INLINE new' #-}+new', pinnedNew :: Int -> IO MutByteArray+new' nbytes | nbytes < 0 =+ errorWithoutStackTrace "new': size must be >= 0"+new' nbytes = pinnedNewRaw nbytes+RENAME_PRIME(pinnedNew,new)++-- XXX add "newRoundedUp" to round up the large size to the next page boundary+-- and return the allocated size.+-- Uses the pinned version of allocated if the size required is >+-- largeObjectThreshold+{-# INLINE new #-}+new :: Int -> IO MutByteArray+new nbytes | nbytes > largeObjectThreshold = pinnedNewRaw nbytes+new nbytes | nbytes < 0 =+ errorWithoutStackTrace "newByteArray: size must be >= 0"+new (I# nbytes) = IO $ \s ->+ case newByteArray# nbytes s of+ (# s', mbarr# #) ->+ let c = MutByteArray mbarr#+ in (# s', c #)++{-# DEPRECATED pinnedNewAlignedBytes "Please use pinnedNew instead" #-}+{-# INLINE pinnedNewAlignedBytes #-}+pinnedNewAlignedBytes :: Int -> Int -> IO MutByteArray+pinnedNewAlignedBytes nbytes _align | nbytes < 0 =+ errorWithoutStackTrace "pinnedNewAlignedBytes: size must be >= 0"+pinnedNewAlignedBytes (I# nbytes) (I# align) = IO $ \s ->+ case newAlignedPinnedByteArray# nbytes align s of+ (# s', mbarr# #) ->+ let c = MutByteArray mbarr#+ in (# s', c #)++{-# INLINE newAs #-}+newBytesAs, newAs :: PinnedState -> Int -> IO MutByteArray+newAs Unpinned = new+newAs Pinned = pinnedNew++-- | @reallocSliceAs pinType newLen array offset len@ reallocates a slice+-- from @array@ starting at @offset@ and having length @len@ to a new array of+-- length @newLen@ copying the old data to the new. Note that if the @newLen@+-- is smaller than @len@ it will truncate the old data.+{-# INLINE reallocSliceAs #-}+reallocSliceAs ::+ PinnedState -> Int -> MutByteArray -> Int -> Int -> IO MutByteArray+reallocSliceAs ps newLen (MutByteArray src#) srcStart srcLen = do+ MutByteArray dst# <- newBytesAs ps newLen++ -- Copy old data+ let !(I# srcStart#) = srcStart+ !(I# newLen#) = min srcLen newLen+ IO $ \s# -> (# copyMutableByteArray# src# srcStart#+ dst# 0# newLen# s#, MutByteArray dst# #)++-------------------------------------------------------------------------------+-- Copying+-------------------------------------------------------------------------------++-- Note: Array copy is more efficient than streaming copy.+-- CopyMutableByteArray# translates to genMemcpy in GHC/CmmToAsm/X86/CodeGen.hs+-- glibc memcpy copies bytes/words/pages - unrolls the loops:+-- https://github.com/bminor/glibc/blob/4290aed05135ae4c0272006442d147f2155e70d7/string/memcpy.c+-- https://github.com/bminor/glibc/blob/4290aed05135ae4c0272006442d147f2155e70d7/string/wordcopy.c++-- | @unsafePutSlice src srcOffset dst dstOffset len@ copies @len@ bytes from+-- @src@ at @srcOffset@ to dst at @dstOffset@.+--+-- This is unsafe as it does not check the bounds of @src@ or @dst@.+--+{-# INLINE unsafePutSlice #-}+putSliceUnsafe, unsafePutSlice ::+ MonadIO m+ => MutByteArray+ -> Int+ -> MutByteArray+ -> Int+ -> Int+ -> m ()+unsafePutSlice src srcStartBytes dst dstStartBytes lenBytes = liftIO $ do+#ifdef DEBUG+ srcLen <- length src+ dstLen <- length dst+ when (srcLen - srcStartBytes < lenBytes)+ $ error $ "unsafePutSlice: src overflow: start" ++ show srcStartBytes+ ++ " end " ++ show srcLen ++ " len " ++ show lenBytes+ when (dstLen - dstStartBytes < lenBytes)+ $ error $ "unsafePutSlice: dst overflow: start" ++ show dstStartBytes+ ++ " end " ++ show dstLen ++ " len " ++ show lenBytes+#endif+ let !(I# srcStartBytes#) = srcStartBytes+ !(I# dstStartBytes#) = dstStartBytes+ !(I# lenBytes#) = lenBytes+ let arrS# = getMutByteArray# src+ arrD# = getMutByteArray# dst+ IO $ \s# -> (# copyMutableByteArray#+ arrS# srcStartBytes# arrD# dstStartBytes# lenBytes# s#+ , () #)++foreign import ccall unsafe "string.h memcpy" c_memcpy_pinned+ :: Addr# -> Addr# -> CSize -> IO (Ptr Word8)++-- | @unsafePutPtrN srcPtr dst dstOffset len@ copies @len@ bytes from @srcPtr@+-- to dst at @dstOffset@.+--+-- /Unsafe/:+--+-- The caller has to ensure that:+--+-- * the MutByteArray @dst@ is valid up to @dstOffset + len@.+-- * the @srcPtr@ is alive and pinned during the call.+-- * the @srcPtr@ is valid up to length @len@.+--+{-# INLINE unsafePutPtrN #-}+unsafePutPtrN ::+ MonadIO m+ => Ptr Word8+ -> MutByteArray+ -> Int+ -> Int+ -> m ()+unsafePutPtrN (Ptr srcAddr) dst dstOffset len = liftIO $ do+#ifdef DEBUG+ dstLen <- length dst+ when (dstLen - dstOffset < len)+ $ error $ "unsafePutPtrN: dst overflow: start" ++ show dstOffset+ ++ " end " ++ show dstLen ++ " len " ++ show len+#endif+ let !dstAddr# = byteArrayContents# (unsafeCoerce# (getMutByteArray# dst))+ !(I# dstOff#) = dstOffset+ !dstAddr1# = plusAddr# dstAddr# dstOff#+ _ <- c_memcpy_pinned dstAddr1# srcAddr (fromIntegral len)+ pure ()++-- | Unsafe as it does not check whether the start offset and length supplied+-- are valid inside the array.+{-# INLINE unsafeCloneSliceAs #-}+cloneSliceUnsafeAs, unsafeCloneSliceAs :: MonadIO m =>+ PinnedState -> Int -> Int -> MutByteArray -> m MutByteArray+unsafeCloneSliceAs ps srcOff srcLen src =+ liftIO $ do+ mba <- newAs ps srcLen+ unsafePutSlice src srcOff mba 0 srcLen+ return mba++-- | @unsafeCloneSlice offset len arr@ clones a slice of the supplied array+-- starting at the given offset and equal to the given length.+{-# INLINE unsafeCloneSlice #-}+cloneSliceUnsafe, unsafeCloneSlice :: MonadIO m => Int -> Int -> MutByteArray -> m MutByteArray+unsafeCloneSlice = unsafeCloneSliceAs Unpinned++-- | @unsafePinnedCloneSlice offset len arr@+{-# INLINE unsafePinnedCloneSlice #-}+pinnedCloneSliceUnsafe, unsafePinnedCloneSlice :: MonadIO m =>+ Int -> Int -> MutByteArray -> m MutByteArray+unsafePinnedCloneSlice = unsafeCloneSliceAs Pinned++unsafeByteCmp+ :: MutByteArray -> Int -> MutByteArray -> Int -> Int -> IO Int+unsafeByteCmp+ (MutByteArray marr1) (I# st1#) (MutByteArray marr2) (I# st2#) (I# len#) =+ IO $ \s# ->+ let res =+ I#+ (compareByteArrays#+ (unsafeCoerce# marr1)+ st1#+ (unsafeCoerce# marr2)+ st2#+ len#)+ in (# s#, res #)++-------------------------------------------------------------------------------+-- Pinning & Unpinning+-------------------------------------------------------------------------------++-- | Return 'True' if the array is allocated in pinned memory.+{-# INLINE isPinned #-}+isPinned :: MutByteArray -> Bool+isPinned (MutByteArray arr#) =+ let pinnedInt = I# (isMutableByteArrayPinned# arr#)+ in pinnedInt /= 0+++{-# INLINE cloneMutableArrayWith# #-}+cloneMutableArrayWith#+ :: (Int# -> State# RealWorld -> (# State# RealWorld+ , MutableByteArray# RealWorld #))+ -> MutableByteArray# RealWorld+ -> State# RealWorld+ -> (# State# RealWorld, MutableByteArray# RealWorld #)+cloneMutableArrayWith# alloc# arr# s# =+ case getSizeofMutableByteArray# arr# s# of+ (# s1#, i# #) ->+ case alloc# i# s1# of+ (# s2#, arr1# #) ->+ case copyMutableByteArray# arr# 0# arr1# 0# i# s2# of+ s3# -> (# s3#, arr1# #)++-- | Return a copy of the array in pinned memory if unpinned, else return the+-- original array.+{-# INLINE pin #-}+pin :: MutByteArray -> IO MutByteArray+pin arr@(MutByteArray marr#) =+ if isPinned arr+ then return arr+ else+#ifdef DEBUG+ do+ -- XXX dump stack trace+ trace ("pin: Copying array") (return ())+#endif+ IO+ $ \s# ->+ case cloneMutableArrayWith# newPinnedByteArray# marr# s# of+ (# s1#, marr1# #) -> (# s1#, MutByteArray marr1# #)++-- | Return a copy of the array in unpinned memory if pinned, else return the+-- original array.+{-# INLINE unpin #-}+unpin :: MutByteArray -> IO MutByteArray+unpin arr@(MutByteArray marr#) =+ if not (isPinned arr)+ then return arr+ else+#ifdef DEBUG+ do+ -- XXX dump stack trace+ trace ("unpin: Copying array") (return ())+#endif+ IO+ $ \s# ->+ case cloneMutableArrayWith# newByteArray# marr# s# of+ (# s1#, marr1# #) -> (# s1#, MutByteArray marr1# #)++--------------------------------------------------------------------------------+-- Renaming+--------------------------------------------------------------------------------++RENAME(getMutableByteArray#, getMutByteArray#)+RENAME(newBytesAs, newAs)+RENAME(sizeOfMutableByteArray, length)+RENAME(putSliceUnsafe, unsafePutSlice)+RENAME(cloneSliceUnsafeAs, unsafeCloneSliceAs)+RENAME(cloneSliceUnsafe, unsafeCloneSlice)+RENAME(pinnedCloneSliceUnsafe, unsafePinnedCloneSlice)
src/Streamly/Internal/Data/Parser.hs view
@@ -1,14 +1,3639 @@--- |--- Module : Streamly.Internal.Data.Parser--- Copyright : (c) 2019 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC----module Streamly.Internal.Data.Parser- ( module Streamly.Internal.Data.Parser.ParserD- )-where--import Streamly.Internal.Data.Parser.ParserD+{-# LANGUAGE CPP #-}+{-# LANGUAGE NoMonoLocalBinds #-}+-- |+-- Module : Streamly.Internal.Data.Parser+-- Copyright : (c) 2020 Composewell Technologies+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC++module Streamly.Internal.Data.Parser+ (+ -- * Setup+ -- | To execute the code examples provided in this module in ghci, please+ -- run the following commands first.+ --+ -- $setup++ module Streamly.Internal.Data.Parser.Type+ --, module Streamly.Internal.Data.Parser.Tee++ -- * Downgrade to Fold+ , toFold++ -- First order parsers+ -- * Accumulators+ , fromFold+ , fromFoldMaybe++ -- * Map on input+ , postscan++ -- * Element parsers+ , peek++ -- All of these can be expressed in terms of either+ , one+ , oneEq+ , oneNotEq+ , oneOf+ , noneOf+ , eof+ , satisfy+ , maybe+ , either++ -- * Sequence parsers (tokenizers)+ --+ -- | Parsers chained in series, if one parser terminates the composition+ -- terminates. Currently we are using folds to collect the output of the+ -- parsers but we can use Parsers instead of folds to make the composition+ -- more powerful. For example, we can do:+ --+ -- takeEndByOrMax cond n p = takeEndBy cond (take n p)+ -- takeEndByBetween cond m n p = takeEndBy cond (takeBetween m n p)+ -- takeWhileBetween cond m n p = takeWhile cond (takeBetween m n p)+ , lookAhead++ -- ** By length+ -- | Grab a sequence of input elements without inspecting them+ , takeBetween+ -- , take -- takeBetween 0 n+ , takeEQ -- takeBetween n n+ , takeGE -- takeBetween n maxBound+ -- , takeGE1 -- take1 -- takeBetween 1 n+ , takeP++ -- Grab a sequence of input elements by inspecting them+ -- ** Exact match+ , listEq+ , listEqBy+ , streamEqBy+ , subsequenceBy++ -- ** By predicate+ , takeWhile+ , takeWhileP+ , takeWhile1+ , dropWhile++ -- ** Separated by elements+ -- | Separator could be in prefix postion ('takeBeginBy'), or suffix+ -- position ('takeEndBy'). See 'deintercalate', 'sepBy' etc for infix+ -- separator parsing, also see 'intersperseQuotedBy' fold.++ -- These can be implemented modularly with refolds, using takeWhile and+ -- satisfy.+ , takeEndBy+ , takeEndBy_+ , takeEndByEsc+ -- , takeEndByEsc_+ , takeBeginBy+ , takeBeginBy_+ , takeEitherSepBy+ , wordBy++ -- ** Grouped by element comparison+ , groupBy+ , groupByRolling+ , groupByRollingEither++ -- ** Framed by elements+ -- | Also see 'intersperseQuotedBy' fold.+ -- Framed by a one or more ocurrences of a separator around a word like+ -- spaces or quotes. No nesting.+ , wordFramedBy -- XXX Remove this? Covered by wordWithQuotes?+ , wordWithQuotes+ , wordKeepQuotes+ , wordProcessQuotes++ -- Framed by separate start and end characters, potentially nested.+ -- blockWithQuotes allows quotes inside a block. However,+ -- takeFramedByGeneric can be used to express takeBeginBy, takeEndBy and+ -- block with escaping.+ -- , takeFramedBy+ , takeFramedBy_+ , takeFramedByEsc_+ , takeFramedByGeneric+ , blockWithQuotes++ -- Matching strings+ -- , prefixOf -- match any prefix of a given string+ -- , suffixOf -- match any suffix of a given string+ -- , infixOf -- match any substring of a given string++ -- ** Spanning+ , span+ , spanBy+ , spanByRolling++ -- Second order parsers (parsers using parsers)+ -- * Binary Combinators+ {-+ -- ** Parallel Applicatives+ , teeWith+ , teeWithFst+ , teeWithMin+ -- , teeTill -- like manyTill but parallel+ -}++ {-+ -- ** Parallel Alternatives+ , shortest+ , longest+ -- , fastest+ -}++ -- * N-ary Combinators+ -- ** Sequential Collection+ , sequence++ -- ** Sequential Repetition+ , count+ , countBetween+ -- , countBetweenTill+ , manyP+ , many+ , some++ -- ** Interleaved Repetition+ -- Use two folds, run a primary parser, its rejected values go to the+ -- secondary parser.+ , deintercalate+ , deintercalate1+ , deintercalateAll+ -- , deintercalatePrefix+ -- , deintercalateSuffix++ -- *** Special cases+ -- | TODO: traditional implmentations of these may be of limited use. For+ -- example, consider parsing lines separated by @\\r\\n@. The main parser+ -- will have to detect and exclude the sequence @\\r\\n@ anyway so that we+ -- can apply the "sep" parser.+ --+ -- We can instead implement these as special cases of deintercalate.+ --+ -- @+ -- , endBy+ -- , sepEndBy+ -- , beginBy+ -- , sepBeginBy+ -- , sepAroundBy+ -- @+ , sepBy1+ , sepBy+ , sepByAll++ , manyTillP+ , manyTill+ , manyThen++ -- -- * Distribution+ --+ -- A simple and stupid impl would be to just convert the stream to an array+ -- and give the array reference to all consumers. The array can be grown on+ -- demand by any consumer and truncated when nonbody needs it.+ --+ -- -- ** Distribute to collection+ -- -- ** Distribute to repetition++ -- ** Interleaved collection+ -- |+ --+ -- 1. Round robin+ -- 2. Priority based+ , roundRobin++ -- -- ** Interleaved repetition+ -- repeat one parser and when it fails run an error recovery parser+ -- e.g. to find a key frame in the stream after an error++ -- ** Collection of Alternatives+ -- | Unimplemented+ --+ -- @+ -- , shortestN+ -- , longestN+ -- , fastestN -- first N successful in time+ -- , choiceN -- first N successful in position+ -- @+ -- , choice -- first successful in position++ -- ** Repeated Alternatives+ , retryMaxTotal+ , retryMaxSuccessive+ , retry++ -- ** Zipping Input+ , zipWithM+ , zip+ , indexed+ , makeIndexFilter+ , sampleFromthen++ -- * Deprecated+ , next+ , takeStartBy+ , takeStartBy_+ )+where++#include "inline.hs"+#include "deprecation.h"+#include "assert.hs"++import Data.Bifunctor (first)+import Fusion.Plugin.Types (Fuse(..))+import Streamly.Internal.Data.Fold.Type (Fold(..))+import Streamly.Internal.Data.SVar.Type (defState)+import Streamly.Internal.Data.Either.Strict (Either'(..))+import Streamly.Internal.Data.Maybe.Strict (Maybe'(..))+import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))+import Streamly.Internal.Data.Stream.Type (Stream)++import qualified Data.Foldable as Foldable+import qualified Streamly.Internal.Data.Fold.Type as FL+import qualified Streamly.Internal.Data.Stream.Type as D+import qualified Streamly.Internal.Data.Stream.Generate as D++import Streamly.Internal.Data.Parser.Type+--import Streamly.Internal.Data.Parser.Tee -- It's empty++import Prelude hiding+ (any, all, take, takeWhile, sequence, concatMap, maybe, either, span+ , zip, filter, dropWhile)++#include "DocTestDataParser.hs"++-------------------------------------------------------------------------------+-- Downgrade a parser to a Fold+-------------------------------------------------------------------------------++-- | Make a 'Fold' from a 'Parser'. The fold just throws an exception if the+-- parser fails or tries to backtrack.+--+-- This can be useful in combinators that accept a Fold and we know that a+-- Parser cannot fail or failure exception is acceptable as there is no way to+-- recover.+--+-- /Pre-release/+--+{-# INLINE toFold #-}+toFold :: Monad m => Parser a m b -> Fold m a b+toFold (Parser pstep pinitial pextract) = Fold step initial extract final++ where++ initial = do+ r <- pinitial+ case r of+ IPartial s -> return $ FL.Partial s+ IDone b -> return $ FL.Done b+ IError err ->+ error $ "toFold: parser throws error in initial" ++ err++ perror n = error $ "toFold: parser backtracks in SPartial: " ++ show n+ cerror n = error $ "toFold: parser backtracks in SContinue: " ++ show n+ derror n = error $ "toFold: parser backtracks in SDone: " ++ show n+ eerror err = error $ "toFold: parser throws error: " ++ err++ step st a = do+ r <- pstep st a+ case r of+ SPartial 1 s -> return $ FL.Partial s+ SContinue 1 s -> return $ FL.Partial s+ SDone 1 b -> return $ FL.Done b+ SPartial n _ -> perror n+ SContinue n _ -> cerror n+ SDone n _ -> derror n+ SError err -> eerror err++ extract = error "toFold: parser cannot be used for scanning"++ final st = do+ r <- pextract st+ case r of+ FDone 0 b -> return b+ FContinue n _ -> cerror n+ FDone n _ -> derror n+ FError err -> eerror err++-------------------------------------------------------------------------------+-- Upgrade folds to parses+-------------------------------------------------------------------------------++-- | Make a 'Parser' from a 'Fold'. This parser sends all of its input to the+-- fold.+--+{-# INLINE fromFold #-}+fromFold :: Monad m => Fold m a b -> Parser a m b+fromFold (Fold fstep finitial _ ffinal) = Parser step initial extract++ where++ initial = do+ res <- finitial+ return+ $ case res of+ FL.Partial s1 -> IPartial s1+ FL.Done b -> IDone b++ step s a = do+ res <- fstep s a+ return+ $ case res of+ FL.Partial s1 -> SPartial 1 s1+ FL.Done b -> SDone 1 b++ extract = fmap (FDone 0) . ffinal++-- | Convert a Maybe returning fold to an error returning parser. The first+-- argument is the error message that the parser would return when the fold+-- returns Nothing.+--+-- /Pre-release/+--+{-# INLINE fromFoldMaybe #-}+fromFoldMaybe :: Monad m => String -> Fold m a (Maybe b) -> Parser a m b+fromFoldMaybe errMsg (Fold fstep finitial _ ffinal) =+ Parser step initial extract++ where++ initial = do+ res <- finitial+ return+ $ case res of+ FL.Partial s1 -> IPartial s1+ FL.Done b ->+ case b of+ Just x -> IDone x+ Nothing -> IError errMsg++ step s a = do+ res <- fstep s a+ return+ $ case res of+ FL.Partial s1 -> SPartial 1 s1+ FL.Done b ->+ case b of+ Just x -> SDone 1 x+ Nothing -> SError errMsg++ extract s = do+ res <- ffinal s+ case res of+ Just x -> return $ FDone 0 x+ Nothing -> return $ FError errMsg++-------------------------------------------------------------------------------+-- Failing Parsers+-------------------------------------------------------------------------------++-- | Peek the head element of a stream, without consuming it. Fails if it+-- encounters end of input.+--+-- >>> Stream.parse ((,) <$> Parser.peek <*> Parser.satisfy (> 0)) $ Stream.fromList [1]+-- Right (1,1)+--+-- @+-- peek = lookAhead (satisfy True)+-- @+--+{-# INLINE peek #-}+peek :: Monad m => Parser a m a+peek = Parser step initial extract++ where++ initial = return $ IPartial ()++ step () a = return $ SDone 0 a++ extract () = return $ FError "peek: end of input"++-- | Succeeds if we are at the end of input, fails otherwise.+--+-- >>> Stream.parse ((,) <$> Parser.satisfy (> 0) <*> Parser.eof) $ Stream.fromList [1]+-- Right (1,())+--+{-# INLINE eof #-}+eof :: Monad m => Parser a m ()+eof = Parser step initial extract++ where++ initial = return $ IPartial ()++ step () _ = return $ SError "eof: not at end of input"++ extract () = return $ FDone 0 ()++-- | Return the next element of the input. Returns 'Nothing'+-- on end of input. Also known as 'head'.+--+-- /Pre-release/+--+{-# DEPRECATED next "Please use \"fromFold Fold.one\" instead" #-}+{-# INLINE next #-}+next :: Monad m => Parser a m (Maybe a)+next = Parser step initial extract++ where++ initial = pure $ IPartial ()++ step () a = pure $ SDone 1 (Just a)++ extract () = pure $ FDone 0 Nothing++-- | Map an 'Either' returning function on the next element in the stream. If+-- the function returns 'Left err', the parser fails with the error message+-- @err@ otherwise returns the 'Right' value.+--+-- /Pre-release/+--+{-# INLINE either #-}+either :: Monad m => (a -> Either String b) -> Parser a m b+either f = Parser step initial extract++ where++ initial = return $ IPartial ()++ step () a = return $+ case f a of+ Right b -> SDone 1 b+ Left err -> SError err++ extract () = return $ FError "end of input"++-- | Map a 'Maybe' returning function on the next element in the stream. The+-- parser fails if the function returns 'Nothing' otherwise returns the 'Just'+-- value.+--+-- >>> toEither = Maybe.maybe (Left "maybe: predicate failed") Right+-- >>> maybe f = Parser.either (toEither . f)+--+-- >>> maybe f = Parser.fromFoldMaybe "maybe: predicate failed" (Fold.maybe f)+--+-- /Pre-release/+--+{-# INLINE maybe #-}+maybe :: Monad m => (a -> Maybe b) -> Parser a m b+-- maybe f = either (Maybe.maybe (Left "maybe: predicate failed") Right . f)+maybe parserF = Parser step initial extract++ where++ initial = return $ IPartial ()++ step () a = return $+ case parserF a of+ Just b -> SDone 1 b+ Nothing -> SError "maybe: predicate failed"++ extract () = return $ FError "maybe: end of input"++-- | Returns the next element if it passes the predicate, fails otherwise.+--+-- >>> Stream.parse (Parser.satisfy (== 1)) $ Stream.fromList [1,0,1]+-- Right 1+--+-- >>> toMaybe f x = if f x then Just x else Nothing+-- >>> satisfy f = Parser.maybe (toMaybe f)+--+{-# INLINE satisfy #-}+satisfy :: Monad m => (a -> Bool) -> Parser a m a+-- satisfy predicate = maybe (\a -> if predicate a then Just a else Nothing)+satisfy predicate = Parser step initial extract++ where++ initial = return $ IPartial ()++ step () a = return $+ if predicate a+ then SDone 1 a+ else SError "satisfy: predicate failed"++ extract () = return $ FError "satisfy: end of input"++-- | Consume one element from the head of the stream. Fails if it encounters+-- end of input.+--+-- >>> one = Parser.satisfy $ const True+--+{-# INLINE one #-}+one :: Monad m => Parser a m a+one = satisfy $ const True++-- Alternate names: "only", "onlyThis".++-- | Match a specific element.+--+-- >>> oneEq x = Parser.satisfy (== x)+--+{-# INLINE oneEq #-}+oneEq :: (Monad m, Eq a) => a -> Parser a m a+oneEq x = satisfy (== x)++-- Alternate names: "exclude", "notThis".++-- | Match anything other than the supplied element.+--+-- >>> oneNotEq x = Parser.satisfy (/= x)+--+{-# INLINE oneNotEq #-}+oneNotEq :: (Monad m, Eq a) => a -> Parser a m a+oneNotEq x = satisfy (/= x)++-- | Match any one of the elements in the supplied list.+--+-- >>> oneOf xs = Parser.satisfy (`Foldable.elem` xs)+--+-- When performance matters a pattern matching predicate could be more+-- efficient than a 'Foldable' datatype:+--+-- @+-- let p x =+-- case x of+-- 'a' -> True+-- 'e' -> True+-- _ -> False+-- in satisfy p+-- @+--+-- GHC may use a binary search instead of linear search in the list.+-- Alternatively, you can also use an array instead of list for storage and+-- search.+--+{-# INLINE oneOf #-}+oneOf :: (Monad m, Eq a, Foldable f) => f a -> Parser a m a+oneOf xs = satisfy (`Foldable.elem` xs)++-- | See performance notes in 'oneOf'.+--+-- >>> noneOf xs = Parser.satisfy (`Foldable.notElem` xs)+--+{-# INLINE noneOf #-}+noneOf :: (Monad m, Eq a, Foldable f) => f a -> Parser a m a+noneOf xs = satisfy (`Foldable.notElem` xs)++-------------------------------------------------------------------------------+-- Taking elements+-------------------------------------------------------------------------------++-- Required to fuse "take" with "many" in "chunksOf", for ghc-9.x+{-# ANN type Tuple'Fused Fuse #-}+data Tuple'Fused a b = Tuple'Fused !a !b deriving Show++-- | @takeBetween m n@ takes a minimum of @m@ and a maximum of @n@ input+-- elements and folds them using the supplied fold.+--+-- Stops after @n@ elements.+-- Fails if the stream ends before @m@ elements could be taken.+--+-- Examples: -+--+-- @+-- >>> :{+-- takeBetween' low high ls = Stream.parsePos prsr (Stream.fromList ls)+-- where prsr = Parser.takeBetween low high Fold.toList+-- :}+--+-- @+--+-- >>> takeBetween' 2 4 [1, 2, 3, 4, 5]+-- Right [1,2,3,4]+--+-- >>> takeBetween' 2 4 [1, 2]+-- Right [1,2]+--+-- >>> takeBetween' 2 4 [1]+-- Left (ParseErrorPos 1 "takeBetween: Expecting alteast 2 elements, got 1")+--+-- >>> takeBetween' 0 0 [1, 2]+-- Right []+--+-- >>> takeBetween' 0 1 []+-- Right []+--+-- @takeBetween@ is the most general take operation, other take operations can+-- be defined in terms of takeBetween. For example:+--+-- >>> take n = Parser.takeBetween 0 n+-- >>> takeEQ n = Parser.takeBetween n n+-- >>> takeGE n = Parser.takeBetween n maxBound+--+-- /Pre-release/+--+{-# INLINE takeBetween #-}+takeBetween :: Monad m => Int -> Int -> Fold m a b -> Parser a m b+takeBetween low high (Fold fstep finitial _ ffinal) =++ Parser step initial (extract streamErr)++ where++ streamErr i =+ "takeBetween: Expecting alteast " ++ show low+ ++ " elements, got " ++ show i++ invalidRange =+ "takeBetween: lower bound - " ++ show low+ ++ " is greater than higher bound - " ++ show high++ foldErr :: Int -> String+ foldErr i =+ "takeBetween: the collecting fold terminated after"+ ++ " consuming" ++ show i ++ " elements"+ ++ " minimum" ++ show low ++ " elements needed"++ -- Exactly the same as snext except different constructors, we can possibly+ -- deduplicate the two.+ {-# INLINE inext #-}+ inext i res =+ let i1 = i + 1+ in case res of+ FL.Partial s -> do+ let s1 = Tuple'Fused i1 s+ if i1 < high+ -- XXX ideally this should be a Continue instead+ then return $ IPartial s1+ else iextract foldErr s1+ FL.Done b ->+ return+ $ if i1 >= low+ then IDone b+ else IError (foldErr i1)++ -- In the case of Identity monad+ -- @+ -- when True (error invalidRange)+ -- @+ -- does not evaluate the @error invalidRange@ due to which no error occurs.+ initial =+ if low >= 0 && high >= 0 && low > high+ then error invalidRange+ else finitial >>= inext (-1)++ -- Keep the impl same as inext+ {-# INLINE snext #-}+ snext i res =+ let i1 = i + 1+ in case res of+ FL.Partial s -> do+ let s1 = Tuple'Fused i1 s+ if i1 < low+ then return $ SContinue 1 s1+ else if i1 < high+ then return $ SPartial 1 s1+ else fmap (SDone 1) (ffinal s)+ FL.Done b ->+ return+ $ if i1 >= low+ then SDone 1 b+ else SError (foldErr i1)++ step (Tuple'Fused i s) a = fstep s a >>= snext i++ extract f (Tuple'Fused i s)+ | i >= low && i <= high = fmap (FDone 0) (ffinal s)+ | otherwise = return $ FError (f i)++ -- XXX Need to make Initial return type Step to deduplicate this+ iextract f (Tuple'Fused i s)+ | i >= low && i <= high = fmap IDone (ffinal s)+ | otherwise = return $ IError (f i)++-- | Stops after taking exactly @n@ input elements.+--+-- * Stops - after consuming @n@ elements.+-- * Fails - if the stream or the collecting fold ends before it can collect+-- exactly @n@ elements.+--+-- >>> Stream.parsePos (Parser.takeEQ 2 Fold.toList) $ Stream.fromList [1,0,1]+-- Right [1,0]+--+-- >>> Stream.parsePos (Parser.takeEQ 4 Fold.toList) $ Stream.fromList [1,0,1]+-- Left (ParseErrorPos 3 "takeEQ: Expecting exactly 4 elements, input terminated on 3")+--+{-# INLINE takeEQ #-}+takeEQ :: Monad m => Int -> Fold m a b -> Parser a m b+takeEQ n (Fold fstep finitial _ ffinal) = Parser step initial extract++ where++ initial = do+ res <- finitial+ case res of+ FL.Partial s ->+ if n > 0+ then return $ IPartial $ Tuple'Fused 1 s+ else fmap IDone (ffinal s)+ FL.Done b -> return $+ if n > 0+ then IError+ $ "takeEQ: Expecting exactly " ++ show n+ ++ " elements, fold terminated without"+ ++ " consuming any elements"+ else IDone b++ step (Tuple'Fused i1 r) a = do+ res <- fstep r a+ if n > i1+ then+ return+ $ case res of+ FL.Partial s -> SContinue 1 $ Tuple'Fused (i1 + 1) s+ FL.Done _ ->+ SError+ $ "takeEQ: Expecting exactly " ++ show n+ ++ " elements, fold terminated on " ++ show i1+ else+ -- assert (n == i1)+ SDone 1+ <$> case res of+ FL.Partial s -> ffinal s+ FL.Done b -> return b++ extract (Tuple'Fused i _) =+ -- Using the count "i" in the message below causes large performance+ -- regression unless we use Fuse annotation on Tuple.+ return+ $ FError+ $ "takeEQ: Expecting exactly " ++ show n+ ++ " elements, input terminated on " ++ show (i - 1)++{-# ANN type TakeGEState Fuse #-}+data TakeGEState s =+ TakeGELT !Int !s+ | TakeGEGE !s++-- | Take at least @n@ input elements, but can collect more.+--+-- * Stops - when the collecting fold stops.+-- * Fails - if the stream or the collecting fold ends before producing @n@+-- elements.+--+-- >>> Stream.parsePos (Parser.takeGE 4 Fold.toList) $ Stream.fromList [1,0,1]+-- Left (ParseErrorPos 3 "takeGE: Expecting at least 4 elements, input terminated on 3")+--+-- >>> Stream.parse (Parser.takeGE 4 Fold.toList) $ Stream.fromList [1,0,1,0,1]+-- Right [1,0,1,0,1]+--+-- /Pre-release/+--+{-# INLINE takeGE #-}+takeGE :: Monad m => Int -> Fold m a b -> Parser a m b+takeGE n (Fold fstep finitial _ ffinal) = Parser step initial extract++ where++ initial = do+ res <- finitial+ case res of+ FL.Partial s ->+ if n > 0+ then return $ IPartial $ TakeGELT 1 s+ else return $ IPartial $ TakeGEGE s+ FL.Done b -> return $+ if n > 0+ then IError+ $ "takeGE: Expecting at least " ++ show n+ ++ " elements, fold terminated without"+ ++ " consuming any elements"+ else IDone b++ step (TakeGELT i1 r) a = do+ res <- fstep r a+ if n > i1+ then+ return+ $ case res of+ FL.Partial s -> SContinue 1 $ TakeGELT (i1 + 1) s+ FL.Done _ ->+ SError+ $ "takeGE: Expecting at least " ++ show n+ ++ " elements, fold terminated on " ++ show i1+ else+ -- assert (n <= i1)+ return+ $ case res of+ FL.Partial s -> SPartial 1 $ TakeGEGE s+ FL.Done b -> SDone 1 b+ step (TakeGEGE r) a = do+ res <- fstep r a+ return+ $ case res of+ FL.Partial s -> SPartial 1 $ TakeGEGE s+ FL.Done b -> SDone 1 b++ extract (TakeGELT i _) =+ return+ $ FError+ $ "takeGE: Expecting at least " ++ show n+ ++ " elements, input terminated on " ++ show (i - 1)+ extract (TakeGEGE r) = fmap (FDone 0) $ ffinal r++-------------------------------------------------------------------------------+-- Conditional splitting+-------------------------------------------------------------------------------++-- XXX We should perhaps use only takeWhileP and rename it to takeWhile.++-- | Like 'takeWhile' but uses a 'Parser' instead of a 'Fold' to collect the+-- input. The combinator stops when the condition fails or if the collecting+-- parser stops.+--+-- Other interesting parsers can be implemented in terms of this parser:+--+-- >>> takeWhile1 cond p = Parser.takeWhileP cond (Parser.takeBetween 1 maxBound p)+-- >>> takeWhileBetween cond m n p = Parser.takeWhileP cond (Parser.takeBetween m n p)+--+-- Stops: when the condition fails or the collecting parser stops.+-- Fails: when the collecting parser fails.+--+-- /Pre-release/+--+{-# INLINE takeWhileP #-}+takeWhileP :: Monad m => (a -> Bool) -> Parser a m b -> Parser a m b+takeWhileP predicate (Parser pstep pinitial pextract) =+ Parser step pinitial pextract++ where++ step s a =+ if predicate a+ then pstep s a+ else do+ -- In this case when converting Final to Step we don't add 1 as we+ -- don't consume the current element.+ r <- pextract s+ case r of+ FError err -> return $ SError err+ FDone n s1 -> return $ SDone n s1+ FContinue n s1 -> return $ SContinue n s1++-- | Collect stream elements until an element fails the predicate. The element+-- on which the predicate fails is returned back to the input stream.+--+-- * Stops - when the predicate fails or the collecting fold stops.+-- * Fails - never.+--+-- >>> Stream.parse (Parser.takeWhile (== 0) Fold.toList) $ Stream.fromList [0,0,1,0,1]+-- Right [0,0]+--+-- >>> takeWhile cond f = Parser.takeWhileP cond (Parser.fromFold f)+--+-- We can implement a @breakOn@ using 'takeWhile':+--+-- @+-- breakOn p = takeWhile (not p)+-- @+--+{-# INLINE takeWhile #-}+takeWhile :: Monad m => (a -> Bool) -> Fold m a b -> Parser a m b+-- takeWhile cond f = takeWhileP cond (fromFold f)+takeWhile predicate (Fold fstep finitial _ ffinal) =+ Parser step initial extract++ where++ initial = do+ res <- finitial+ return $ case res of+ FL.Partial s -> IPartial s+ FL.Done b -> IDone b++ step s a =+ if predicate a+ then do+ fres <- fstep s a+ return+ $ case fres of+ FL.Partial s1 -> SPartial 1 s1+ FL.Done b -> SDone 1 b+ else SDone 0 <$> ffinal s++ extract s = fmap (FDone 0) (ffinal s)++{-+-- XXX This may not be composable because of the b argument. We can instead+-- return a "Reparse b a m b" so that those can be composed.+{-# INLINE takeWhile1X #-}+takeWhile1 :: Monad m => b -> (a -> Bool) -> Refold m b a b -> Parser a m b+-- We can implement this using satisfy and takeWhile. We can use "satisfy+-- p", fold the result with the refold and then use the "takeWhile p" and+-- fold that using the refold.+takeWhile1 acc cond f = undefined+-}++-- | Like 'takeWhile' but takes at least one element otherwise fails.+--+-- >>> takeWhile1 cond p = Parser.takeWhileP cond (Parser.takeBetween 1 maxBound p)+--+{-# INLINE takeWhile1 #-}+takeWhile1 :: Monad m => (a -> Bool) -> Fold m a b -> Parser a m b+-- takeWhile1 cond f = takeWhileP cond (takeBetween 1 maxBound f)+takeWhile1 predicate (Fold fstep finitial _ ffinal) =+ Parser step initial extract++ where++ initial = do+ res <- finitial+ return $ case res of+ FL.Partial s -> IPartial (Left' s)+ FL.Done _ ->+ IError+ $ "takeWhile1: fold terminated without consuming:"+ ++ " any element"++ {-# INLINE process #-}+ process s a = do+ res <- fstep s a+ return+ $ case res of+ FL.Partial s1 -> SPartial 1 (Right' s1)+ FL.Done b -> SDone 1 b++ step (Left' s) a =+ if predicate a+ then process s a+ else return $ SError "takeWhile1: predicate failed on first element"+ step (Right' s) a =+ if predicate a+ then process s a+ else do+ b <- ffinal s+ return $ SDone 0 b++ extract (Left' _) = return $ FError "takeWhile1: end of input"+ extract (Right' s) = fmap (FDone 0) (ffinal s)++-- | Drain the input as long as the predicate succeeds, running the effects and+-- discarding the results.+--+-- This is also called @skipWhile@ in some parsing libraries.+--+-- >>> dropWhile p = Parser.takeWhile p Fold.drain+--+{-# INLINE dropWhile #-}+dropWhile :: Monad m => (a -> Bool) -> Parser a m ()+dropWhile p = takeWhile p FL.drain++-------------------------------------------------------------------------------+-- Separators+-------------------------------------------------------------------------------++{-# ANN type FramedEscState Fuse #-}+data FramedEscState s =+ FrameEscInit !s | FrameEscGo !s !Int | FrameEscEsc !s !Int++-- XXX We can remove Maybe from esc+{-# INLINE takeFramedByGeneric #-}+takeFramedByGeneric :: Monad m =>+ Maybe (a -> Bool) -- is escape char?+ -> Maybe (a -> Bool) -- is frame begin?+ -> Maybe (a -> Bool) -- is frame end?+ -> Fold m a b+ -> Parser a m b+takeFramedByGeneric esc begin end (Fold fstep finitial _ ffinal) =++ Parser step initial extract++ where++ initial = do+ res <- finitial+ return $+ case res of+ FL.Partial s -> IPartial (FrameEscInit s)+ FL.Done _ ->+ error "takeFramedByGeneric: fold done without input"++ {-# INLINE process #-}+ process s a n = do+ res <- fstep s a+ return+ $ case res of+ FL.Partial s1 -> SContinue 1 (FrameEscGo s1 n)+ FL.Done b -> SDone 1 b++ {-# INLINE processNoEsc #-}+ processNoEsc s a n =+ case end of+ Just isEnd ->+ case begin of+ Just isBegin ->+ -- takeFramedBy case+ if isEnd a+ then+ if n == 0+ then SDone 1 <$> ffinal s+ else process s a (n - 1)+ else+ let n1 = if isBegin a then n + 1 else n+ in process s a n1+ Nothing -> -- takeEndBy case+ if isEnd a+ then SDone 1 <$> ffinal s+ else process s a n+ Nothing -> -- takeBeginBy case+ case begin of+ Just isBegin ->+ if isBegin a+ then SDone 1 <$> ffinal s+ else process s a n+ Nothing ->+ error $ "takeFramedByGeneric: "+ ++ "Both begin and end frame predicate missing"++ {-# INLINE processCheckEsc #-}+ processCheckEsc s a n =+ case esc of+ Just isEsc ->+ if isEsc a+ then return $ SPartial 1 $ FrameEscEsc s n+ else processNoEsc s a n+ Nothing -> processNoEsc s a n++ step (FrameEscInit s) a =+ case begin of+ Just isBegin ->+ if isBegin a+ then return $ SPartial 1 (FrameEscGo s 0)+ else return $ SError "takeFramedByGeneric: missing frame start"+ Nothing ->+ case end of+ Just isEnd ->+ if isEnd a+ then SDone 1 <$> ffinal s+ else processCheckEsc s a 0+ Nothing ->+ error "Both begin and end frame predicate missing"+ step (FrameEscGo s n) a = processCheckEsc s a n+ step (FrameEscEsc s n) a = process s a n++ err = return . FError++ extract (FrameEscInit _) =+ err "takeFramedByGeneric: empty token"+ extract (FrameEscGo s _) =+ case begin of+ Just _ ->+ case end of+ Nothing -> fmap (FDone 0) $ ffinal s+ Just _ -> err "takeFramedByGeneric: missing frame end"+ Nothing -> err "takeFramedByGeneric: missing closing frame"+ extract (FrameEscEsc _ _) = err "takeFramedByGeneric: trailing escape"++data BlockParseState s =+ BlockInit !s+ | BlockUnquoted !Int !s+ | BlockQuoted !Int !s+ | BlockQuotedEsc !Int !s++-- Blocks can be of different types e.g. {} or (). We only parse from the+-- perspective of the outermost block type. The nesting of that block are+-- checked. Any other block types nested inside it are opaque to us and can be+-- parsed when the contents of the block are parsed.++-- XXX Put a limit on nest level to keep the API safe.++-- | Parse a block enclosed within open, close brackets. Block contents may be+-- quoted, brackets inside quotes are ignored. Quoting characters can be used+-- within quotes if escaped. A block can have a nested block inside it.+--+-- Quote begin and end chars are the same. Block brackets and quote chars must+-- not overlap. Block start and end brackets must be different for nesting+-- blocks within blocks.+--+-- >>> p = Parser.blockWithQuotes (== '\\') (== '"') '{' '}' Fold.toList+-- >>> Stream.parse p $ Stream.fromList "{msg: \"hello world\"}"+-- Right "msg: \"hello world\""+--+{-# INLINE blockWithQuotes #-}+blockWithQuotes :: (Monad m, Eq a) =>+ (a -> Bool) -- ^ escape char+ -> (a -> Bool) -- ^ quote char, to quote inside brackets+ -> a -- ^ Block opening bracket+ -> a -- ^ Block closing bracket+ -> Fold m a b+ -> Parser a m b+blockWithQuotes isEsc isQuote bopen bclose+ (Fold fstep finitial _ ffinal) =+ Parser step initial extract++ where++ initial = do+ res <- finitial+ return $+ case res of+ FL.Partial s -> IPartial (BlockInit s)+ FL.Done _ ->+ error "blockWithQuotes: fold finished without input"++ {-# INLINE process #-}+ process s a nextState = do+ res <- fstep s a+ return+ $ case res of+ FL.Partial s1 -> SContinue 1 (nextState s1)+ FL.Done b -> SDone 1 b++ step (BlockInit s) a =+ return+ $ if a == bopen+ then SContinue 1 $ BlockUnquoted 1 s+ else SError "blockWithQuotes: missing block start"+ step (BlockUnquoted level s) a+ | a == bopen = process s a (BlockUnquoted (level + 1))+ | a == bclose =+ if level == 1+ then fmap (SDone 1) (ffinal s)+ else process s a (BlockUnquoted (level - 1))+ | isQuote a = process s a (BlockQuoted level)+ | otherwise = process s a (BlockUnquoted level)+ step (BlockQuoted level s) a+ | isEsc a = process s a (BlockQuotedEsc level)+ | otherwise =+ if isQuote a+ then process s a (BlockUnquoted level)+ else process s a (BlockQuoted level)+ step (BlockQuotedEsc level s) a = process s a (BlockQuoted level)++ err = return . FError++ extract (BlockInit s) = fmap (FDone 0) $ ffinal s+ extract (BlockUnquoted level _) =+ err $ "blockWithQuotes: finished at block nest level " ++ show level+ extract (BlockQuoted level _) =+ err $ "blockWithQuotes: finished, inside an unfinished quote, "+ ++ "at block nest level " ++ show level+ extract (BlockQuotedEsc level _) =+ err $ "blockWithQuotes: finished, inside an unfinished quote, "+ ++ "after an escape char, at block nest level " ++ show level++{-# INLINE takeEndByDone #-}+takeEndByDone :: Monad f => (s -> f (Final s b)) -> Step s b -> f (Step s b)+takeEndByDone pextract res =+ -- If the parser is backtracking we let it backtrack even if the+ -- predicate is true.+ case res of+ SPartial 1 s1 -> do+ res1 <- pextract s1+ pure $ case res1 of+ FDone n b -> SDone (1 + n) b+ FContinue n s -> SPartial (1 + n) s+ FError err -> SError err+ SContinue 1 s1 -> do+ res1 <- pextract s1+ pure $ case res1 of+ FDone n b -> SDone (1 + n) b+ FContinue n s -> SContinue (1 + n) s+ FError err -> SError err+ SPartial _ _ -> return res+ SContinue _ _ -> return res+ SDone n b -> return $ SDone n b+ SError n -> return $ SError n++-- | @takeEndBy cond parser@ parses a token that ends by a separator chosen by+-- the supplied predicate. The separator is also taken with the token.+--+-- This can be combined with other parsers to implement other interesting+-- parsers as follows:+--+-- >>> takeEndByLE cond n p = Parser.takeEndBy cond (Parser.fromFold $ Fold.take n p)+-- >>> takeEndByBetween cond m n p = Parser.takeEndBy cond (Parser.takeBetween m n p)+--+-- >>> takeEndBy = Parser.takeEndByEsc (const False)+--+-- See also "Streamly.Data.Fold.takeEndBy". Unlike the fold, the collecting+-- parser in the takeEndBy parser can decide whether to fail or not if the+-- stream does not end with separator.+--+-- /Pre-release/+--+{-# INLINE takeEndBy #-}+takeEndBy :: Monad m => (a -> Bool) -> Parser a m b -> Parser a m b+-- takeEndBy = takeEndByEsc (const False)+takeEndBy cond (Parser pstep pinitial pextract) =++ Parser step initial pextract++ where++ initial = pinitial++ step s a = do+ res <- pstep s a+ if not (cond a)+ then return res+ else takeEndByDone pextract res++-- | Like 'takeEndBy' but the separator elements can be escaped using an+-- escape char determined by the first predicate. The escape characters are+-- removed.+--+-- /pre-release/+{-# INLINE takeEndByEsc #-}+takeEndByEsc :: Monad m =>+ (a -> Bool) -> (a -> Bool) -> Parser a m b -> Parser a m b+takeEndByEsc isEsc isSep (Parser pstep pinitial pextract) =++ Parser step initial extract++ where++ initial = first Left' <$> pinitial++ step (Left' s) a = do+ if isEsc a+ then return $ SPartial 1 $ Right' s+ else do+ res <- pstep s a+ if not (isSep a)+ then return $ first Left' res+ else fmap (first Left') $ takeEndByDone pextract res++ step (Right' s) a = do+ res <- pstep s a+ return $ first Left' res++ extract (Left' s) = fmap (first Left') $ pextract s+ extract (Right' _) =+ return $ FError "takeEndByEsc: trailing escape"++-- | Like 'takeEndBy' but the separator is dropped.+--+-- See also "Streamly.Data.Fold.takeEndBy_".+--+-- /Pre-release/+--+{-# INLINE takeEndBy_ #-}+takeEndBy_ :: Monad m => (a -> Bool) -> Parser a m b -> Parser a m b+{-+takeEndBy_ isEnd p =+ takeFramedByGeneric Nothing Nothing (Just isEnd) (toFold p)+-}+takeEndBy_ cond (Parser pstep pinitial pextract) =++ Parser step pinitial pextract++ where++ step s a =+ if cond a+ then do+ res <- pextract s+ pure $ case res of+ FDone n b -> SDone (n + 1) b+ FContinue n s1 -> SPartial (n + 1) s1+ FError err -> SError err+ else pstep s a++-- | Take either the separator or the token. Separator is a Left value and+-- token is Right value.+--+-- /Unimplemented/+{-# INLINE takeEitherSepBy #-}+takeEitherSepBy :: -- Monad m =>+ (a -> Bool) -> Fold m (Either a b) c -> Parser a m c+takeEitherSepBy _cond = undefined -- D.toParserK . D.takeEitherSepBy cond++-- | Parse a token that starts with an element chosen by the predicate. The+-- parser fails if the input does not start with the selected element.+--+-- * Stops - when the predicate succeeds in non-leading position.+-- * Fails - when the predicate fails in the leading position.+--+-- >>> splitWithPrefix p f = Stream.parseMany (Parser.takeBeginBy p f)+--+-- Examples: -+--+-- >>> p = Parser.takeBeginBy (== ',') Fold.toList+-- >>> leadingComma = Stream.parsePos p . Stream.fromList+-- >>> leadingComma "a,b"+-- Left (ParseErrorPos 1 "takeBeginBy: missing frame start")+-- ...+-- >>> leadingComma ",,"+-- Right ","+-- >>> leadingComma ",a,b"+-- Right ",a"+-- >>> leadingComma ""+-- Right ""+--+-- /Pre-release/+--+{-# INLINE takeBeginBy #-}+takeBeginBy, takeStartBy :: Monad m =>+ (a -> Bool) -> Fold m a b -> Parser a m b+takeBeginBy cond (Fold fstep finitial _ ffinal) =++ Parser step initial extract++ where++ initial = do+ res <- finitial+ return $+ case res of+ FL.Partial s -> IPartial (Left' s)+ FL.Done _ -> IError "takeBeginBy: fold done without input"++ {-# INLINE process #-}+ process s a = do+ res <- fstep s a+ return+ $ case res of+ FL.Partial s1 -> SPartial 1 (Right' s1)+ FL.Done b -> SDone 1 b++ step (Left' s) a =+ if cond a+ then process s a+ else return $ SError "takeBeginBy: missing frame start"+ step (Right' s) a =+ if not (cond a)+ then process s a+ else SDone 0 <$> ffinal s++ extract (Left' s) = fmap (FDone 0) $ ffinal s+ extract (Right' s) = fmap (FDone 0) $ ffinal s++RENAME(takeStartBy,takeBeginBy)++-- | Like 'takeBeginBy' but drops the separator.+--+-- >>> takeBeginBy_ isBegin = Parser.takeFramedByGeneric Nothing (Just isBegin) Nothing+--+{-# INLINE takeBeginBy_ #-}+takeBeginBy_, takeStartBy_ :: Monad m =>+ (a -> Bool) -> Fold m a b -> Parser a m b+takeBeginBy_ isBegin = takeFramedByGeneric Nothing (Just isBegin) Nothing++RENAME(takeStartBy_,takeBeginBy_)++-- | @takeFramedByEsc_ isEsc isBegin isEnd fold@ parses a token framed using a+-- begin and end predicate, and an escape character. The frame begin and end+-- characters lose their special meaning if preceded by the escape character.+--+-- Nested frames are allowed if begin and end markers are different, nested+-- frames must be balanced unless escaped, nested frame markers are emitted as+-- it is.+--+-- For example,+--+-- >>> p = Parser.takeFramedByEsc_ (== '\\') (== '{') (== '}') Fold.toList+-- >>> Stream.parse p $ Stream.fromList "{hello}"+-- Right "hello"+-- >>> Stream.parse p $ Stream.fromList "{hello {world}}"+-- Right "hello {world}"+-- >>> Stream.parse p $ Stream.fromList "{hello \\{world}"+-- Right "hello {world"+-- >>> Stream.parsePos p $ Stream.fromList "{hello {world}"+-- Left (ParseErrorPos 14 "takeFramedByEsc_: missing frame end")+--+-- /Pre-release/+{-# INLINE takeFramedByEsc_ #-}+takeFramedByEsc_ :: Monad m =>+ (a -> Bool) -> (a -> Bool) -> (a -> Bool) -> Fold m a b -> Parser a m b+-- takeFramedByEsc_ isEsc isEnd p =+-- takeFramedByGeneric (Just isEsc) Nothing (Just isEnd) (toFold p)+takeFramedByEsc_ isEsc isBegin isEnd (Fold fstep finitial _ ffinal ) =++ Parser step initial extract++ where++ initial = do+ res <- finitial+ return $+ case res of+ FL.Partial s -> IPartial (FrameEscInit s)+ FL.Done _ ->+ error "takeFramedByEsc_: fold done without input"++ {-# INLINE process #-}+ process s a n = do+ res <- fstep s a+ return+ $ case res of+ FL.Partial s1 -> SContinue 1 (FrameEscGo s1 n)+ FL.Done b -> SDone 1 b++ step (FrameEscInit s) a =+ if isBegin a+ then return $ SPartial 1 (FrameEscGo s 0)+ else return $ SError "takeFramedByEsc_: missing frame start"+ step (FrameEscGo s n) a =+ if isEsc a+ then return $ SPartial 1 $ FrameEscEsc s n+ else do+ if not (isEnd a)+ then+ let n1 = if isBegin a then n + 1 else n+ in process s a n1+ else+ if n == 0+ then SDone 1 <$> ffinal s+ else process s a (n - 1)+ step (FrameEscEsc s n) a = process s a n++ err = return . FError++ extract (FrameEscInit _) = err "takeFramedByEsc_: empty token"+ extract (FrameEscGo _ _) = err "takeFramedByEsc_: missing frame end"+ extract (FrameEscEsc _ _) = err "takeFramedByEsc_: trailing escape"++data FramedState s = FrameInit !s | FrameGo !s Int++-- | @takeFramedBy_ isBegin isEnd fold@ parses a token framed by a begin and an+-- end predicate.+--+-- >>> takeFramedBy_ = Parser.takeFramedByEsc_ (const False)+--+{-# INLINE takeFramedBy_ #-}+takeFramedBy_ :: Monad m =>+ (a -> Bool) -> (a -> Bool) -> Fold m a b -> Parser a m b+-- takeFramedBy_ isBegin isEnd =+-- takeFramedByGeneric (Just (const False)) (Just isBegin) (Just isEnd)+takeFramedBy_ isBegin isEnd (Fold fstep finitial _ ffinal) =++ Parser step initial extract++ where++ initial = do+ res <- finitial+ return $+ case res of+ FL.Partial s -> IPartial (FrameInit s)+ FL.Done _ ->+ error "takeFramedBy_: fold done without input"++ {-# INLINE process #-}+ process s a n = do+ res <- fstep s a+ return+ $ case res of+ FL.Partial s1 -> SContinue 1 (FrameGo s1 n)+ FL.Done b -> SDone 1 b++ step (FrameInit s) a =+ if isBegin a+ then return $ SContinue 1 (FrameGo s 0)+ else return $ SError "takeFramedBy_: missing frame start"+ step (FrameGo s n) a+ | not (isEnd a) =+ let n1 = if isBegin a then n + 1 else n+ in process s a n1+ | n == 0 = SDone 1 <$> ffinal s+ | otherwise = process s a (n - 1)++ err = return . FError++ extract (FrameInit _) = err "takeFramedBy_: empty token"+ extract (FrameGo _ _) = err "takeFramedBy_: missing frame end"++-------------------------------------------------------------------------------+-- Grouping and words+-------------------------------------------------------------------------------++data WordByState s b = WBLeft !s | WBWord !s | WBRight !b++-- Note we can also get words using something like:+-- sepBy FL.toList (takeWhile (not . p) Fold.toList) (dropWhile p)+--+-- But that won't be as efficient and ergonomic.++-- | Like 'splitOn' but strips leading, trailing, and repeated separators.+-- Therefore, @".a..b."@ having '.' as the separator would be parsed as+-- @["a","b"]@. In other words, its like parsing words from whitespace+-- separated text.+--+-- * Stops - when it finds a word separator after a non-word element+-- * Fails - never.+--+-- >>> wordBy = Parser.wordFramedBy (const False) (const False) (const False)+--+-- @+-- S.wordsBy pred f = S.parseMany (PR.wordBy pred f)+-- @+--+{-# INLINE wordBy #-}+wordBy :: Monad m => (a -> Bool) -> Fold m a b -> Parser a m b+wordBy predicate (Fold fstep finitial _ ffinal) = Parser step initial extract++ where++ {-# INLINE worder #-}+ worder s a = do+ res <- fstep s a+ return+ $ case res of+ FL.Partial s1 -> SPartial 1 $ WBWord s1+ FL.Done b -> SDone 1 b++ initial = do+ res <- finitial+ return+ $ case res of+ FL.Partial s -> IPartial $ WBLeft s+ FL.Done b -> IDone b++ step (WBLeft s) a =+ if not (predicate a)+ then worder s a+ else return $ SPartial 1 $ WBLeft s+ step (WBWord s) a =+ if not (predicate a)+ then worder s a+ else do+ b <- ffinal s+ return $ SPartial 1 $ WBRight b+ step (WBRight b) a =+ return+ $ if not (predicate a)+ then SDone 0 b+ else SPartial 1 $ WBRight b++ extract (WBLeft s) = fmap (FDone 0) $ ffinal s+ extract (WBWord s) = fmap (FDone 0) $ ffinal s+ extract (WBRight b) = return (FDone 0 b)++data WordFramedState s b =+ WordFramedSkipPre !s+ | WordFramedWord !s !Int+ | WordFramedEsc !s !Int+ | WordFramedSkipPost !b++-- | Like 'wordBy' but treats anything inside a pair of quotes as a single+-- word, the quotes can be escaped by an escape character. Recursive quotes+-- are possible if quote begin and end characters are different, quotes must be+-- balanced. Outermost quotes are stripped.+--+-- >>> braces = Parser.wordFramedBy (== '\\') (== '{') (== '}') isSpace Fold.toList+-- >>> Stream.parse braces $ Stream.fromList "{ab} cd"+-- Right "ab"+-- >>> Stream.parse braces $ Stream.fromList "{ab}{cd}"+-- Right "abcd"+-- >>> Stream.parse braces $ Stream.fromList "a{b} cd"+-- Right "ab"+-- >>> Stream.parse braces $ Stream.fromList "a{{b}} cd"+-- Right "a{b}"+--+-- >>> quotes = Parser.wordFramedBy (== '\\') (== '"') (== '"') isSpace Fold.toList+-- >>> Stream.parse quotes $ Stream.fromList "\"a\"\"b\""+-- Right "ab"+--+{-# INLINE wordFramedBy #-}+wordFramedBy :: Monad m =>+ (a -> Bool) -- ^ Matches escape elem?+ -> (a -> Bool) -- ^ Matches left quote?+ -> (a -> Bool) -- ^ matches right quote?+ -> (a -> Bool) -- ^ matches word separator?+ -> Fold m a b+ -> Parser a m b+wordFramedBy isEsc isBegin isEnd isSep+ (Fold fstep finitial _ ffinal) =+ Parser step initial extract++ where++ initial = do+ res <- finitial+ return $+ case res of+ FL.Partial s -> IPartial (WordFramedSkipPre s)+ FL.Done _ ->+ error "wordFramedBy: fold done without input"++ {-# INLINE process #-}+ process s a n = do+ res <- fstep s a+ return+ $ case res of+ FL.Partial s1 -> SContinue 1 (WordFramedWord s1 n)+ FL.Done b -> SDone 1 b++ step (WordFramedSkipPre s) a+ | isEsc a = return $ SContinue 1 $ WordFramedEsc s 0+ | isSep a = return $ SPartial 1 $ WordFramedSkipPre s+ | isBegin a = return $ SContinue 1 $ WordFramedWord s 1+ | isEnd a =+ return $ SError "wordFramedBy: missing frame start"+ | otherwise = process s a 0+ step (WordFramedWord s n) a+ | isEsc a = return $ SContinue 1 $ WordFramedEsc s n+ | n == 0 && isSep a = do+ b <- ffinal s+ return $ SPartial 1 $ WordFramedSkipPost b+ | otherwise = do+ -- We need to use different order for checking begin and end for+ -- the n == 0 and n == 1 case so that when the begin and end+ -- character is the same we treat the one after begin as end.+ if n == 0+ then+ -- Need to check isBegin first+ if isBegin a+ then return $ SContinue 1 $ WordFramedWord s 1+ else if isEnd a+ then return $ SError "wordFramedBy: missing frame start"+ else process s a n+ else+ -- Need to check isEnd first+ if isEnd a+ then+ if n == 1+ then return $ SContinue 1 $ WordFramedWord s 0+ else process s a (n - 1)+ else if isBegin a+ then process s a (n + 1)+ else process s a n+ step (WordFramedEsc s n) a = process s a n+ step (WordFramedSkipPost b) a =+ return+ $ if not (isSep a)+ then SDone 0 b+ else SPartial 1 $ WordFramedSkipPost b++ err = return . FError++ extract (WordFramedSkipPre s) = fmap (FDone 0) $ ffinal s+ extract (WordFramedWord s n) =+ if n == 0+ then fmap (FDone 0) $ ffinal s+ else err "wordFramedBy: missing frame end"+ extract (WordFramedEsc _ _) =+ err "wordFramedBy: trailing escape"+ extract (WordFramedSkipPost b) = return (FDone 0 b)++data WordQuotedState s b a =+ WordQuotedSkipPre !s+ | WordUnquotedWord !s+ | WordQuotedWord !s !Int !a !a+ | WordUnquotedEsc !s+ | WordQuotedEsc !s !Int !a !a+ | WordQuotedSkipPost !b++-- | Quote and bracket aware word splitting with escaping. Like 'wordBy' but+-- word separators within specified quotes or brackets are ignored. Quotes and+-- escape characters can be processed. If the end quote is different from the+-- start quote it is called a bracket. The following quoting rules apply:+--+-- * In an unquoted string a character may be preceded by an escape character.+-- The escape character is removed and the character following it is treated+-- literally with no special meaning e.g. e.g. h\ e\ l\ l\ o is a single word,+-- \n is same as n.+-- * Any part of the word can be placed within quotes. Inside quotes all+-- characters are treated literally with no special meaning. Quoting character+-- itself cannot be used within quotes unless escape processing within quotes+-- is applied to allow it.+-- * Optionally escape processing for quoted part can be specified. Escape+-- character has no special meaning inside quotes unless it is followed by a+-- character that has a escape translation specified, in that case the escape+-- character is removed, and the specified translation is applied to the+-- character following it. This can be used to escape the quoting character+-- itself within quotes.+-- * There can be multiple quoting characters, when a quote starts, all other+-- quoting characters within that quote lose any special meaning until the+-- quote is closed.+-- * A starting quote char without an ending char generates a parse error. An+-- ending bracket char without a corresponding bracket begin is ignored.+-- * Brackets can be nested.+--+-- We should note that unquoted and quoted escape processing are different. In+-- unquoted part escape character is always removed. In quoted part it is+-- removed only if followed by a special meaning character. This is consistent+-- with how shell performs escape processing.++-- Examples of quotes - "double quotes", 'single quotes', (parens), {braces},+-- ((nested) brackets).+--+-- Example:+--+-- >>> :{+-- >>> q x =+-- >>> case x of+-- >>> '"' -> Just x+-- >>> '\'' -> Just x+-- >>> _ -> Nothing+-- >>> :}+--+-- >>> p = Parser.wordKeepQuotes (== '\\') q isSpace Fold.toList+-- >>> Stream.parse p $ Stream.fromList "a\"b'c\";'d\"e'f ghi"+-- Right "a\"b'c\";'d\"e'f"+--+-- Note that outer quotes and backslashes from the input string are consumed by+-- Haskell, therefore, the actual input string passed to the parser is:+-- a"b'c";'d"e'f ghi+--+-- Similarly, when printing, double quotes are escaped by Haskell.+--+-- Limitations:+--+-- Shell like quote processing can be performed by using quote char specific+-- escape processing, single quotes with no escapes, and double quotes with+-- escapes.+--+-- JSON string processing can also be achieved except the "\uXXXX" style+-- escaping for Unicode characters.+--+{-# INLINE wordWithQuotes #-}+wordWithQuotes :: (Monad m, Eq a) =>+ Bool -- ^ Retain the quotes and escape chars in the output+ -> (a -> a -> Maybe a) -- ^ quote char -> escaped char -> translated char+ -> a -- ^ Matches an escape elem?+ -> (a -> Maybe a) -- ^ If left quote, return right quote, else Nothing.+ -> (a -> Bool) -- ^ Matches a word separator?+ -> Fold m a b+ -> Parser a m b+wordWithQuotes keepQuotes tr escChar toRight isSep+ (Fold fstep finitial _ ffinal) =+ Parser step initial extract++ where++ -- Can be used to generate parse error for a bracket end without a bracket+ -- begin.+ isInvalid = const False++ isEsc = (== escChar)++ initial = do+ res <- finitial+ return $+ case res of+ FL.Partial s -> IPartial (WordQuotedSkipPre s)+ FL.Done _ ->+ error "wordKeepQuotes: fold done without input"++ {-# INLINE processQuoted #-}+ processQuoted s a n ql qr = do+ res <- fstep s a+ return+ $ case res of+ FL.Partial s1 -> SContinue 1 (WordQuotedWord s1 n ql qr)+ FL.Done b -> SDone 1 b++ {-# INLINE processUnquoted #-}+ processUnquoted s a = do+ res <- fstep s a+ return+ $ case res of+ FL.Partial s1 -> SContinue 1 (WordUnquotedWord s1)+ FL.Done b -> SDone 1 b++ {-# INLINE checkRightQuoteAndProcess #-}+ checkRightQuoteAndProcess s a n ql qr+ | a == qr =+ if n == 1+ then if keepQuotes+ then processUnquoted s a+ else return $ SContinue 1 $ WordUnquotedWord s+ else processQuoted s a (n - 1) ql qr+ | a == ql = processQuoted s a (n + 1) ql qr+ | otherwise = processQuoted s a n ql qr++ step (WordQuotedSkipPre s) a+ | isEsc a = return $ SContinue 1 $ WordUnquotedEsc s+ | isSep a = return $ SPartial 1 $ WordQuotedSkipPre s+ | otherwise =+ case toRight a of+ Just qr ->+ if keepQuotes+ then processQuoted s a 1 a qr+ else return $ SContinue 1 $ WordQuotedWord s 1 a qr+ Nothing+ | isInvalid a ->+ return $ SError "wordKeepQuotes: invalid unquoted char"+ | otherwise -> processUnquoted s a+ step (WordUnquotedWord s) a+ | isEsc a = return $ SContinue 1 $ WordUnquotedEsc s+ | isSep a = do+ b <- ffinal s+ return $ SPartial 1 $ WordQuotedSkipPost b+ | otherwise = do+ case toRight a of+ Just qr ->+ if keepQuotes+ then processQuoted s a 1 a qr+ else return $ SContinue 1 $ WordQuotedWord s 1 a qr+ Nothing ->+ if isInvalid a+ then return $ SError "wordKeepQuotes: invalid unquoted char"+ else processUnquoted s a+ step (WordQuotedWord s n ql qr) a+ | isEsc a = return $ SContinue 1 $ WordQuotedEsc s n ql qr+ {-+ -- XXX Will this ever occur? Will n ever be 0?+ | n == 0 && isSep a = do+ b <- fextract s+ return $ SPartial 1 $ WordQuotedSkipPost b+ -}+ | otherwise = checkRightQuoteAndProcess s a n ql qr+ step (WordUnquotedEsc s) a = processUnquoted s a+ step (WordQuotedEsc s n ql qr) a =+ case tr ql a of+ Nothing -> do+ res <- fstep s escChar+ case res of+ FL.Partial s1 -> checkRightQuoteAndProcess s1 a n ql qr+ FL.Done b -> return $ SDone 1 b+ Just x -> processQuoted s x n ql qr+ step (WordQuotedSkipPost b) a =+ return+ $ if not (isSep a)+ then SDone 0 b+ else SPartial 1 $ WordQuotedSkipPost b++ err = return . FError++ extract (WordQuotedSkipPre s) = fmap (FDone 0) $ ffinal s+ extract (WordUnquotedWord s) = fmap (FDone 0) $ ffinal s+ extract (WordQuotedWord s n _ _) =+ if n == 0+ then fmap (FDone 0) $ ffinal s+ else err "wordWithQuotes: missing frame end"+ extract WordQuotedEsc {} =+ err "wordWithQuotes: trailing escape"+ extract (WordUnquotedEsc _) =+ err "wordWithQuotes: trailing escape"+ extract (WordQuotedSkipPost b) = return (FDone 0 b)++-- | 'wordWithQuotes' without processing the quotes and escape function+-- supplied to escape the quote char within a quote. Can be used to parse words+-- keeping the quotes and escapes intact.+--+-- >>> wordKeepQuotes = Parser.wordWithQuotes True (\_ _ -> Nothing)+--+{-# INLINE wordKeepQuotes #-}+wordKeepQuotes :: (Monad m, Eq a) =>+ a -- ^ Escape char+ -> (a -> Maybe a) -- ^ If left quote, return right quote, else Nothing.+ -> (a -> Bool) -- ^ Matches a word separator?+ -> Fold m a b+ -> Parser a m b+wordKeepQuotes =+ -- Escape the quote char itself+ wordWithQuotes True (\q x -> if q == x then Just x else Nothing)++-- See the "Quoting Rules" section in the "bash" manual page for a primer on+-- how quotes are used by shells.++-- | 'wordWithQuotes' with quote processing applied and escape function+-- supplied to escape the quote char within a quote. Can be ysed to parse words+-- and processing the quoting and escaping at the same time.+--+-- >>> wordProcessQuotes = Parser.wordWithQuotes False (\_ _ -> Nothing)+--+{-# INLINE wordProcessQuotes #-}+wordProcessQuotes :: (Monad m, Eq a) =>+ a -- ^ Escape char+ -> (a -> Maybe a) -- ^ If left quote, return right quote, else Nothing.+ -> (a -> Bool) -- ^ Matches a word separator?+ -> Fold m a b+ -> Parser a m b+wordProcessQuotes =+ -- Escape the quote char itself+ wordWithQuotes False (\q x -> if q == x then Just x else Nothing)++{-# ANN type GroupByState Fuse #-}+data GroupByState a s+ = GroupByInit !s+ | GroupByGrouping !a !s++-- | Given an input stream @[a,b,c,...]@ and a comparison function @cmp@, the+-- parser assigns the element @a@ to the first group, then if @a \`cmp` b@ is+-- 'True' @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 the parser is terminated. Each group is folded using the 'Fold' @f@ and+-- the result of the fold is the result of the parser.+--+-- * Stops - when the comparison fails.+-- * Fails - never.+--+-- >>> :{+-- runGroupsBy eq =+-- Stream.fold Fold.toList+-- . Stream.parseMany (Parser.groupBy eq Fold.toList)+-- . Stream.fromList+-- :}+--+-- >>> runGroupsBy (<) []+-- []+--+-- >>> runGroupsBy (<) [1]+-- [Right [1]]+--+-- >>> runGroupsBy (<) [3, 5, 4, 1, 2, 0]+-- [Right [3,5,4],Right [1,2],Right [0]]+--+{-# INLINE groupBy #-}+groupBy :: Monad m => (a -> a -> Bool) -> Fold m a b -> Parser a m b+groupBy eq (Fold fstep finitial _ ffinal) = Parser step initial extract++ where++ {-# INLINE grouper #-}+ grouper s a0 a = do+ res <- fstep s a+ return+ $ case res of+ FL.Done b -> SDone 1 b+ FL.Partial s1 -> SPartial 1 (GroupByGrouping a0 s1)++ initial = do+ res <- finitial+ return+ $ case res of+ FL.Partial s -> IPartial $ GroupByInit s+ FL.Done b -> IDone b++ step (GroupByInit s) a = grouper s a a+ step (GroupByGrouping a0 s) a =+ if eq a0 a+ then grouper s a0 a+ else SDone 0 <$> ffinal s++ extract (GroupByInit s) = fmap (FDone 0) $ ffinal s+ extract (GroupByGrouping _ s) = fmap (FDone 0) $ ffinal s++-- | Unlike 'groupBy' this combinator performs a rolling comparison of two+-- successive elements in the input stream. Assuming the input stream+-- is @[a,b,c,...]@ and the comparison function is @cmp@, the parser+-- first assigns the element @a@ to the first group, then if @a \`cmp` b@ is+-- 'True' @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 the parser is terminated. Each group is folded using the 'Fold' @f@ and+-- the result of the fold is the result of the parser.+--+-- * Stops - when the comparison fails.+-- * Fails - never.+--+-- >>> :{+-- runGroupsByRolling eq =+-- Stream.fold Fold.toList+-- . Stream.parseMany (Parser.groupByRolling eq Fold.toList)+-- . Stream.fromList+-- :}+--+-- >>> runGroupsByRolling (<) []+-- []+--+-- >>> runGroupsByRolling (<) [1]+-- [Right [1]]+--+-- >>> runGroupsByRolling (<) [3, 5, 4, 1, 2, 0]+-- [Right [3,5],Right [4],Right [1,2],Right [0]]+--+-- /Pre-release/+--+{-# INLINE groupByRolling #-}+groupByRolling :: Monad m => (a -> a -> Bool) -> Fold m a b -> Parser a m b+groupByRolling eq (Fold fstep finitial _ ffinal) = Parser step initial extract++ where++ {-# INLINE grouper #-}+ grouper s a = do+ res <- fstep s a+ return+ $ case res of+ FL.Done b -> SDone 1 b+ FL.Partial s1 -> SPartial 1 (GroupByGrouping a s1)++ initial = do+ res <- finitial+ return+ $ case res of+ FL.Partial s -> IPartial $ GroupByInit s+ FL.Done b -> IDone b++ step (GroupByInit s) a = grouper s a+ step (GroupByGrouping a0 s) a =+ if eq a0 a+ then grouper s a+ else SDone 0 <$> ffinal s++ extract (GroupByInit s) = fmap (FDone 0) $ ffinal s+ extract (GroupByGrouping _ s) = fmap (FDone 0) $ ffinal s++{-# ANN type GroupByStatePair Fuse #-}+data GroupByStatePair a s1 s2+ = GroupByInitPair !s1 !s2+ | GroupByGroupingPair !a !s1 !s2+ | GroupByGroupingPairL !a !s1 !s2+ | GroupByGroupingPairR !a !s1 !s2++-- | Like 'groupByRolling', but if the predicate is 'True' then collects using+-- the first fold as long as the predicate holds 'True', if the predicate is+-- 'False' collects using the second fold as long as it remains 'False'.+-- Returns 'Left' for the first case and 'Right' for the second case.+--+-- For example, if we want to detect sorted sequences in a stream, both+-- ascending and descending cases we can use 'groupByRollingEither (<=)+-- Fold.toList Fold.toList'.+--+-- /Pre-release/+{-# INLINE groupByRollingEither #-}+groupByRollingEither :: Monad m =>+ (a -> a -> Bool) -> Fold m a b -> Fold m a c -> Parser a m (Either b c)+groupByRollingEither+ eq+ (Fold fstep1 finitial1 _ ffinal1)+ (Fold fstep2 finitial2 _ ffinal2) = Parser step initial extract++ where++ {-# INLINE grouper #-}+ grouper s1 s2 a = do+ return $ SContinue 1 (GroupByGroupingPair a s1 s2)++ {-# INLINE grouperL2 #-}+ grouperL2 s1 s2 a = do+ res <- fstep1 s1 a+ return+ $ case res of+ FL.Done b -> SDone 1 (Left b)+ FL.Partial s11 -> SPartial 1 (GroupByGroupingPairL a s11 s2)++ {-# INLINE grouperL #-}+ grouperL s1 s2 a0 a = do+ res <- fstep1 s1 a0+ case res of+ FL.Done b -> return $ SDone 1 (Left b)+ FL.Partial s11 -> grouperL2 s11 s2 a++ {-# INLINE grouperR2 #-}+ grouperR2 s1 s2 a = do+ res <- fstep2 s2 a+ return+ $ case res of+ FL.Done b -> SDone 1 (Right b)+ FL.Partial s21 -> SPartial 1 (GroupByGroupingPairR a s1 s21)++ {-# INLINE grouperR #-}+ grouperR s1 s2 a0 a = do+ res <- fstep2 s2 a0+ case res of+ FL.Done b -> return $ SDone 1 (Right b)+ FL.Partial s21 -> grouperR2 s1 s21 a++ initial = do+ res1 <- finitial1+ res2 <- finitial2+ return+ $ case res1 of+ FL.Partial s1 ->+ case res2 of+ FL.Partial s2 -> IPartial $ GroupByInitPair s1 s2+ FL.Done b -> IDone (Right b)+ FL.Done b -> IDone (Left b)++ step (GroupByInitPair s1 s2) a = grouper s1 s2 a++ step (GroupByGroupingPair a0 s1 s2) a =+ if not (eq a0 a)+ then grouperL s1 s2 a0 a+ else grouperR s1 s2 a0 a++ step (GroupByGroupingPairL a0 s1 s2) a =+ if not (eq a0 a)+ then grouperL2 s1 s2 a+ else SDone 0 . Left <$> ffinal1 s1++ step (GroupByGroupingPairR a0 s1 s2) a =+ if eq a0 a+ then grouperR2 s1 s2 a+ else SDone 0 . Right <$> ffinal2 s2++ extract (GroupByInitPair s1 _) = FDone 0 . Left <$> ffinal1 s1+ extract (GroupByGroupingPairL _ s1 _) = FDone 0 . Left <$> ffinal1 s1+ extract (GroupByGroupingPairR _ _ s2) = FDone 0 . Right <$> ffinal2 s2+ extract (GroupByGroupingPair a s1 _) = do+ res <- fstep1 s1 a+ case res of+ FL.Done b -> return $ FDone 0 (Left b)+ FL.Partial s11 -> FDone 0 . Left <$> ffinal1 s11++-- XXX use an Unfold instead of a list?+-- XXX custom combinators for matching list, array and stream?+-- XXX rename to listBy?++-- | Match the given sequence of elements using the given comparison function.+-- Returns the original sequence if successful.+--+-- Definition:+--+-- >>> listEqBy cmp xs = Parser.streamEqBy cmp (Stream.fromList xs) *> Parser.fromPure xs+--+-- Examples:+--+-- >>> Stream.parse (Parser.listEqBy (==) "string") $ Stream.fromList "string"+-- Right "string"+--+-- >>> Stream.parsePos (Parser.listEqBy (==) "mismatch") $ Stream.fromList "match"+-- Left (ParseErrorPos 2 "streamEqBy: mismtach occurred")+--+{-# INLINE listEqBy #-}+listEqBy :: Monad m => (a -> a -> Bool) -> [a] -> Parser a m [a]+listEqBy cmp xs = streamEqByInternal cmp (D.fromList xs) *> fromPure xs+{-+listEqBy cmp str = Parser step initial extract++ where++ -- XXX Should return IDone in initial for [] case+ initial = return $ IPartial str++ step [] _ = return $ SDone 1 str+ step [x] a =+ return+ $ if x `cmp` a+ then SDone 1 str+ else SError "listEqBy: failed, yet to match the last element"+ step (x:xs) a =+ return+ $ if x `cmp` a+ then SContinue 1 xs+ else SError+ $ "listEqBy: failed, yet to match "+ ++ show (length xs + 1) ++ " elements"++ extract xs =+ return+ $ SError+ $ "listEqBy: end of input, yet to match "+ ++ show (length xs) ++ " elements"+-}++{-# INLINE streamEqByInternal #-}+streamEqByInternal :: Monad m => (a -> a -> Bool) -> D.Stream m a -> Parser a m ()+streamEqByInternal cmp (D.Stream sstep state) = Parser step initial extract++ where++ initial = do+ r <- sstep defState state+ case r of+ D.Yield x s -> return $ IPartial (Just' x, s)+ D.Stop -> return $ IDone ()+ -- Need Skip/Continue in initial to loop right here+ D.Skip s -> return $ IPartial (Nothing', s)++ step (Just' x, st) a =+ if x `cmp` a+ then do+ r <- sstep defState st+ return+ $ case r of+ D.Yield x1 s -> SContinue 1 (Just' x1, s)+ D.Stop -> SDone 1 ()+ D.Skip s -> SContinue 0 (Nothing', s)+ else return $ SError "streamEqBy: mismtach occurred"+ step (Nothing', st) a = do+ r <- sstep defState st+ return+ $ case r of+ D.Yield x s -> do+ if x `cmp` a+ then SContinue 1 (Nothing', s)+ else SError "streamEqBy: mismatch occurred"+ D.Stop -> SDone 0 ()+ D.Skip s -> SContinue 0 (Nothing', s)++ extract _ = return $ FError "streamEqBy: end of input"++-- | Like 'listEqBy' but uses a stream instead of a list and does not return+-- the stream.+--+{-# INLINE streamEqBy #-}+streamEqBy :: Monad m => (a -> a -> Bool) -> D.Stream m a -> Parser a m ()+-- XXX Somehow composing this with "*>" is much faster on the microbenchmark.+-- Need to investigate why.+streamEqBy cmp stream = streamEqByInternal cmp stream *> fromPure ()++-- Rename to "list".+-- | Match the input sequence with the supplied list and return it if+-- successful.+--+-- >>> listEq = Parser.listEqBy (==)+--+{-# INLINE listEq #-}+listEq :: (Monad m, Eq a) => [a] -> Parser a m [a]+listEq = listEqBy (==)++-- | Match if the input stream is a subsequence of the argument stream i.e. all+-- the elements of the input stream occur, in order, in the argument stream.+-- The elements do not have to occur consecutively. A sequence is considered a+-- subsequence of itself.+{-# INLINE subsequenceBy #-}+subsequenceBy :: -- Monad m =>+ (a -> a -> Bool) -> Stream m a -> Parser a m ()+subsequenceBy = undefined++{-+-- Should go in Data.Parser.Regex in streamly package so that it can depend on+-- regex backends.+{-# INLINE regexPosix #-}+regexPosix :: -- Monad m =>+ Regex -> Parser m a (Maybe (Array (MatchOffset, MatchLength)))+regexPosix = undefined++{-# INLINE regexPCRE #-}+regexPCRE :: -- Monad m =>+ Regex -> Parser m a (Maybe (Array (MatchOffset, MatchLength)))+regexPCRE = undefined+-}++-------------------------------------------------------------------------------+-- Transformations on input+-------------------------------------------------------------------------------++-- Initial needs a "Continue" constructor to implement scans on parsers. As a+-- parser can always return a Continue in initial when we feed the fold's+-- initial result to it. We can work this around for postscan by introducing an+-- initial state and calling "initial" only on the first input.++-- | Stateful scan on the input of a parser using a Fold.+--+-- /Unimplemented/+--+{-# INLINE postscan #-}+postscan :: -- Monad m =>+ Fold m a b -> Parser b m c -> Parser a m c+postscan = undefined++{-# INLINE zipWithM #-}+zipWithM :: Monad m =>+ (a -> b -> m c) -> D.Stream m a -> Fold m c x -> Parser b m x+zipWithM zf (D.Stream sstep state) (Fold fstep finitial _ ffinal) =+ Parser step initial extract++ where++ initial = do+ fres <- finitial+ case fres of+ FL.Partial fs -> do+ r <- sstep defState state+ case r of+ D.Yield x s -> return $ IPartial (Just' x, s, fs)+ D.Stop -> do+ x <- ffinal fs+ return $ IDone x+ -- Need Skip/Continue in initial to loop right here+ D.Skip s -> return $ IPartial (Nothing', s, fs)+ FL.Done x -> return $ IDone x++ step (Just' a, st, fs) b = do+ c <- zf a b+ fres <- fstep fs c+ case fres of+ FL.Partial fs1 -> do+ r <- sstep defState st+ case r of+ D.Yield x1 s -> return $ SContinue 1 (Just' x1, s, fs1)+ D.Stop -> do+ x <- ffinal fs1+ return $ SDone 1 x+ D.Skip s -> return $ SContinue 0 (Nothing', s, fs1)+ FL.Done x -> return $ SDone 1 x+ step (Nothing', st, fs) b = do+ r <- sstep defState st+ case r of+ D.Yield a s -> do+ c <- zf a b+ fres <- fstep fs c+ case fres of+ FL.Partial fs1 ->+ return $ SContinue 1 (Nothing', s, fs1)+ FL.Done x -> return $ SDone 1 x+ D.Stop -> do+ x <- ffinal fs+ return $ SDone 0 x+ D.Skip s -> return $ SContinue 0 (Nothing', s, fs)++ extract _ = return $ FError "zipWithM: end of input"++-- | Zip the input of a fold with a stream.+--+-- /Pre-release/+--+{-# INLINE zip #-}+zip :: Monad m => D.Stream m a -> Fold m (a, b) x -> Parser b m x+zip = zipWithM (curry return)++-- | Pair each element of a fold input with its index, starting from index 0.+--+-- /Pre-release/+{-# INLINE indexed #-}+indexed :: forall m a b. Monad m => Fold m (Int, a) b -> Parser a m b+indexed = zip (D.enumerateFromIntegral 0 :: D.Stream m Int)++-- | @makeIndexFilter indexer filter predicate@ generates a fold filtering+-- function using a fold indexing function that attaches an index to each input+-- element and a filtering function that filters using @(index, element) ->+-- Bool) as predicate.+--+-- For example:+--+-- @+-- filterWithIndex = makeIndexFilter indexed filter+-- filterWithAbsTime = makeIndexFilter timestamped filter+-- filterWithRelTime = makeIndexFilter timeIndexed filter+-- @+--+-- /Pre-release/+{-# INLINE makeIndexFilter #-}+makeIndexFilter ::+ (Fold m (s, a) b -> Parser a m b)+ -> (((s, a) -> Bool) -> Fold m (s, a) b -> Fold m (s, a) b)+ -> (((s, a) -> Bool) -> Fold m a b -> Parser a m b)+makeIndexFilter f comb g = f . comb g . FL.lmap snd++-- | @sampleFromthen offset stride@ samples the element at @offset@ index and+-- then every element at strides of @stride@.+--+-- /Pre-release/+{-# INLINE sampleFromthen #-}+sampleFromthen :: Monad m => Int -> Int -> Fold m a b -> Parser a m b+sampleFromthen offset size =+ makeIndexFilter indexed FL.filter (\(i, _) -> (i + offset) `mod` size == 0)++--------------------------------------------------------------------------------+--- Spanning+--------------------------------------------------------------------------------++-- | @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 = Stream.parse (Parser.span p Fold.toList Fold.toList) $ Stream.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],[])+--+-- @+--+-- /Pre-release/+{-# INLINE span #-}+span :: Monad m => (a -> Bool) -> Fold m a b -> Fold m a c -> Parser a m (b, c)+span p f1 f2 = noErrorUnsafeSplitWith (,) (takeWhile p f1) (fromFold f2)++-- | 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.+--+-- /Pre-release/+--+{-# INLINE spanBy #-}+spanBy ::+ Monad m+ => (a -> a -> Bool) -> Fold m a b -> Fold m a c -> Parser a m (b, c)+spanBy eq f1 f2 = noErrorUnsafeSplitWith (,) (groupBy eq f1) (fromFold f2)++-- | Like 'spanBy' but applies the predicate in a rolling fashion i.e.+-- predicate is applied to the previous and the next input elements.+--+-- /Pre-release/+{-# INLINE spanByRolling #-}+spanByRolling ::+ Monad m+ => (a -> a -> Bool) -> Fold m a b -> Fold m a c -> Parser a m (b, c)+spanByRolling eq f1 f2 =+ noErrorUnsafeSplitWith (,) (groupByRolling eq f1) (fromFold f2)++-------------------------------------------------------------------------------+-- nested parsers+-------------------------------------------------------------------------------++-- | Takes at-most @n@ input elements.+--+-- * Stops - when the collecting parser stops.+-- * Fails - when the collecting parser fails.+--+-- >>> Stream.parse (Parser.takeP 4 (Parser.takeEQ 2 Fold.toList)) $ Stream.fromList [1, 2, 3, 4, 5]+-- Right [1,2]+--+-- >>> Stream.parsePos (Parser.takeP 4 (Parser.takeEQ 5 Fold.toList)) $ Stream.fromList [1, 2, 3, 4, 5]+-- Left (ParseErrorPos 4 "takeEQ: Expecting exactly 5 elements, input terminated on 4")+--+-- /Internal/+{-# INLINE takeP #-}+takeP :: Monad m => Int -> Parser a m b -> Parser a m b+takeP lim (Parser pstep pinitial pextract) = Parser step initial extract++ where++ initial = do+ res <- pinitial+ case res of+ IPartial s ->+ if lim > 0+ then return $ IPartial $ Tuple' 0 s+ else iextract s+ IDone b -> return $ IDone b+ IError e -> return $ IError e++ step (Tuple' cnt r) a = do+ assertM(cnt < lim)+ res <- pstep r a+ case res of+ SPartial 1 s -> do+ let cnt1 = cnt + 1+ assertM(cnt1 >= 0)+ if cnt1 < lim+ then return $ SPartial 1 $ Tuple' cnt1 s+ else do+ r1 <- pextract s+ return $ case r1 of+ FDone n b -> SDone (n + 1) b+ FContinue n s1 -> SContinue (n + 1) (Tuple' (cnt1 + n) s1)+ FError err -> SError err++ SContinue 1 s -> do+ let cnt1 = cnt + 1+ assertM(cnt1 >= 0)+ if cnt1 < lim+ then return $ SContinue 1 $ Tuple' cnt1 s+ else do+ r1 <- pextract s+ return $ case r1 of+ FDone n b -> SDone (n + 1) b+ FContinue n s1 -> SContinue (n + 1) (Tuple' (cnt1 + n) s1)+ FError err -> SError err+ SPartial n s -> do+ let taken = cnt + n+ assertM(taken >= 0)+ return $ SPartial n $ Tuple' taken s+ SContinue n s -> do+ let taken = cnt + n+ assertM(taken >= 0)+ return $ SContinue n $ Tuple' taken s+ SDone n b -> return $ SDone n b+ SError str -> return $ SError str++ extract (Tuple' cnt r) = do+ r1 <- pextract r+ return $ case r1 of+ FDone n b -> FDone n b+ FContinue n s1 -> FContinue n (Tuple' (cnt + n) s1)+ FError err -> FError err++ -- XXX Need to make the Initial type Step to remove this+ iextract s = do+ r <- pextract s+ return $ case r of+ FDone _ b -> IDone b+ FError err -> IError err+ _ -> error "Bug: takeP invalid state in initial"++-- | Run a parser without consuming the input.+--+{-# INLINE lookAhead #-}+lookAhead :: Monad m => Parser a m b -> Parser a m b+lookAhead (Parser step1 initial1 _) = Parser step initial extract++ where++ initial = do+ res <- initial1+ return $ case res of+ IPartial s -> IPartial (Tuple'Fused 0 s)+ IDone b -> IDone b+ IError e -> IError e++ step (Tuple'Fused cnt st) a = do+ r <- step1 st a+ return+ $ case r of+ SPartial n s -> SContinue n (Tuple'Fused (cnt + n) s)+ SContinue n s -> SContinue n (Tuple'Fused (cnt + n) s)+ SDone _ b -> SDone (- cnt) b+ SError err -> SError err++ -- XXX returning an error let's us backtrack. To implement it in a way so+ -- that it terminates on eof without an error then we need a way to+ -- backtrack on eof, that will require extract to return 'Step' type.+ extract (Tuple'Fused n _) =+ return+ $ FError+ $ "lookAhead: end of input after consuming "+ ++ show n ++ " elements"++-------------------------------------------------------------------------------+-- Interleaving+-------------------------------------------------------------------------------+--+-- To deinterleave we can chain two parsers one behind the other. The input is+-- given to the first parser and the input definitively rejected by the first+-- parser is given to the second parser.+--+-- We can either have the parsers themselves buffer the input or use the shared+-- global buffer to hold it until none of the parsers need it. When the first+-- parser returns Skip (i.e. rewind) we let the second parser consume the+-- rejected input and when it is done we move the cursor forward to the first+-- parser again. This will require a "move forward" command as well.+--+-- To implement grep we can use three parsers, one to find the pattern, one+-- to store the context behind the pattern and one to store the context in+-- front of the pattern. When a match occurs we need to emit the accumulator of+-- all the three parsers. One parser can count the line numbers to provide the+-- line number info.++{-# ANN type DeintercalateAllState Fuse #-}+data DeintercalateAllState fs sp ss =+ DeintercalateAllInitL !fs+ | DeintercalateAllL !fs !sp+ | DeintercalateAllInitR !fs+ | DeintercalateAllR !fs !ss++-- XXX rename this to intercalate++-- Having deintercalateAll for accepting or rejecting entire input could be+-- useful. For example, in case of JSON parsing we get an entire block of+-- key-value pairs which we need to verify. This version may be simpler, more+-- efficient. We could implement this as a stream operation like parseMany.+--+-- XXX Also, it may be a good idea to provide a parse driver for a fold. For+-- example, in case of csv parsing as we are feeding a line to a fold we can+-- parse it.++-- | Like 'deintercalate' but the entire input must satisfy the pattern+-- otherwise the parser fails. This is many times faster than deintercalate.+--+-- >>> p1 = Parser.takeWhile1 (not . (== '+')) Fold.toList+-- >>> p2 = Parser.satisfy (== '+')+-- >>> p = Parser.deintercalateAll p1 p2 Fold.toList+-- >>> Stream.parse p $ Stream.fromList ""+-- Right []+-- >>> Stream.parse p $ Stream.fromList "1"+-- Right [Left "1"]+-- >>> Stream.parsePos p $ Stream.fromList "1+"+-- Left (ParseErrorPos 2 "takeWhile1: end of input")+-- >>> Stream.parse p $ Stream.fromList "1+2+3"+-- Right [Left "1",Right '+',Left "2",Right '+',Left "3"]+--+-- See also 'Streamly.Internal.Data.ParserK.chainl1'.+--+{-# INLINE deintercalateAll #-}+deintercalateAll :: Monad m =>+ Parser a m x+ -> Parser a m y+ -> Fold m (Either x y) z+ -> Parser a m z+deintercalateAll+ (Parser stepL initialL extractL)+ (Parser stepR initialR _)+ (Fold fstep finitial _ ffinal) = Parser step initial extract++ where++ errMsg p status =+ error $ "deintercalate: " ++ p ++ " parser cannot "+ ++ status ++ " without input"++ initial = do+ res <- finitial+ case res of+ FL.Partial fs -> return $ IPartial $ DeintercalateAllInitL fs+ FL.Done c -> return $ IDone c++ {-# INLINE processL #-}+ processL foldAction n nextState = do+ fres <- foldAction+ case fres of+ FL.Partial fs1 -> return $ SPartial n (nextState fs1)+ FL.Done c -> return $ SDone n c++ {-# INLINE runStepL #-}+ runStepL fs sL a = do+ r <- stepL sL a+ case r of+ SPartial n s -> return $ SPartial n (DeintercalateAllL fs s)+ SContinue n s -> return $ SContinue n (DeintercalateAllL fs s)+ SDone n b ->+ processL (fstep fs (Left b)) n DeintercalateAllInitR+ SError err -> return $ SError err++ {-# INLINE processR #-}+ processR foldAction n = do+ fres <- foldAction+ case fres of+ FL.Partial fs1 -> do+ res <- initialL+ case res of+ IPartial ps -> return $ SPartial n (DeintercalateAllL fs1 ps)+ IDone _ -> errMsg "left" "succeed"+ IError _ -> errMsg "left" "fail"+ FL.Done c -> return $ SDone n c++ {-# INLINE runStepR #-}+ runStepR fs sR a = do+ r <- stepR sR a+ case r of+ SPartial n s -> return $ SPartial n (DeintercalateAllR fs s)+ SContinue n s -> return $ SContinue n (DeintercalateAllR fs s)+ SDone n b -> processR (fstep fs (Right b)) n+ SError err -> return $ SError err++ step (DeintercalateAllInitL fs) a = do+ res <- initialL+ case res of+ IPartial s -> runStepL fs s a+ IDone _ -> errMsg "left" "succeed"+ IError _ -> errMsg "left" "fail"+ step (DeintercalateAllL fs sL) a = runStepL fs sL a+ step (DeintercalateAllInitR fs) a = do+ res <- initialR+ case res of+ IPartial s -> runStepR fs s a+ IDone _ -> errMsg "right" "succeed"+ IError _ -> errMsg "right" "fail"+ step (DeintercalateAllR fs sR) a = runStepR fs sR a++ {-# INLINE extractResult #-}+ extractResult n fs r = do+ res <- fstep fs r+ case res of+ FL.Partial fs1 -> fmap (FDone n) $ ffinal fs1+ FL.Done c -> return (FDone n c)+ extract (DeintercalateAllInitL fs) = fmap (FDone 0) $ ffinal fs+ extract (DeintercalateAllL fs sL) = do+ r <- extractL sL+ case r of+ FDone n b -> extractResult n fs (Left b)+ FError err -> return $ FError err+ FContinue n s -> return $ FContinue n (DeintercalateAllL fs s)+ extract (DeintercalateAllInitR fs) = fmap (FDone 0) $ ffinal fs+ extract (DeintercalateAllR _ _) =+ return $ FError "deintercalateAll: input ended at 'Right' value"++{-# ANN type DeintercalateState Fuse #-}+data DeintercalateState b fs sp ss =+ DeintercalateInitL !fs+ | DeintercalateL !Int !fs !sp+ | DeintercalateInitR !fs+ | DeintercalateR !Int !fs !ss+ | DeintercalateRL !Int !b !fs !sp++-- XXX Add tests that the next character that we take after running a parser is+-- correct. Especially for the parsers that maintain a count. In the stream+-- finished case (extract) as well as not finished case.++-- | Apply two parsers alternately to an input stream. The input stream is+-- considered an interleaving of two patterns. The two parsers represent the+-- two patterns. Parsing starts at the first parser and stops at the first+-- parser. It can be used to parse a infix style pattern e.g. p1 p2 p1 . Empty+-- input or single parse of the first parser is accepted.+--+-- >>> p1 = Parser.takeWhile1 (not . (== '+')) Fold.toList+-- >>> p2 = Parser.satisfy (== '+')+-- >>> p = Parser.deintercalate p1 p2 Fold.toList+-- >>> Stream.parse p $ Stream.fromList ""+-- Right []+-- >>> Stream.parse p $ Stream.fromList "1"+-- Right [Left "1"]+-- >>> Stream.parse p $ Stream.fromList "1+"+-- Right [Left "1"]+-- >>> Stream.parse p $ Stream.fromList "1+2+3"+-- Right [Left "1",Right '+',Left "2",Right '+',Left "3"]+--+-- See also 'Streamly.Internal.Data.ParserK.chainl1'.+--+{-# INLINE deintercalate #-}+deintercalate :: Monad m =>+ Parser a m x+ -> Parser a m y+ -> Fold m (Either x y) z+ -> Parser a m z+deintercalate+ (Parser stepL initialL extractL)+ (Parser stepR initialR _)+ (Fold fstep finitial _ ffinal) = Parser step initial extract++ where++ errMsg p status =+ error $ "deintercalate: " ++ p ++ " parser cannot "+ ++ status ++ " without input"++ initial = do+ res <- finitial+ case res of+ FL.Partial fs -> return $ IPartial $ DeintercalateInitL fs+ FL.Done c -> return $ IDone c++ {-# INLINE processL #-}+ processL foldAction n nextState = do+ fres <- foldAction+ case fres of+ FL.Partial fs1 -> return $ SPartial n (nextState fs1)+ FL.Done c -> return $ SDone n c++ {-# INLINE runStepL #-}+ runStepL cnt fs sL a = do+ r <- stepL sL a+ case r of+ -- XXX If we subtract instead of adding we do not need to negate+ -- when returning cnt.+ SPartial n s -> return $ SContinue n (DeintercalateL (cnt + n) fs s)+ SContinue n s -> return $ SContinue n (DeintercalateL (cnt + n) fs s)+ SDone n b ->+ processL (fstep fs (Left b)) n DeintercalateInitR+ SError _ -> do+ xs <- ffinal fs+ return $ SDone (-cnt) xs++ {-# INLINE processR #-}+ processR cnt b fs n = do+ res <- initialL+ case res of+ IPartial ps -> return $ SContinue n (DeintercalateRL cnt b fs ps)+ IDone _ -> errMsg "left" "succeed"+ IError _ -> errMsg "left" "fail"++ {-# INLINE runStepR #-}+ runStepR cnt fs sR a = do+ r <- stepR sR a+ case r of+ SPartial n s -> return $ SContinue n (DeintercalateR (cnt + n) fs s)+ SContinue n s -> return $ SContinue n (DeintercalateR (cnt + n) fs s)+ SDone n b -> processR (cnt + n) b fs n+ SError _ -> do+ xs <- ffinal fs+ return $ SDone (- cnt) xs++ step (DeintercalateInitL fs) a = do+ res <- initialL+ case res of+ IPartial s -> runStepL 0 fs s a+ IDone _ -> errMsg "left" "succeed"+ IError _ -> errMsg "left" "fail"+ step (DeintercalateL cnt fs sL) a = runStepL cnt fs sL a+ step (DeintercalateInitR fs) a = do+ res <- initialR+ case res of+ IPartial s -> runStepR 0 fs s a+ IDone _ -> errMsg "right" "succeed"+ IError _ -> errMsg "right" "fail"+ step (DeintercalateR cnt fs sR) a = runStepR cnt fs sR a+ step (DeintercalateRL cnt bR fs sL) a = do+ r <- stepL sL a+ case r of+ SPartial n s -> return $ SContinue n (DeintercalateRL (cnt + n) bR fs s)+ SContinue n s -> return $ SContinue n (DeintercalateRL (cnt + n) bR fs s)+ SDone n bL -> do+ res <- fstep fs (Right bR)+ case res of+ FL.Partial fs1 -> do+ fres <- fstep fs1 (Left bL)+ case fres of+ FL.Partial fs2 ->+ return $ SPartial n (DeintercalateInitR fs2)+ FL.Done c -> return $ SDone n c+ -- XXX We could have the fold accept pairs of (bR, bL)+ FL.Done _ -> error "Fold terminated consuming partial input"+ SError _ -> do+ xs <- ffinal fs+ return $ SDone (- cnt) xs++ {-# INLINE extractResult #-}+ extractResult n fs r = do+ res <- fstep fs r+ case res of+ FL.Partial fs1 -> fmap (FDone n) $ ffinal fs1+ FL.Done c -> return (FDone n c)++ extract (DeintercalateInitL fs) = fmap (FDone 0) $ ffinal fs+ extract (DeintercalateL cnt fs sL) = do+ r <- extractL sL+ case r of+ FDone n b -> extractResult n fs (Left b)+ FContinue n s -> return $ FContinue n (DeintercalateL (cnt + n) fs s)+ FError _ -> do+ xs <- ffinal fs+ return $ FDone (- cnt) xs+ extract (DeintercalateInitR fs) = fmap (FDone 0) $ ffinal fs+ extract (DeintercalateR cnt fs _) = fmap (FDone (- cnt)) $ ffinal fs+ extract (DeintercalateRL cnt bR fs sL) = do+ r <- extractL sL+ case r of+ FDone n bL -> do+ res <- fstep fs (Right bR)+ case res of+ FL.Partial fs1 -> extractResult n fs1 (Left bL)+ FL.Done _ -> error "Fold terminated consuming partial input"+ FContinue n s -> return $ FContinue n (DeintercalateRL (cnt + n) bR fs s)+ FError _ -> do+ xs <- ffinal fs+ return $ FDone (- cnt) xs++{-# ANN type Deintercalate1State Fuse #-}+data Deintercalate1State b fs sp ss =+ Deintercalate1InitL !Int !fs !sp+ | Deintercalate1InitR !fs+ | Deintercalate1R !Int !fs !ss+ | Deintercalate1RL !Int !b !fs !sp++-- | Apply two parsers alternately to an input stream. The input stream is+-- considered an interleaving of two patterns. The two parsers represent the+-- two patterns. Parsing starts at the first parser and stops at the first+-- parser. It can be used to parse a infix style pattern e.g. p1 p2 p1 . Empty+-- input or single parse of the first parser is accepted.+--+-- >>> p1 = Parser.takeWhile1 (not . (== '+')) Fold.toList+-- >>> p2 = Parser.satisfy (== '+')+-- >>> p = Parser.deintercalate1 p1 p2 Fold.toList+-- >>> Stream.parsePos p $ Stream.fromList ""+-- Left (ParseErrorPos 0 "takeWhile1: end of input")+-- >>> Stream.parse p $ Stream.fromList "1"+-- Right [Left "1"]+-- >>> Stream.parse p $ Stream.fromList "1+"+-- Right [Left "1"]+-- >>> Stream.parse p $ Stream.fromList "1+2+3"+-- Right [Left "1",Right '+',Left "2",Right '+',Left "3"]+--+{-# INLINE deintercalate1 #-}+deintercalate1 :: Monad m =>+ Parser a m x+ -> Parser a m y+ -> Fold m (Either x y) z+ -> Parser a m z+deintercalate1+ (Parser stepL initialL extractL)+ (Parser stepR initialR _)+ (Fold fstep finitial _ ffinal) = Parser step initial extract++ where++ errMsg p status =+ error $ "deintercalate: " ++ p ++ " parser cannot "+ ++ status ++ " without input"++ initial = do+ res <- finitial+ case res of+ FL.Partial fs -> do+ pres <- initialL+ case pres of+ IPartial s -> return $ IPartial $ Deintercalate1InitL 0 fs s+ IDone _ -> errMsg "left" "succeed"+ IError _ -> errMsg "left" "fail"+ FL.Done c -> return $ IDone c++ {-# INLINE processL #-}+ processL foldAction n nextState = do+ fres <- foldAction+ case fres of+ FL.Partial fs1 -> return $ SPartial n (nextState fs1)+ FL.Done c -> return $ SDone n c++ {-# INLINE runStepInitL #-}+ runStepInitL cnt fs sL a = do+ r <- stepL sL a+ case r of+ SPartial n s -> return $ SContinue n (Deintercalate1InitL (cnt + n) fs s)+ SContinue n s -> return $ SContinue n (Deintercalate1InitL (cnt + n) fs s)+ SDone n b ->+ processL (fstep fs (Left b)) n Deintercalate1InitR+ SError err -> return $ SError err++ {-# INLINE processR #-}+ processR cnt b fs n = do+ res <- initialL+ case res of+ IPartial ps -> return $ SContinue n (Deintercalate1RL cnt b fs ps)+ IDone _ -> errMsg "left" "succeed"+ IError _ -> errMsg "left" "fail"++ {-# INLINE runStepR #-}+ runStepR cnt fs sR a = do+ r <- stepR sR a+ case r of+ SPartial n s -> return $ SContinue n (Deintercalate1R (cnt + n) fs s)+ SContinue n s -> return $ SContinue n (Deintercalate1R (cnt + n) fs s)+ SDone n b -> processR (cnt + n) b fs n+ SError _ -> do+ xs <- ffinal fs+ return $ SDone (- cnt) xs++ step (Deintercalate1InitL cnt fs sL) a = runStepInitL cnt fs sL a+ step (Deintercalate1InitR fs) a = do+ res <- initialR+ case res of+ IPartial s -> runStepR 0 fs s a+ IDone _ -> errMsg "right" "succeed"+ IError _ -> errMsg "right" "fail"+ step (Deintercalate1R cnt fs sR) a = runStepR cnt fs sR a+ step (Deintercalate1RL cnt bR fs sL) a = do+ r <- stepL sL a+ case r of+ SPartial n s -> return $ SContinue n (Deintercalate1RL (cnt + n) bR fs s)+ SContinue n s -> return $ SContinue n (Deintercalate1RL (cnt + n) bR fs s)+ SDone n bL -> do+ res <- fstep fs (Right bR)+ case res of+ FL.Partial fs1 -> do+ fres <- fstep fs1 (Left bL)+ case fres of+ FL.Partial fs2 ->+ return $ SPartial n (Deintercalate1InitR fs2)+ FL.Done c -> return $ SDone n c+ -- XXX We could have the fold accept pairs of (bR, bL)+ FL.Done _ -> error "Fold terminated consuming partial input"+ SError _ -> do+ xs <- ffinal fs+ return $ SDone (- cnt) xs++ {-# INLINE extractResult #-}+ extractResult n fs r = do+ res <- fstep fs r+ case res of+ FL.Partial fs1 -> fmap (FDone n) $ ffinal fs1+ FL.Done c -> return (FDone n c)++ extract (Deintercalate1InitL cnt fs sL) = do+ r <- extractL sL+ case r of+ FDone n b -> extractResult n fs (Left b)+ FContinue n s -> return $ FContinue n (Deintercalate1InitL (cnt + n) fs s)+ FError err -> return $ FError err+ extract (Deintercalate1InitR fs) = fmap (FDone 0) $ ffinal fs+ extract (Deintercalate1R cnt fs _) = fmap (FDone (- cnt)) $ ffinal fs+ extract (Deintercalate1RL cnt bR fs sL) = do+ r <- extractL sL+ case r of+ FDone n bL -> do+ res <- fstep fs (Right bR)+ case res of+ FL.Partial fs1 -> extractResult n fs1 (Left bL)+ FL.Done _ -> error "Fold terminated consuming partial input"+ FContinue n s -> return $ FContinue n (Deintercalate1RL (cnt + n) bR fs s)+ FError _ -> do+ xs <- ffinal fs+ return $ FDone (- cnt) xs++{-# ANN type SepByState Fuse #-}+data SepByState fs sp ss =+ SepByInitL !fs+ | SepByL !Int !fs !sp+ | SepByInitR !fs+ | SepByR !Int !fs !ss++-- | Apply two parsers alternately to an input stream. Parsing starts at the+-- first parser and stops at the first parser. The output of the first parser+-- is emiited and the output of the second parser is discarded. It can be used+-- to parse a infix style pattern e.g. p1 p2 p1 . Empty input or single parse+-- of the first parser is accepted.+--+-- Definitions:+--+-- >>> sepBy p1 p2 f = Parser.deintercalate p1 p2 (Fold.catLefts f)+-- >>> sepBy p1 p2 f = Parser.sepBy1 p1 p2 f <|> Parser.fromEffect (Fold.finalM f)+--+-- Examples:+--+-- >>> p1 = Parser.takeWhile1 (not . (== '+')) Fold.toList+-- >>> p2 = Parser.satisfy (== '+')+-- >>> p = Parser.sepBy p1 p2 Fold.toList+-- >>> Stream.parse p $ Stream.fromList ""+-- Right []+-- >>> Stream.parse p $ Stream.fromList "1"+-- Right ["1"]+-- >>> Stream.parse p $ Stream.fromList "1+"+-- Right ["1"]+-- >>> Stream.parse p $ Stream.fromList "1+2+3"+-- Right ["1","2","3"]+--+{-# INLINE sepBy #-}+sepBy :: Monad m =>+ Parser a m b -> Parser a m x -> Fold m b c -> Parser a m c+-- This has similar performance as the custom impl below.+-- sepBy p1 p2 f = deintercalate p1 p2 (FL.catLefts f)+sepBy+ (Parser stepL initialL extractL)+ (Parser stepR initialR _)+ (Fold fstep finitial _ ffinal) = Parser step initial extract++ where++ errMsg p status =+ error $ "sepBy: " ++ p ++ " parser cannot "+ ++ status ++ " without input"++ initial = do+ res <- finitial+ case res of+ FL.Partial fs -> return $ IPartial $ SepByInitL fs+ FL.Done c -> return $ IDone c++ {-# INLINE processL #-}+ processL foldAction n nextState = do+ fres <- foldAction+ case fres of+ FL.Partial fs1 -> return $ SPartial n (nextState fs1)+ FL.Done c -> return $ SDone n c++ {-# INLINE runStepL #-}+ runStepL cnt fs sL a = do+ r <- stepL sL a+ case r of+ SPartial n s -> return $ SContinue n (SepByL (cnt + n) fs s)+ SContinue n s -> return $ SContinue n (SepByL (cnt + n) fs s)+ SDone n b ->+ processL (fstep fs b) n SepByInitR+ SError _ -> do+ xs <- ffinal fs+ return $ SDone (- cnt) xs++ {-# INLINE processR #-}+ processR cnt fs n = do+ res <- initialL+ case res of+ IPartial ps -> return $ SContinue n (SepByL cnt fs ps)+ IDone _ -> errMsg "left" "succeed"+ IError _ -> errMsg "left" "fail"++ {-# INLINE runStepR #-}+ runStepR cnt fs sR a = do+ r <- stepR sR a+ case r of+ SPartial n s -> return $ SContinue n (SepByR (cnt + n) fs s)+ SContinue n s -> return $ SContinue n (SepByR (cnt + n) fs s)+ SDone n _ -> processR (cnt + n) fs n+ SError _ -> do+ xs <- ffinal fs+ return $ SDone (- cnt) xs++ step (SepByInitL fs) a = do+ res <- initialL+ case res of+ IPartial s -> runStepL 0 fs s a+ IDone _ -> errMsg "left" "succeed"+ IError _ -> errMsg "left" "fail"+ step (SepByL cnt fs sL) a = runStepL cnt fs sL a+ step (SepByInitR fs) a = do+ res <- initialR+ case res of+ IPartial s -> runStepR 0 fs s a+ IDone _ -> errMsg "right" "succeed"+ IError _ -> errMsg "right" "fail"+ step (SepByR cnt fs sR) a = runStepR cnt fs sR a++ {-# INLINE extractResult #-}+ extractResult n fs r = do+ res <- fstep fs r+ case res of+ FL.Partial fs1 -> fmap (FDone n) $ ffinal fs1+ FL.Done c -> return (FDone n c)++ extract (SepByInitL fs) = fmap (FDone 0) $ ffinal fs+ extract (SepByL cnt fs sL) = do+ r <- extractL sL+ case r of+ FDone n b -> extractResult n fs b+ FContinue n s -> return $ FContinue n (SepByL (cnt + n) fs s)+ FError _ -> do+ xs <- ffinal fs+ return $ FDone (- cnt) xs+ extract (SepByInitR fs) = fmap (FDone 0) $ ffinal fs+ extract (SepByR cnt fs _) = fmap (FDone (- cnt)) $ ffinal fs++-- | Non-backtracking version of sepBy. Several times faster.+{-# INLINE sepByAll #-}+sepByAll :: Monad m =>+ Parser a m b -> Parser a m x -> Fold m b c -> Parser a m c+sepByAll p1 p2 f = deintercalateAll p1 p2 (FL.catLefts f)++-- XXX This can be implemented using refold, parse one and then continue+-- collecting the rest in that.++{-# ANN type SepBy1State Fuse #-}+data SepBy1State fs sp ss =+ SepBy1InitL !Int !fs sp+ | SepBy1L !Int !fs !sp+ | SepBy1InitR !fs+ | SepBy1R !Int !fs !ss++{-+{-# INLINE sepBy1 #-}+sepBy1 :: Monad m =>+ Parser a m b -> Parser a m x -> Fold m b c -> Parser a m c+sepBy1 p sep sink = do+ x <- p+ f <- fromEffect $ FL.reduce sink+ f1 <- fromEffect $ FL.snoc f x+ many (sep >> p) f1+-}++-- | Like 'sepBy' but requires at least one successful parse.+--+-- Definition:+--+-- >>> sepBy1 p1 p2 f = Parser.deintercalate1 p1 p2 (Fold.catLefts f)+--+-- Examples:+--+-- >>> p1 = Parser.takeWhile1 (not . (== '+')) Fold.toList+-- >>> p2 = Parser.satisfy (== '+')+-- >>> p = Parser.sepBy1 p1 p2 Fold.toList+-- >>> Stream.parsePos p $ Stream.fromList ""+-- Left (ParseErrorPos 0 "takeWhile1: end of input")+-- >>> Stream.parse p $ Stream.fromList "1"+-- Right ["1"]+-- >>> Stream.parse p $ Stream.fromList "1+"+-- Right ["1"]+-- >>> Stream.parse p $ Stream.fromList "1+2+3"+-- Right ["1","2","3"]+--+{-# INLINE sepBy1 #-}+sepBy1 :: Monad m =>+ Parser a m b -> Parser a m x -> Fold m b c -> Parser a m c+sepBy1+ (Parser stepL initialL extractL)+ (Parser stepR initialR _)+ (Fold fstep finitial _ ffinal) = Parser step initial extract++ where++ errMsg p status =+ error $ "sepBy: " ++ p ++ " parser cannot "+ ++ status ++ " without input"++ initial = do+ res <- finitial+ case res of+ FL.Partial fs -> do+ pres <- initialL+ case pres of+ IPartial s -> return $ IPartial $ SepBy1InitL 0 fs s+ IDone _ -> errMsg "left" "succeed"+ IError _ -> errMsg "left" "fail"+ FL.Done c -> return $ IDone c++ {-# INLINE processL #-}+ processL foldAction n nextState = do+ fres <- foldAction+ case fres of+ FL.Partial fs1 -> return $ SPartial n (nextState fs1)+ FL.Done c -> return $ SDone n c++ {-# INLINE runStepInitL #-}+ runStepInitL cnt fs sL a = do+ r <- stepL sL a+ case r of+ SPartial n s -> return $ SContinue n (SepBy1InitL (cnt + n) fs s)+ SContinue n s -> return $ SContinue n (SepBy1InitL (cnt + n) fs s)+ SDone n b ->+ processL (fstep fs b) n SepBy1InitR+ SError err -> return $ SError err++ {-# INLINE runStepL #-}+ runStepL cnt fs sL a = do+ r <- stepL sL a+ case r of+ SPartial n s -> return $ SContinue n (SepBy1L (cnt + n) fs s)+ SContinue n s -> return $ SContinue n (SepBy1L (cnt + n) fs s)+ SDone n b ->+ processL (fstep fs b) n SepBy1InitR+ SError _ -> do+ xs <- ffinal fs+ return $ SDone (- cnt) xs++ {-# INLINE processR #-}+ processR cnt fs n = do+ res <- initialL+ case res of+ IPartial ps -> return $ SContinue n (SepBy1L cnt fs ps)+ IDone _ -> errMsg "left" "succeed"+ IError _ -> errMsg "left" "fail"++ {-# INLINE runStepR #-}+ runStepR cnt fs sR a = do+ r <- stepR sR a+ case r of+ SPartial n s -> return $ SContinue n (SepBy1R (cnt + n) fs s)+ SContinue n s -> return $ SContinue n (SepBy1R (cnt + n) fs s)+ -- XXX review, need tests for sepBy1+ SDone n _ -> processR (cnt + n) fs n+ SError _ -> do+ xs <- ffinal fs+ return $ SDone (-cnt) xs++ step (SepBy1InitL cnt fs sL) a = runStepInitL cnt fs sL a+ step (SepBy1L cnt fs sL) a = runStepL cnt fs sL a+ step (SepBy1InitR fs) a = do+ res <- initialR+ case res of+ IPartial s -> runStepR 0 fs s a+ IDone _ -> errMsg "right" "succeed"+ IError _ -> errMsg "right" "fail"+ step (SepBy1R cnt fs sR) a = runStepR cnt fs sR a++ {-# INLINE extractResult #-}+ extractResult n fs r = do+ res <- fstep fs r+ case res of+ FL.Partial fs1 -> fmap (FDone n) $ ffinal fs1+ FL.Done c -> return (FDone n c)++ extract (SepBy1InitL cnt fs sL) = do+ r <- extractL sL+ case r of+ FDone n b -> extractResult n fs b+ FContinue n s -> return $ FContinue n (SepBy1InitL (cnt + n) fs s)+ FError err -> return $ FError err+ extract (SepBy1L cnt fs sL) = do+ r <- extractL sL+ case r of+ FDone n b -> extractResult n fs b+ FContinue n s -> return $ FContinue n (SepBy1L (cnt + n) fs s)+ FError _ -> do+ xs <- ffinal fs+ return $ FDone (- cnt) xs+ extract (SepBy1InitR fs) = fmap (FDone 0) $ ffinal fs+ extract (SepBy1R cnt fs _) = fmap (FDone (- cnt)) $ ffinal fs++-------------------------------------------------------------------------------+-- Interleaving a collection of parsers+-------------------------------------------------------------------------------+--+-- | Apply a collection of parsers to an input stream in a round robin fashion.+-- Each parser is applied until it stops and then we repeat starting with the+-- the first parser again.+--+-- /Unimplemented/+--+{-# INLINE roundRobin #-}+roundRobin :: -- (Foldable t, Monad m) =>+ t (Parser a m b) -> Fold m b c -> Parser a m c+roundRobin _ps _f = undefined++-------------------------------------------------------------------------------+-- Sequential Collection+-------------------------------------------------------------------------------++-- | @sequence f p@ collects sequential parses of parsers in a+-- serial stream @p@ using the fold @f@. Fails if the input ends or any+-- of the parsers fail.+--+-- /Pre-release/+--+{-# INLINE sequence #-}+sequence :: Monad m =>+ D.Stream m (Parser a m b) -> Fold m b c -> Parser a m c+sequence (D.Stream sstep sstate) (Fold fstep finitial _ ffinal) =+ Parser step initial extract++ where++ initial = do+ fres <- finitial+ case fres of+ FL.Partial fs -> return $ IPartial (Nothing', sstate, fs)+ FL.Done c -> return $ IDone c++ -- state does not contain any parser+ -- yield a new parser from the stream+ step (Nothing', ss, fs) _ = do+ sres <- sstep defState ss+ case sres of+ D.Yield p ss1 -> return $ SContinue 0 (Just' p, ss1, fs)+ D.Stop -> do+ c <- ffinal fs+ return $ SDone 0 c+ D.Skip ss1 -> return $ SContinue 0 (Nothing', ss1, fs)++ -- state holds a parser that may or may not have been+ -- initialized. pinit holds the initial parser state+ -- or modified parser state respectively+ step (Just' (Parser pstep pinit pextr), ss, fs) a = do+ ps <- pinit+ case ps of+ IPartial ps1 -> do+ pres <- pstep ps1 a+ case pres of+ SPartial n ps2 ->+ let newP =+ Just' $ Parser pstep (return $ IPartial ps2) pextr+ in return $ SPartial n (newP, ss, fs)+ SContinue n ps2 ->+ let newP =+ Just' $ Parser pstep (return $ IPartial ps2) pextr+ in return $ SContinue n (newP, ss, fs)+ SDone n b -> do+ fres <- fstep fs b+ case fres of+ FL.Partial fs1 ->+ return $ SPartial n (Nothing', ss, fs1)+ FL.Done c -> return $ SDone n c+ SError msg -> return $ SError msg+ IDone b -> do+ fres <- fstep fs b+ case fres of+ FL.Partial fs1 ->+ return $ SPartial 0 (Nothing', ss, fs1)+ FL.Done c -> return $ SDone 0 c+ IError err -> return $ SError err++ extract (Nothing', _, fs) = fmap (FDone 0) $ ffinal fs+ extract (Just' (Parser pstep pinit pextr), ss, fs) = do+ ps <- pinit+ case ps of+ IPartial ps1 -> do+ r <- pextr ps1+ case r of+ FDone n b -> do+ res <- fstep fs b+ case res of+ FL.Partial fs1 -> fmap (FDone n) $ ffinal fs1+ FL.Done c -> return (FDone n c)+ FError err -> return $ FError err+ FContinue n s -> return $ FContinue n (Just' (Parser pstep (return (IPartial s)) pextr), ss, fs)+ IDone b -> do+ fres <- fstep fs b+ case fres of+ FL.Partial fs1 -> fmap (FDone 0) $ ffinal fs1+ FL.Done c -> return (FDone 0 c)+ IError err -> return $ FError err++-------------------------------------------------------------------------------+-- Alternative Collection+-------------------------------------------------------------------------------++{-+-- | @choice parsers@ applies the @parsers@ in order and returns the first+-- successful parse.+--+-- This is same as 'asum' but more efficient.+--+-- /Broken/+--+{-# INLINE choice #-}+choice :: (MonadCatch m, Foldable t) => t (Parser a m b) -> Parser a m b+choice = foldl1 shortest+-}++-------------------------------------------------------------------------------+-- Sequential Repetition+-------------------------------------------------------------------------------++-- | Like 'many' but uses a 'Parser' instead of a 'Fold' to collect the+-- results. Parsing stops or fails if the collecting parser stops or fails.+--+-- /Unimplemented/+--+{-# INLINE manyP #-}+manyP :: -- MonadCatch m =>+ Parser a m b -> Parser b m c -> Parser a m c+manyP _p _f = undefined++-- | Collect zero or more parses. Apply the supplied parser repeatedly on the+-- input stream and push the parse results to a downstream fold.+--+-- Stops: when the downstream fold stops or the parser fails.+-- Fails: never, produces zero or more results.+--+-- >>> many = Parser.countBetween 0 maxBound+--+-- Compare with 'Control.Applicative.many'.+--+{-# INLINE many #-}+many :: Monad m => Parser a m b -> Fold m b c -> Parser a m c+many = splitMany+-- many = countBetween 0 maxBound++-- Note: many1 would perhaps be a better name for this and consistent with+-- other names like takeWhile1. But we retain the name "some" for+-- compatibility.++-- | Collect one or more parses. Apply the supplied parser repeatedly on the+-- input stream and push the parse results to a downstream fold.+--+-- Stops: when the downstream fold stops or the parser fails.+-- Fails: if it stops without producing a single result.+--+-- >>> some p f = Parser.manyP p (Parser.takeGE 1 f)+-- >>> some = Parser.countBetween 1 maxBound+--+-- Compare with 'Control.Applicative.some'.+--+{-# INLINE some #-}+some :: Monad m => Parser a m b -> Fold m b c -> Parser a m c+some = splitSome+-- some p f = manyP p (takeGE 1 f)+-- some = countBetween 1 maxBound++-- | @countBetween m n f p@ collects between @m@ and @n@ sequential parses of+-- parser @p@ using the fold @f@. Stop after collecting @n@ results. Fails if+-- the input ends or the parser fails before @m@ results are collected.+--+-- >>> countBetween m n p f = Parser.manyP p (Parser.takeBetween m n f)+--+-- /Unimplemented/+--+{-# INLINE countBetween #-}+countBetween :: -- MonadCatch m =>+ Int -> Int -> Parser a m b -> Fold m b c -> Parser a m c+countBetween _m _n _p = undefined+-- countBetween m n p f = manyP p (takeBetween m n f)++-- | @count n f p@ collects exactly @n@ sequential parses of parser @p@ using+-- the fold @f@. Fails if the input ends or the parser fails before @n@+-- results are collected.+--+-- >>> count n = Parser.countBetween n n+-- >>> count n p f = Parser.manyP p (Parser.takeEQ n f)+--+-- /Unimplemented/+--+{-# INLINE count #-}+count :: -- MonadCatch m =>+ Int -> Parser a m b -> Fold m b c -> Parser a m c+count n = countBetween n n+-- count n p f = manyP p (takeEQ n f)++-- | Like 'manyTill' but uses a 'Parser' to collect the results instead of a+-- 'Fold'. Parsing stops or fails if the collecting parser stops or fails.+--+-- We can implemnent parsers like the following using 'manyTillP':+--+-- @+-- countBetweenTill m n f p = manyTillP (takeBetween m n f) p+-- @+--+-- /Unimplemented/+--+{-# INLINE manyTillP #-}+manyTillP :: -- Monad m =>+ Parser a m b -> Parser a m x -> Parser b m c -> Parser a m c+manyTillP _p1 _p2 _f = undefined+ -- D.toParserK $ D.manyTillP (D.fromParserK p1) (D.fromParserK p2) f++{-# ANN type ManyTillState Fuse #-}+data ManyTillState fs sr sl+ = ManyTillR !Int !fs !sr+ | ManyTillL !fs !sl++-- | @manyTill p test f@ tries the parser @test@ on the input, if @test@+-- fails it backtracks and tries @p@, after @p@ succeeds @test@ is+-- tried again and so on. The parser stops when @test@ succeeds. The output of+-- @test@ is discarded and the output of @p@ is accumulated by the+-- supplied fold. The parser fails if @p@ fails.+--+-- Stops when the fold @f@ stops.+--+{-# INLINE manyTill #-}+manyTill :: Monad m+ => Parser a m b -> Parser a m x -> Fold m b c -> Parser a m c+manyTill (Parser stepL initialL extractL)+ (Parser stepR initialR _)+ (Fold fstep finitial _ ffinal) =+ Parser step initial extract++ where++ -- Caution: Mutual recursion++ scrutL fs p c d e = do+ resL <- initialL+ case resL of+ IPartial sl -> return $ c (ManyTillL fs sl)+ IDone bl -> do+ fr <- fstep fs bl+ case fr of+ FL.Partial fs1 -> scrutR fs1 p c d e+ FL.Done fb -> return $ d fb+ IError err -> return $ e err++ scrutR fs p c d e = do+ resR <- initialR+ case resR of+ IPartial sr -> return $ p (ManyTillR 0 fs sr)+ IDone _ -> d <$> ffinal fs+ IError _ -> scrutL fs p c d e++ initial = do+ res <- finitial+ case res of+ FL.Partial fs -> scrutR fs IPartial IPartial IDone IError+ FL.Done b -> return $ IDone b++ step (ManyTillR cnt fs st) a = do+ r <- stepR st a+ case r of+ SPartial n s -> return $ SPartial n (ManyTillR 0 fs s)+ SContinue n s -> do+ assertM(cnt + n >= 0)+ return $ SContinue n (ManyTillR (cnt + n) fs s)+ SDone n _ -> do+ b <- ffinal fs+ return $ SDone n b+ SError _ -> do+ resL <- initialL+ case resL of+ IPartial sl ->+ return $ SContinue (negate cnt) (ManyTillL fs sl)+ IDone bl -> do+ fr <- fstep fs bl+ -- XXX review, need tests for manyTill+ case fr of+ FL.Partial fs1 ->+ scrutR+ fs1+ (SPartial (-cnt))+ (SContinue (-cnt))+ (SDone (-cnt))+ SError+ FL.Done fb -> return $ SDone (-cnt) fb+ IError err -> return $ SError err+ step (ManyTillL fs st) a = do+ r <- stepL st a+ case r of+ SPartial n s -> return $ SPartial n (ManyTillL fs s)+ SContinue n s -> return $ SContinue n (ManyTillL fs s)+ SDone n b -> do+ fs1 <- fstep fs b+ case fs1 of+ FL.Partial s ->+ scrutR s (SPartial n) (SContinue n) (SDone n) SError+ FL.Done b1 -> return $ SDone n b1+ SError err -> return $ SError err++ extract (ManyTillL fs sR) = do+ res <- extractL sR+ case res of+ FDone n b -> do+ r <- fstep fs b+ case r of+ FL.Partial fs1 -> fmap (FDone n) $ ffinal fs1+ FL.Done c -> return (FDone n c)+ FError err -> return $ FError err+ FContinue n s -> return $ FContinue n (ManyTillL fs s)+ extract (ManyTillR _ fs _) = fmap (FDone 0) $ ffinal fs++-- | @manyThen f collect recover@ repeats the parser @collect@ on the input and+-- collects the output in the supplied fold. If the the parser @collect@ fails,+-- parser @recover@ is run until it stops and then we start repeating the+-- parser @collect@ again. The parser fails if the recovery parser fails.+--+-- For example, this can be used to find a key frame in a video stream after an+-- error.+--+-- /Unimplemented/+--+{-# INLINE manyThen #-}+manyThen :: -- (Foldable t, Monad m) =>+ Parser a m b -> Parser a m x -> Fold m b c -> Parser a m c+manyThen _parser _recover _f = undefined++-------------------------------------------------------------------------------+-- Repeated Alternatives+-------------------------------------------------------------------------------++-- | Keep trying a parser up to a maximum of @n@ failures. When the parser+-- fails the input consumed till now is dropped and the new instance is tried+-- on the fresh input.+--+-- /Unimplemented/+--+{-# INLINE retryMaxTotal #-}+retryMaxTotal :: -- (Monad m) =>+ Int -> Parser a m b -> Fold m b c -> Parser a m c+retryMaxTotal _n _p _f = undefined++-- | Like 'retryMaxTotal' but aborts after @n@ successive failures.+--+-- /Unimplemented/+--+{-# INLINE retryMaxSuccessive #-}+retryMaxSuccessive :: -- (Monad m) =>+ Int -> Parser a m b -> Fold m b c -> Parser a m c+retryMaxSuccessive _n _p _f = undefined++-- | Keep trying a parser until it succeeds. When the parser fails the input+-- consumed till now is dropped and the new instance is tried on the fresh+-- input.+--+-- /Unimplemented/+--+{-# INLINE retry #-}+retry :: -- (Monad m) =>+ Parser a m b -> Parser a m b+retry _p = undefined
− src/Streamly/Internal/Data/Parser/ParserD.hs
@@ -1,3629 +0,0 @@-{-# LANGUAGE CPP #-}--- |--- Module : Streamly.Internal.Data.Parser.ParserD--- Copyright : (c) 2020 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC--module Streamly.Internal.Data.Parser.ParserD- (- -- * Setup- -- $setup-- -- * Types- Parser (..)- , ParseError (..)- , Step (..)- , Initial (..)-- -- * Downgrade to Fold- , toFold-- -- First order parsers- -- * Accumulators- , fromFold- , fromFoldMaybe- , fromPure- , fromEffect- , die- , dieM-- -- * Map on input- , lmap- , lmapM- , postscan- , filter-- -- * Map on output- , rmapM-- -- * Element parsers- , peek-- -- All of these can be expressed in terms of either- , one- , oneEq- , oneNotEq- , oneOf- , noneOf- , eof- , satisfy- , maybe- , either-- -- * Sequence parsers (tokenizers)- --- -- | Parsers chained in series, if one parser terminates the composition- -- terminates. Currently we are using folds to collect the output of the- -- parsers but we can use Parsers instead of folds to make the composition- -- more powerful. For example, we can do:- --- -- takeEndByOrMax cond n p = takeEndBy cond (take n p)- -- takeEndByBetween cond m n p = takeEndBy cond (takeBetween m n p)- -- takeWhileBetween cond m n p = takeWhile cond (takeBetween m n p)- , lookAhead-- -- ** By length- -- | Grab a sequence of input elements without inspecting them- , takeBetween- -- , take -- takeBetween 0 n- , takeEQ -- takeBetween n n- , takeGE -- takeBetween n maxBound- -- , takeGE1 -- take1 -- takeBetween 1 n- , takeP-- -- Grab a sequence of input elements by inspecting them- -- ** Exact match- , listEq- , listEqBy- , streamEqBy- , subsequenceBy-- -- ** By predicate- , takeWhile- , takeWhileP- , takeWhile1- , dropWhile-- -- ** Separated by elements- -- | Separator could be in prefix postion ('takeStartBy'), or suffix- -- position ('takeEndBy'). See 'deintercalate', 'sepBy' etc for infix- -- separator parsing, also see 'intersperseQuotedBy' fold.-- -- These can be implemented modularly with refolds, using takeWhile and- -- satisfy.- , takeEndBy- , takeEndBy_- , takeEndByEsc- -- , takeEndByEsc_- , takeStartBy- , takeStartBy_- , takeEitherSepBy- , wordBy-- -- ** Grouped by element comparison- , groupBy- , groupByRolling- , groupByRollingEither-- -- ** Framed by elements- -- | Also see 'intersperseQuotedBy' fold.- -- Framed by a one or more ocurrences of a separator around a word like- -- spaces or quotes. No nesting.- , wordFramedBy -- XXX Remove this? Covered by wordWithQuotes?- , wordWithQuotes- , wordKeepQuotes- , wordProcessQuotes-- -- Framed by separate start and end characters, potentially nested.- -- blockWithQuotes allows quotes inside a block. However,- -- takeFramedByGeneric can be used to express takeStartBy, takeEndBy and- -- block with escaping.- -- , takeFramedBy- , takeFramedBy_- , takeFramedByEsc_- , takeFramedByGeneric- , blockWithQuotes-- -- Matching strings- -- , prefixOf -- match any prefix of a given string- -- , suffixOf -- match any suffix of a given string- -- , infixOf -- match any substring of a given string-- -- ** Spanning- , span- , spanBy- , spanByRolling-- -- Second order parsers (parsers using parsers)- -- * Binary Combinators-- -- ** Sequential Applicative- , splitWith- , split_-- {-- -- ** Parallel Applicatives- , teeWith- , teeWithFst- , teeWithMin- -- , teeTill -- like manyTill but parallel- -}-- -- ** Sequential Alternative- , alt-- {-- -- ** Parallel Alternatives- , shortest- , longest- -- , fastest- -}-- -- * N-ary Combinators- -- ** Sequential Collection- , sequence- , concatMap-- -- ** Sequential Repetition- , count- , countBetween- -- , countBetweenTill- , manyP- , many- , some-- -- ** Interleaved Repetition- -- Use two folds, run a primary parser, its rejected values go to the- -- secondary parser.- , deintercalate- , deintercalate1- , deintercalateAll- -- , deintercalatePrefix- -- , deintercalateSuffix-- -- *** Special cases- -- | TODO: traditional implmentations of these may be of limited use. For- -- example, consider parsing lines separated by @\\r\\n@. The main parser- -- will have to detect and exclude the sequence @\\r\\n@ anyway so that we- -- can apply the "sep" parser.- --- -- We can instead implement these as special cases of deintercalate.- --- -- @- -- , endBy- -- , sepEndBy- -- , beginBy- -- , sepBeginBy- -- , sepAroundBy- -- @- , sepBy1- , sepBy- , sepByAll-- , manyTillP- , manyTill- , manyThen-- -- -- * Distribution- --- -- A simple and stupid impl would be to just convert the stream to an array- -- and give the array reference to all consumers. The array can be grown on- -- demand by any consumer and truncated when nonbody needs it.- --- -- -- ** Distribute to collection- -- -- ** Distribute to repetition-- -- ** Interleaved collection- -- |- --- -- 1. Round robin- -- 2. Priority based- , roundRobin-- -- -- ** Interleaved repetition- -- repeat one parser and when it fails run an error recovery parser- -- e.g. to find a key frame in the stream after an error-- -- ** Collection of Alternatives- -- | Unimplemented- --- -- @- -- , shortestN- -- , longestN- -- , fastestN -- first N successful in time- -- , choiceN -- first N successful in position- -- @- -- , choice -- first successful in position-- -- ** Repeated Alternatives- , retryMaxTotal- , retryMaxSuccessive- , retry-- -- ** Zipping Input- , zipWithM- , zip- , indexed- , makeIndexFilter- , sampleFromthen-- -- * Deprecated- , next- )-where--#include "inline.hs"-#include "assert.hs"--import Control.Monad (when)-import Data.Bifunctor (first)-import Fusion.Plugin.Types (Fuse(..))-import Streamly.Internal.Data.Fold.Type (Fold(..))-import Streamly.Internal.Data.SVar.Type (defState)-import Streamly.Internal.Data.Either.Strict (Either'(..))-import Streamly.Internal.Data.Maybe.Strict (Maybe'(..))-import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))-import Streamly.Internal.Data.Stream.StreamD.Type (Stream)--import qualified Data.Foldable as Foldable-import qualified Streamly.Internal.Data.Fold.Type as FL-import qualified Streamly.Internal.Data.Stream.StreamD.Type as D-import qualified Streamly.Internal.Data.Stream.StreamD.Generate as D--import Prelude hiding- (any, all, take, takeWhile, sequence, concatMap, maybe, either, span- , zip, filter, dropWhile)--- import Streamly.Internal.Data.Parser.ParserD.Tee-import Streamly.Internal.Data.Parser.ParserD.Type--#include "DocTestDataParser.hs"------------------------------------------------------------------------------------ Downgrade a parser to a Fold------------------------------------------------------------------------------------ | Make a 'Fold' from a 'Parser'. The fold just throws an exception if the--- parser fails or tries to backtrack.------ This can be useful in combinators that accept a Fold and we know that a--- Parser cannot fail or failure exception is acceptable as there is no way to--- recover.------ /Pre-release/----{-# INLINE toFold #-}-toFold :: Monad m => Parser a m b -> Fold m a b-toFold (Parser pstep pinitial pextract) = Fold step initial extract-- where-- initial = do- r <- pinitial- case r of- IPartial s -> return $ FL.Partial s- IDone b -> return $ FL.Done b- IError err ->- error $ "toFold: parser throws error in initial" ++ err-- perror n = error $ "toFold: parser backtracks in Partial: " ++ show n- cerror n = error $ "toFold: parser backtracks in Continue: " ++ show n- derror n = error $ "toFold: parser backtracks in Done: " ++ show n- eerror err = error $ "toFold: parser throws error: " ++ err-- step st a = do- r <- pstep st a- case r of- Partial 0 s -> return $ FL.Partial s- Continue 0 s -> return $ FL.Partial s- Done 0 b -> return $ FL.Done b- Partial n _ -> perror n- Continue n _ -> cerror n- Done n _ -> derror n- Error err -> eerror err-- extract st = do- r <- pextract st- case r of- Done 0 b -> return b- Partial n _ -> perror n- Continue n _ -> cerror n- Done n _ -> derror n- Error err -> eerror err------------------------------------------------------------------------------------ Upgrade folds to parses------------------------------------------------------------------------------------ | Make a 'Parser' from a 'Fold'. This parser sends all of its input to the--- fold.----{-# INLINE fromFold #-}-fromFold :: Monad m => Fold m a b -> Parser a m b-fromFold (Fold fstep finitial fextract) = Parser step initial extract-- where-- initial = do- res <- finitial- return- $ case res of- FL.Partial s1 -> IPartial s1- FL.Done b -> IDone b-- step s a = do- res <- fstep s a- return- $ case res of- FL.Partial s1 -> Partial 0 s1- FL.Done b -> Done 0 b-- extract = fmap (Done 0) . fextract---- | Convert a Maybe returning fold to an error returning parser. The first--- argument is the error message that the parser would return when the fold--- returns Nothing.------ /Pre-release/----{-# INLINE fromFoldMaybe #-}-fromFoldMaybe :: Monad m => String -> Fold m a (Maybe b) -> Parser a m b-fromFoldMaybe errMsg (Fold fstep finitial fextract) =- Parser step initial extract-- where-- initial = do- res <- finitial- return- $ case res of- FL.Partial s1 -> IPartial s1- FL.Done b ->- case b of- Just x -> IDone x- Nothing -> IError errMsg-- step s a = do- res <- fstep s a- return- $ case res of- FL.Partial s1 -> Partial 0 s1- FL.Done b ->- case b of- Just x -> Done 0 x- Nothing -> Error errMsg-- extract s = do- res <- fextract s- case res of- Just x -> return $ Done 0 x- Nothing -> return $ Error errMsg------------------------------------------------------------------------------------ Failing Parsers------------------------------------------------------------------------------------ | Peek the head element of a stream, without consuming it. Fails if it--- encounters end of input.------ >>> Stream.parse ((,) <$> Parser.peek <*> Parser.satisfy (> 0)) $ Stream.fromList [1]--- Right (1,1)------ @--- peek = lookAhead (satisfy True)--- @----{-# INLINE peek #-}-peek :: Monad m => Parser a m a-peek = Parser step initial extract-- where-- initial = return $ IPartial ()-- step () a = return $ Done 1 a-- extract () = return $ Error "peek: end of input"---- | Succeeds if we are at the end of input, fails otherwise.------ >>> Stream.parse ((,) <$> Parser.satisfy (> 0) <*> Parser.eof) $ Stream.fromList [1]--- Right (1,())----{-# INLINE eof #-}-eof :: Monad m => Parser a m ()-eof = Parser step initial extract-- where-- initial = return $ IPartial ()-- step () _ = return $ Error "eof: not at end of input"-- extract () = return $ Done 0 ()---- | Return the next element of the input. Returns 'Nothing'--- on end of input. Also known as 'head'.------ /Pre-release/----{-# DEPRECATED next "Please use \"fromFold Fold.one\" instead" #-}-{-# INLINE next #-}-next :: Monad m => Parser a m (Maybe a)-next = Parser step initial extract-- where-- initial = pure $ IPartial ()-- step () a = pure $ Done 0 (Just a)-- extract () = pure $ Done 0 Nothing---- | Map an 'Either' returning function on the next element in the stream. If--- the function returns 'Left err', the parser fails with the error message--- @err@ otherwise returns the 'Right' value.------ /Pre-release/----{-# INLINE either #-}-either :: Monad m => (a -> Either String b) -> Parser a m b-either f = Parser step initial extract-- where-- initial = return $ IPartial ()-- step () a = return $- case f a of- Right b -> Done 0 b- Left err -> Error err-- extract () = return $ Error "end of input"---- | Map a 'Maybe' returning function on the next element in the stream. The--- parser fails if the function returns 'Nothing' otherwise returns the 'Just'--- value.------ >>> toEither = Maybe.maybe (Left "maybe: predicate failed") Right--- >>> maybe f = Parser.either (toEither . f)------ >>> maybe f = Parser.fromFoldMaybe "maybe: predicate failed" (Fold.maybe f)------ /Pre-release/----{-# INLINE maybe #-}-maybe :: Monad m => (a -> Maybe b) -> Parser a m b--- maybe f = either (Maybe.maybe (Left "maybe: predicate failed") Right . f)-maybe parserF = Parser step initial extract-- where-- initial = return $ IPartial ()-- step () a = return $- case parserF a of- Just b -> Done 0 b- Nothing -> Error "maybe: predicate failed"-- extract () = return $ Error "maybe: end of input"---- | Returns the next element if it passes the predicate, fails otherwise.------ >>> Stream.parse (Parser.satisfy (== 1)) $ Stream.fromList [1,0,1]--- Right 1------ >>> toMaybe f x = if f x then Just x else Nothing--- >>> satisfy f = Parser.maybe (toMaybe f)----{-# INLINE satisfy #-}-satisfy :: Monad m => (a -> Bool) -> Parser a m a--- satisfy predicate = maybe (\a -> if predicate a then Just a else Nothing)-satisfy predicate = Parser step initial extract-- where-- initial = return $ IPartial ()-- step () a = return $- if predicate a- then Done 0 a- else Error "satisfy: predicate failed"-- extract () = return $ Error "satisfy: end of input"---- | Consume one element from the head of the stream. Fails if it encounters--- end of input.------ >>> one = Parser.satisfy $ const True----{-# INLINE one #-}-one :: Monad m => Parser a m a-one = satisfy $ const True---- Alternate names: "only", "onlyThis".---- | Match a specific element.------ >>> oneEq x = Parser.satisfy (== x)----{-# INLINE oneEq #-}-oneEq :: (Monad m, Eq a) => a -> Parser a m a-oneEq x = satisfy (== x)---- Alternate names: "exclude", "notThis".---- | Match anything other than the supplied element.------ >>> oneNotEq x = Parser.satisfy (/= x)----{-# INLINE oneNotEq #-}-oneNotEq :: (Monad m, Eq a) => a -> Parser a m a-oneNotEq x = satisfy (/= x)---- | Match any one of the elements in the supplied list.------ >>> oneOf xs = Parser.satisfy (`Foldable.elem` xs)------ When performance matters a pattern matching predicate could be more--- efficient than a 'Foldable' datatype:------ @--- let p x =--- case x of--- 'a' -> True--- 'e' -> True--- _ -> False--- in satisfy p--- @------ GHC may use a binary search instead of linear search in the list.--- Alternatively, you can also use an array instead of list for storage and--- search.----{-# INLINE oneOf #-}-oneOf :: (Monad m, Eq a, Foldable f) => f a -> Parser a m a-oneOf xs = satisfy (`Foldable.elem` xs)---- | See performance notes in 'oneOf'.------ >>> noneOf xs = Parser.satisfy (`Foldable.notElem` xs)----{-# INLINE noneOf #-}-noneOf :: (Monad m, Eq a, Foldable f) => f a -> Parser a m a-noneOf xs = satisfy (`Foldable.notElem` xs)------------------------------------------------------------------------------------ Taking elements------------------------------------------------------------------------------------ Required to fuse "take" with "many" in "chunksOf", for ghc-9.x-{-# ANN type Tuple'Fused Fuse #-}-data Tuple'Fused a b = Tuple'Fused !a !b deriving Show---- | @takeBetween m n@ takes a minimum of @m@ and a maximum of @n@ input--- elements and folds them using the supplied fold.------ Stops after @n@ elements.--- Fails if the stream ends before @m@ elements could be taken.------ Examples: ------- @--- >>> :{--- takeBetween' low high ls = Stream.parse prsr (Stream.fromList ls)--- where prsr = Parser.takeBetween low high Fold.toList--- :}------ @------ >>> takeBetween' 2 4 [1, 2, 3, 4, 5]--- Right [1,2,3,4]------ >>> takeBetween' 2 4 [1, 2]--- Right [1,2]------ >>> takeBetween' 2 4 [1]--- Left (ParseError "takeBetween: Expecting alteast 2 elements, got 1")------ >>> takeBetween' 0 0 [1, 2]--- Right []------ >>> takeBetween' 0 1 []--- Right []------ @takeBetween@ is the most general take operation, other take operations can--- be defined in terms of takeBetween. For example:------ >>> take n = Parser.takeBetween 0 n--- >>> takeEQ n = Parser.takeBetween n n--- >>> takeGE n = Parser.takeBetween n maxBound------ /Pre-release/----{-# INLINE takeBetween #-}-takeBetween :: Monad m => Int -> Int -> Fold m a b -> Parser a m b-takeBetween low high (Fold fstep finitial fextract) =-- Parser step initial (extract streamErr)-- where-- streamErr i =- "takeBetween: Expecting alteast " ++ show low- ++ " elements, got " ++ show i-- invalidRange =- "takeBetween: lower bound - " ++ show low- ++ " is greater than higher bound - " ++ show high-- foldErr :: Int -> String- foldErr i =- "takeBetween: the collecting fold terminated after"- ++ " consuming" ++ show i ++ " elements"- ++ " minimum" ++ show low ++ " elements needed"-- -- Exactly the same as snext except different constructors, we can possibly- -- deduplicate the two.- {-# INLINE inext #-}- inext i res =- let i1 = i + 1- in case res of- FL.Partial s -> do- let s1 = Tuple'Fused i1 s- if i1 < high- -- XXX ideally this should be a Continue instead- then return $ IPartial s1- else iextract foldErr s1- FL.Done b ->- return- $ if i1 >= low- then IDone b- else IError (foldErr i1)-- initial = do- when (low >= 0 && high >= 0 && low > high)- $ error invalidRange-- finitial >>= inext (-1)-- -- Keep the impl same as inext- {-# INLINE snext #-}- snext i res =- let i1 = i + 1- in case res of- FL.Partial s -> do- let s1 = Tuple'Fused i1 s- if i1 < high- then return $ Continue 0 s1- else extract foldErr s1- FL.Done b ->- return- $ if i1 >= low- then Done 0 b- else Error (foldErr i1)-- step (Tuple'Fused i s) a = fstep s a >>= snext i-- extract f (Tuple'Fused i s)- | i >= low && i <= high = fmap (Done 0) (fextract s)- | otherwise = return $ Error (f i)-- -- XXX Need to make Initial return type Step to deduplicate this- iextract f (Tuple'Fused i s)- | i >= low && i <= high = fmap IDone (fextract s)- | otherwise = return $ IError (f i)---- | Stops after taking exactly @n@ input elements.------ * Stops - after consuming @n@ elements.--- * Fails - if the stream or the collecting fold ends before it can collect--- exactly @n@ elements.------ >>> Stream.parse (Parser.takeEQ 2 Fold.toList) $ Stream.fromList [1,0,1]--- Right [1,0]------ >>> Stream.parse (Parser.takeEQ 4 Fold.toList) $ Stream.fromList [1,0,1]--- Left (ParseError "takeEQ: Expecting exactly 4 elements, input terminated on 3")----{-# INLINE takeEQ #-}-takeEQ :: Monad m => Int -> Fold m a b -> Parser a m b-takeEQ n (Fold fstep finitial fextract) = Parser step initial extract-- where-- initial = do- res <- finitial- case res of- FL.Partial s ->- if n > 0- then return $ IPartial $ Tuple'Fused 1 s- else fmap IDone (fextract s)- FL.Done b -> return $- if n > 0- then IError- $ "takeEQ: Expecting exactly " ++ show n- ++ " elements, fold terminated without"- ++ " consuming any elements"- else IDone b-- step (Tuple'Fused i1 r) a = do- res <- fstep r a- if n > i1- then- return- $ case res of- FL.Partial s -> Continue 0 $ Tuple'Fused (i1 + 1) s- FL.Done _ ->- Error- $ "takeEQ: Expecting exactly " ++ show n- ++ " elements, fold terminated on " ++ show i1- else- -- assert (n == i1)- Done 0- <$> case res of- FL.Partial s -> fextract s- FL.Done b -> return b-- extract (Tuple'Fused i _) =- -- Using the count "i" in the message below causes large performance- -- regression unless we use Fuse annotation on Tuple.- return- $ Error- $ "takeEQ: Expecting exactly " ++ show n- ++ " elements, input terminated on " ++ show (i - 1)--{-# ANN type TakeGEState Fuse #-}-data TakeGEState s =- TakeGELT !Int !s- | TakeGEGE !s---- | Take at least @n@ input elements, but can collect more.------ * Stops - when the collecting fold stops.--- * Fails - if the stream or the collecting fold ends before producing @n@--- elements.------ >>> Stream.parse (Parser.takeGE 4 Fold.toList) $ Stream.fromList [1,0,1]--- Left (ParseError "takeGE: Expecting at least 4 elements, input terminated on 3")------ >>> Stream.parse (Parser.takeGE 4 Fold.toList) $ Stream.fromList [1,0,1,0,1]--- Right [1,0,1,0,1]------ /Pre-release/----{-# INLINE takeGE #-}-takeGE :: Monad m => Int -> Fold m a b -> Parser a m b-takeGE n (Fold fstep finitial fextract) = Parser step initial extract-- where-- initial = do- res <- finitial- case res of- FL.Partial s ->- if n > 0- then return $ IPartial $ TakeGELT 1 s- else return $ IPartial $ TakeGEGE s- FL.Done b -> return $- if n > 0- then IError- $ "takeGE: Expecting at least " ++ show n- ++ " elements, fold terminated without"- ++ " consuming any elements"- else IDone b-- step (TakeGELT i1 r) a = do- res <- fstep r a- if n > i1- then- return- $ case res of- FL.Partial s -> Continue 0 $ TakeGELT (i1 + 1) s- FL.Done _ ->- Error- $ "takeGE: Expecting at least " ++ show n- ++ " elements, fold terminated on " ++ show i1- else- -- assert (n <= i1)- return- $ case res of- FL.Partial s -> Partial 0 $ TakeGEGE s- FL.Done b -> Done 0 b- step (TakeGEGE r) a = do- res <- fstep r a- return- $ case res of- FL.Partial s -> Partial 0 $ TakeGEGE s- FL.Done b -> Done 0 b-- extract (TakeGELT i _) =- return- $ Error- $ "takeGE: Expecting at least " ++ show n- ++ " elements, input terminated on " ++ show (i - 1)- extract (TakeGEGE r) = fmap (Done 0) $ fextract r------------------------------------------------------------------------------------ Conditional splitting------------------------------------------------------------------------------------ XXX We should perhaps use only takeWhileP and rename it to takeWhile.---- | Like 'takeWhile' but uses a 'Parser' instead of a 'Fold' to collect the--- input. The combinator stops when the condition fails or if the collecting--- parser stops.------ Other interesting parsers can be implemented in terms of this parser:------ >>> takeWhile1 cond p = Parser.takeWhileP cond (Parser.takeBetween 1 maxBound p)--- >>> takeWhileBetween cond m n p = Parser.takeWhileP cond (Parser.takeBetween m n p)------ Stops: when the condition fails or the collecting parser stops.--- Fails: when the collecting parser fails.------ /Pre-release/----{-# INLINE takeWhileP #-}-takeWhileP :: Monad m => (a -> Bool) -> Parser a m b -> Parser a m b-takeWhileP predicate (Parser pstep pinitial pextract) =- Parser step pinitial pextract-- where-- step s a =- if predicate a- then pstep s a- else do- r <- pextract s- -- XXX need a map on count- case r of- Error err -> return $ Error err- Done n s1 -> return $ Done (n + 1) s1- Partial _ _ -> error "Bug: takeWhileP: Partial in extract"- Continue n s1 -> return $ Continue (n + 1) s1---- | Collect stream elements until an element fails the predicate. The element--- on which the predicate fails is returned back to the input stream.------ * Stops - when the predicate fails or the collecting fold stops.--- * Fails - never.------ >>> Stream.parse (Parser.takeWhile (== 0) Fold.toList) $ Stream.fromList [0,0,1,0,1]--- Right [0,0]------ >>> takeWhile cond f = Parser.takeWhileP cond (Parser.fromFold f)------ We can implement a @breakOn@ using 'takeWhile':------ @--- breakOn p = takeWhile (not p)--- @----{-# INLINE takeWhile #-}-takeWhile :: Monad m => (a -> Bool) -> Fold m a b -> Parser a m b--- takeWhile cond f = takeWhileP cond (fromFold f)-takeWhile predicate (Fold fstep finitial fextract) =- Parser step initial extract-- where-- initial = do- res <- finitial- return $ case res of- FL.Partial s -> IPartial s- FL.Done b -> IDone b-- step s a =- if predicate a- then do- fres <- fstep s a- return- $ case fres of- FL.Partial s1 -> Partial 0 s1- FL.Done b -> Done 0 b- else Done 1 <$> fextract s-- extract s = fmap (Done 0) (fextract s)--{---- XXX This may not be composable because of the b argument. We can instead--- return a "Reparse b a m b" so that those can be composed.-{-# INLINE takeWhile1X #-}-takeWhile1 :: Monad m => b -> (a -> Bool) -> Refold m b a b -> Parser a m b--- We can implement this using satisfy and takeWhile. We can use "satisfy--- p", fold the result with the refold and then use the "takeWhile p" and--- fold that using the refold.-takeWhile1 acc cond f = undefined--}---- | Like 'takeWhile' but takes at least one element otherwise fails.------ >>> takeWhile1 cond p = Parser.takeWhileP cond (Parser.takeBetween 1 maxBound p)----{-# INLINE takeWhile1 #-}-takeWhile1 :: Monad m => (a -> Bool) -> Fold m a b -> Parser a m b--- takeWhile1 cond f = takeWhileP cond (takeBetween 1 maxBound f)-takeWhile1 predicate (Fold fstep finitial fextract) =- Parser step initial extract-- where-- initial = do- res <- finitial- return $ case res of- FL.Partial s -> IPartial (Left' s)- FL.Done _ ->- IError- $ "takeWhile1: fold terminated without consuming:"- ++ " any element"-- {-# INLINE process #-}- process s a = do- res <- fstep s a- return- $ case res of- FL.Partial s1 -> Partial 0 (Right' s1)- FL.Done b -> Done 0 b-- step (Left' s) a =- if predicate a- then process s a- else return $ Error "takeWhile1: predicate failed on first element"- step (Right' s) a =- if predicate a- then process s a- else do- b <- fextract s- return $ Done 1 b-- extract (Left' _) = return $ Error "takeWhile1: end of input"- extract (Right' s) = fmap (Done 0) (fextract s)---- | Drain the input as long as the predicate succeeds, running the effects and--- discarding the results.------ This is also called @skipWhile@ in some parsing libraries.------ >>> dropWhile p = Parser.takeWhile p Fold.drain----{-# INLINE dropWhile #-}-dropWhile :: Monad m => (a -> Bool) -> Parser a m ()-dropWhile p = takeWhile p FL.drain------------------------------------------------------------------------------------ Separators----------------------------------------------------------------------------------{-# ANN type FramedEscState Fuse #-}-data FramedEscState s =- FrameEscInit !s | FrameEscGo !s !Int | FrameEscEsc !s !Int---- XXX We can remove Maybe from esc-{-# INLINE takeFramedByGeneric #-}-takeFramedByGeneric :: Monad m =>- Maybe (a -> Bool) -- is escape char?- -> Maybe (a -> Bool) -- is frame begin?- -> Maybe (a -> Bool) -- is frame end?- -> Fold m a b- -> Parser a m b-takeFramedByGeneric esc begin end (Fold fstep finitial fextract) =-- Parser step initial extract-- where-- initial = do- res <- finitial- return $- case res of- FL.Partial s -> IPartial (FrameEscInit s)- FL.Done _ ->- error "takeFramedByGeneric: fold done without input"-- {-# INLINE process #-}- process s a n = do- res <- fstep s a- return- $ case res of- FL.Partial s1 -> Continue 0 (FrameEscGo s1 n)- FL.Done b -> Done 0 b-- {-# INLINE processNoEsc #-}- processNoEsc s a n =- case end of- Just isEnd ->- case begin of- Just isBegin ->- -- takeFramedBy case- if isEnd a- then- if n == 0- then Done 0 <$> fextract s- else process s a (n - 1)- else- let n1 = if isBegin a then n + 1 else n- in process s a n1- Nothing -> -- takeEndBy case- if isEnd a- then Done 0 <$> fextract s- else process s a n- Nothing -> -- takeStartBy case- case begin of- Just isBegin ->- if isBegin a- then Done 0 <$> fextract s- else process s a n- Nothing ->- error $ "takeFramedByGeneric: "- ++ "Both begin and end frame predicate missing"-- {-# INLINE processCheckEsc #-}- processCheckEsc s a n =- case esc of- Just isEsc ->- if isEsc a- then return $ Partial 0 $ FrameEscEsc s n- else processNoEsc s a n- Nothing -> processNoEsc s a n-- step (FrameEscInit s) a =- case begin of- Just isBegin ->- if isBegin a- then return $ Partial 0 (FrameEscGo s 0)- else return $ Error "takeFramedByGeneric: missing frame start"- Nothing ->- case end of- Just isEnd ->- if isEnd a- then Done 0 <$> fextract s- else processCheckEsc s a 0- Nothing ->- error "Both begin and end frame predicate missing"- step (FrameEscGo s n) a = processCheckEsc s a n- step (FrameEscEsc s n) a = process s a n-- err = return . Error-- extract (FrameEscInit _) =- err "takeFramedByGeneric: empty token"- extract (FrameEscGo s _) =- case begin of- Just _ ->- case end of- Nothing -> fmap (Done 0) $ fextract s- Just _ -> err "takeFramedByGeneric: missing frame end"- Nothing -> err "takeFramedByGeneric: missing closing frame"- extract (FrameEscEsc _ _) = err "takeFramedByGeneric: trailing escape"--data BlockParseState s =- BlockInit !s- | BlockUnquoted !Int !s- | BlockQuoted !Int !s- | BlockQuotedEsc !Int !s---- Blocks can be of different types e.g. {} or (). We only parse from the--- perspective of the outermost block type. The nesting of that block are--- checked. Any other block types nested inside it are opaque to us and can be--- parsed when the contents of the block are parsed.---- XXX Put a limit on nest level to keep the API safe.---- | Parse a block enclosed within open, close brackets. Block contents may be--- quoted, brackets inside quotes are ignored. Quoting characters can be used--- within quotes if escaped. A block can have a nested block inside it.------ Quote begin and end chars are the same. Block brackets and quote chars must--- not overlap. Block start and end brackets must be different for nesting--- blocks within blocks.------ >>> p = Parser.blockWithQuotes (== '\\') (== '"') '{' '}' Fold.toList--- >>> Stream.parse p $ Stream.fromList "{msg: \"hello world\"}"--- Right "msg: \"hello world\""----{-# INLINE blockWithQuotes #-}-blockWithQuotes :: (Monad m, Eq a) =>- (a -> Bool) -- ^ escape char- -> (a -> Bool) -- ^ quote char, to quote inside brackets- -> a -- ^ Block opening bracket- -> a -- ^ Block closing bracket- -> Fold m a b- -> Parser a m b-blockWithQuotes isEsc isQuote bopen bclose- (Fold fstep finitial fextract) =- Parser step initial extract-- where-- initial = do- res <- finitial- return $- case res of- FL.Partial s -> IPartial (BlockInit s)- FL.Done _ ->- error "blockWithQuotes: fold finished without input"-- {-# INLINE process #-}- process s a nextState = do- res <- fstep s a- return- $ case res of- FL.Partial s1 -> Continue 0 (nextState s1)- FL.Done b -> Done 0 b-- step (BlockInit s) a =- return- $ if a == bopen- then Continue 0 $ BlockUnquoted 1 s- else Error "blockWithQuotes: missing block start"- step (BlockUnquoted level s) a- | a == bopen = process s a (BlockUnquoted (level + 1))- | a == bclose =- if level == 1- then fmap (Done 0) (fextract s)- else process s a (BlockUnquoted (level - 1))- | isQuote a = process s a (BlockQuoted level)- | otherwise = process s a (BlockUnquoted level)- step (BlockQuoted level s) a- | isEsc a = process s a (BlockQuotedEsc level)- | otherwise =- if isQuote a- then process s a (BlockUnquoted level)- else process s a (BlockQuoted level)- step (BlockQuotedEsc level s) a = process s a (BlockQuoted level)-- err = return . Error-- extract (BlockInit s) = fmap (Done 0) $ fextract s- extract (BlockUnquoted level _) =- err $ "blockWithQuotes: finished at block nest level " ++ show level- extract (BlockQuoted level _) =- err $ "blockWithQuotes: finished, inside an unfinished quote, "- ++ "at block nest level " ++ show level- extract (BlockQuotedEsc level _) =- err $ "blockWithQuotes: finished, inside an unfinished quote, "- ++ "after an escape char, at block nest level " ++ show level---- | @takeEndBy cond parser@ parses a token that ends by a separator chosen by--- the supplied predicate. The separator is also taken with the token.------ This can be combined with other parsers to implement other interesting--- parsers as follows:------ >>> takeEndByLE cond n p = Parser.takeEndBy cond (Parser.fromFold $ Fold.take n p)--- >>> takeEndByBetween cond m n p = Parser.takeEndBy cond (Parser.takeBetween m n p)------ >>> takeEndBy = Parser.takeEndByEsc (const False)------ See also "Streamly.Data.Fold.takeEndBy". Unlike the fold, the collecting--- parser in the takeEndBy parser can decide whether to fail or not if the--- stream does not end with separator.------ /Pre-release/----{-# INLINE takeEndBy #-}-takeEndBy :: Monad m => (a -> Bool) -> Parser a m b -> Parser a m b--- takeEndBy = takeEndByEsc (const False)-takeEndBy cond (Parser pstep pinitial pextract) =-- Parser step initial pextract-- where-- initial = pinitial-- step s a = do- res <- pstep s a- if not (cond a)- then return res- else extractStep pextract res---- | Like 'takeEndBy' but the separator elements can be escaped using an--- escape char determined by the first predicate. The escape characters are--- removed.------ /pre-release/-{-# INLINE takeEndByEsc #-}-takeEndByEsc :: Monad m =>- (a -> Bool) -> (a -> Bool) -> Parser a m b -> Parser a m b-takeEndByEsc isEsc isSep (Parser pstep pinitial pextract) =-- Parser step initial extract-- where-- initial = first Left' <$> pinitial-- step (Left' s) a = do- if isEsc a- then return $ Partial 0 $ Right' s- else do- res <- pstep s a- if not (isSep a)- then return $ first Left' res- else fmap (first Left') $ extractStep pextract res-- step (Right' s) a = do- res <- pstep s a- return $ first Left' res-- extract (Left' s) = fmap (first Left') $ pextract s- extract (Right' _) =- return $ Error "takeEndByEsc: trailing escape"---- | Like 'takeEndBy' but the separator is dropped.------ See also "Streamly.Data.Fold.takeEndBy_".------ /Pre-release/----{-# INLINE takeEndBy_ #-}-takeEndBy_ :: (a -> Bool) -> Parser a m b -> Parser a m b-{--takeEndBy_ isEnd p =- takeFramedByGeneric Nothing Nothing (Just isEnd) (toFold p)--}-takeEndBy_ cond (Parser pstep pinitial pextract) =-- Parser step pinitial pextract-- where-- step s a =- if cond a- then pextract s- else pstep s a---- | Take either the separator or the token. Separator is a Left value and--- token is Right value.------ /Unimplemented/-{-# INLINE takeEitherSepBy #-}-takeEitherSepBy :: -- Monad m =>- (a -> Bool) -> Fold m (Either a b) c -> Parser a m c-takeEitherSepBy _cond = undefined -- D.toParserK . D.takeEitherSepBy cond---- | Parse a token that starts with an element chosen by the predicate. The--- parser fails if the input does not start with the selected element.------ * Stops - when the predicate succeeds in non-leading position.--- * Fails - when the predicate fails in the leading position.------ >>> splitWithPrefix p f = Stream.parseMany (Parser.takeStartBy p f)------ Examples: ------- >>> p = Parser.takeStartBy (== ',') Fold.toList--- >>> leadingComma = Stream.parse p . Stream.fromList--- >>> leadingComma "a,b"--- Left (ParseError "takeStartBy: missing frame start")--- ...--- >>> leadingComma ",,"--- Right ","--- >>> leadingComma ",a,b"--- Right ",a"--- >>> leadingComma ""--- Right ""------ /Pre-release/----{-# INLINE takeStartBy #-}-takeStartBy :: Monad m => (a -> Bool) -> Fold m a b -> Parser a m b-takeStartBy cond (Fold fstep finitial fextract) =-- Parser step initial extract-- where-- initial = do- res <- finitial- return $- case res of- FL.Partial s -> IPartial (Left' s)- FL.Done _ -> IError "takeStartBy: fold done without input"-- {-# INLINE process #-}- process s a = do- res <- fstep s a- return- $ case res of- FL.Partial s1 -> Partial 0 (Right' s1)- FL.Done b -> Done 0 b-- step (Left' s) a =- if cond a- then process s a- else return $ Error "takeStartBy: missing frame start"- step (Right' s) a =- if not (cond a)- then process s a- else Done 1 <$> fextract s-- extract (Left' s) = fmap (Done 0) $ fextract s- extract (Right' s) = fmap (Done 0) $ fextract s---- | Like 'takeStartBy' but drops the separator.------ >>> takeStartBy_ isBegin = Parser.takeFramedByGeneric Nothing (Just isBegin) Nothing----{-# INLINE takeStartBy_ #-}-takeStartBy_ :: Monad m => (a -> Bool) -> Fold m a b -> Parser a m b-takeStartBy_ isBegin = takeFramedByGeneric Nothing (Just isBegin) Nothing---- | @takeFramedByEsc_ isEsc isBegin isEnd fold@ parses a token framed using a--- begin and end predicate, and an escape character. The frame begin and end--- characters lose their special meaning if preceded by the escape character.------ Nested frames are allowed if begin and end markers are different, nested--- frames must be balanced unless escaped, nested frame markers are emitted as--- it is.------ For example,------ >>> p = Parser.takeFramedByEsc_ (== '\\') (== '{') (== '}') Fold.toList--- >>> Stream.parse p $ Stream.fromList "{hello}"--- Right "hello"--- >>> Stream.parse p $ Stream.fromList "{hello {world}}"--- Right "hello {world}"--- >>> Stream.parse p $ Stream.fromList "{hello \\{world}"--- Right "hello {world"--- >>> Stream.parse p $ Stream.fromList "{hello {world}"--- Left (ParseError "takeFramedByEsc_: missing frame end")------ /Pre-release/-{-# INLINE takeFramedByEsc_ #-}-takeFramedByEsc_ :: Monad m =>- (a -> Bool) -> (a -> Bool) -> (a -> Bool) -> Fold m a b -> Parser a m b--- takeFramedByEsc_ isEsc isEnd p =--- takeFramedByGeneric (Just isEsc) Nothing (Just isEnd) (toFold p)-takeFramedByEsc_ isEsc isBegin isEnd (Fold fstep finitial fextract) =-- Parser step initial extract-- where-- initial = do- res <- finitial- return $- case res of- FL.Partial s -> IPartial (FrameEscInit s)- FL.Done _ ->- error "takeFramedByEsc_: fold done without input"-- {-# INLINE process #-}- process s a n = do- res <- fstep s a- return- $ case res of- FL.Partial s1 -> Continue 0 (FrameEscGo s1 n)- FL.Done b -> Done 0 b-- step (FrameEscInit s) a =- if isBegin a- then return $ Partial 0 (FrameEscGo s 0)- else return $ Error "takeFramedByEsc_: missing frame start"- step (FrameEscGo s n) a =- if isEsc a- then return $ Partial 0 $ FrameEscEsc s n- else do- if not (isEnd a)- then- let n1 = if isBegin a then n + 1 else n- in process s a n1- else- if n == 0- then Done 0 <$> fextract s- else process s a (n - 1)- step (FrameEscEsc s n) a = process s a n-- err = return . Error-- extract (FrameEscInit _) = err "takeFramedByEsc_: empty token"- extract (FrameEscGo _ _) = err "takeFramedByEsc_: missing frame end"- extract (FrameEscEsc _ _) = err "takeFramedByEsc_: trailing escape"--data FramedState s = FrameInit !s | FrameGo !s Int---- | @takeFramedBy_ isBegin isEnd fold@ parses a token framed by a begin and an--- end predicate.------ >>> takeFramedBy_ = Parser.takeFramedByEsc_ (const False)----{-# INLINE takeFramedBy_ #-}-takeFramedBy_ :: Monad m =>- (a -> Bool) -> (a -> Bool) -> Fold m a b -> Parser a m b--- takeFramedBy_ isBegin isEnd =--- takeFramedByGeneric (Just (const False)) (Just isBegin) (Just isEnd)-takeFramedBy_ isBegin isEnd (Fold fstep finitial fextract) =-- Parser step initial extract-- where-- initial = do- res <- finitial- return $- case res of- FL.Partial s -> IPartial (FrameInit s)- FL.Done _ ->- error "takeFramedBy_: fold done without input"-- {-# INLINE process #-}- process s a n = do- res <- fstep s a- return- $ case res of- FL.Partial s1 -> Continue 0 (FrameGo s1 n)- FL.Done b -> Done 0 b-- step (FrameInit s) a =- if isBegin a- then return $ Continue 0 (FrameGo s 0)- else return $ Error "takeFramedBy_: missing frame start"- step (FrameGo s n) a- | not (isEnd a) =- let n1 = if isBegin a then n + 1 else n- in process s a n1- | n == 0 = Done 0 <$> fextract s- | otherwise = process s a (n - 1)-- err = return . Error-- extract (FrameInit _) = err "takeFramedBy_: empty token"- extract (FrameGo _ _) = err "takeFramedBy_: missing frame end"------------------------------------------------------------------------------------ Grouping and words----------------------------------------------------------------------------------data WordByState s b = WBLeft !s | WBWord !s | WBRight !b---- Note we can also get words using something like:--- sepBy FL.toList (takeWhile (not . p) Fold.toList) (dropWhile p)------ But that won't be as efficient and ergonomic.---- | Like 'splitOn' but strips leading, trailing, and repeated separators.--- Therefore, @".a..b."@ having '.' as the separator would be parsed as--- @["a","b"]@. In other words, its like parsing words from whitespace--- separated text.------ * Stops - when it finds a word separator after a non-word element--- * Fails - never.------ >>> wordBy = Parser.wordFramedBy (const False) (const False) (const False)------ @--- S.wordsBy pred f = S.parseMany (PR.wordBy pred f)--- @----{-# INLINE wordBy #-}-wordBy :: Monad m => (a -> Bool) -> Fold m a b -> Parser a m b-wordBy predicate (Fold fstep finitial fextract) = Parser step initial extract-- where-- {-# INLINE worder #-}- worder s a = do- res <- fstep s a- return- $ case res of- FL.Partial s1 -> Partial 0 $ WBWord s1- FL.Done b -> Done 0 b-- initial = do- res <- finitial- return- $ case res of- FL.Partial s -> IPartial $ WBLeft s- FL.Done b -> IDone b-- step (WBLeft s) a =- if not (predicate a)- then worder s a- else return $ Partial 0 $ WBLeft s- step (WBWord s) a =- if not (predicate a)- then worder s a- else do- b <- fextract s- return $ Partial 0 $ WBRight b- step (WBRight b) a =- return- $ if not (predicate a)- then Done 1 b- else Partial 0 $ WBRight b-- extract (WBLeft s) = fmap (Done 0) $ fextract s- extract (WBWord s) = fmap (Done 0) $ fextract s- extract (WBRight b) = return (Done 0 b)--data WordFramedState s b =- WordFramedSkipPre !s- | WordFramedWord !s !Int- | WordFramedEsc !s !Int- | WordFramedSkipPost !b---- | Like 'wordBy' but treats anything inside a pair of quotes as a single--- word, the quotes can be escaped by an escape character. Recursive quotes--- are possible if quote begin and end characters are different, quotes must be--- balanced. Outermost quotes are stripped.------ >>> braces = Parser.wordFramedBy (== '\\') (== '{') (== '}') isSpace Fold.toList--- >>> Stream.parse braces $ Stream.fromList "{ab} cd"--- Right "ab"--- >>> Stream.parse braces $ Stream.fromList "{ab}{cd}"--- Right "abcd"--- >>> Stream.parse braces $ Stream.fromList "a{b} cd"--- Right "ab"--- >>> Stream.parse braces $ Stream.fromList "a{{b}} cd"--- Right "a{b}"------ >>> quotes = Parser.wordFramedBy (== '\\') (== '"') (== '"') isSpace Fold.toList--- >>> Stream.parse quotes $ Stream.fromList "\"a\"\"b\""--- Right "ab"----{-# INLINE wordFramedBy #-}-wordFramedBy :: Monad m =>- (a -> Bool) -- ^ Matches escape elem?- -> (a -> Bool) -- ^ Matches left quote?- -> (a -> Bool) -- ^ matches right quote?- -> (a -> Bool) -- ^ matches word separator?- -> Fold m a b- -> Parser a m b-wordFramedBy isEsc isBegin isEnd isSep- (Fold fstep finitial fextract) =- Parser step initial extract-- where-- initial = do- res <- finitial- return $- case res of- FL.Partial s -> IPartial (WordFramedSkipPre s)- FL.Done _ ->- error "wordFramedBy: fold done without input"-- {-# INLINE process #-}- process s a n = do- res <- fstep s a- return- $ case res of- FL.Partial s1 -> Continue 0 (WordFramedWord s1 n)- FL.Done b -> Done 0 b-- step (WordFramedSkipPre s) a- | isEsc a = return $ Continue 0 $ WordFramedEsc s 0- | isSep a = return $ Partial 0 $ WordFramedSkipPre s- | isBegin a = return $ Continue 0 $ WordFramedWord s 1- | isEnd a =- return $ Error "wordFramedBy: missing frame start"- | otherwise = process s a 0- step (WordFramedWord s n) a- | isEsc a = return $ Continue 0 $ WordFramedEsc s n- | n == 0 && isSep a = do- b <- fextract s- return $ Partial 0 $ WordFramedSkipPost b- | otherwise = do- -- We need to use different order for checking begin and end for- -- the n == 0 and n == 1 case so that when the begin and end- -- character is the same we treat the one after begin as end.- if n == 0- then- -- Need to check isBegin first- if isBegin a- then return $ Continue 0 $ WordFramedWord s 1- else if isEnd a- then return $ Error "wordFramedBy: missing frame start"- else process s a n- else- -- Need to check isEnd first- if isEnd a- then- if n == 1- then return $ Continue 0 $ WordFramedWord s 0- else process s a (n - 1)- else if isBegin a- then process s a (n + 1)- else process s a n- step (WordFramedEsc s n) a = process s a n- step (WordFramedSkipPost b) a =- return- $ if not (isSep a)- then Done 1 b- else Partial 0 $ WordFramedSkipPost b-- err = return . Error-- extract (WordFramedSkipPre s) = fmap (Done 0) $ fextract s- extract (WordFramedWord s n) =- if n == 0- then fmap (Done 0) $ fextract s- else err "wordFramedBy: missing frame end"- extract (WordFramedEsc _ _) =- err "wordFramedBy: trailing escape"- extract (WordFramedSkipPost b) = return (Done 0 b)--data WordQuotedState s b a =- WordQuotedSkipPre !s- | WordUnquotedWord !s- | WordQuotedWord !s !Int !a !a- | WordUnquotedEsc !s- | WordQuotedEsc !s !Int !a !a- | WordQuotedSkipPost !b---- | Quote and bracket aware word splitting with escaping. Like 'wordBy' but--- word separators within specified quotes or brackets are ignored. Quotes and--- escape characters can be processed. If the end quote is different from the--- start quote it is called a bracket. The following quoting rules apply:------ * In an unquoted string a character may be preceded by an escape character.--- The escape character is removed and the character following it is treated--- literally with no special meaning e.g. e.g. h\ e\ l\ l\ o is a single word,--- \n is same as n.--- * Any part of the word can be placed within quotes. Inside quotes all--- characters are treated literally with no special meaning. Quoting character--- itself cannot be used within quotes unless escape processing within quotes--- is applied to allow it.--- * Optionally escape processing for quoted part can be specified. Escape--- character has no special meaning inside quotes unless it is followed by a--- character that has a escape translation specified, in that case the escape--- character is removed, and the specified translation is applied to the--- character following it. This can be used to escape the quoting character--- itself within quotes.--- * There can be multiple quoting characters, when a quote starts, all other--- quoting characters within that quote lose any special meaning until the--- quote is closed.--- * A starting quote char without an ending char generates a parse error. An--- ending bracket char without a corresponding bracket begin is ignored.--- * Brackets can be nested.------ We should note that unquoted and quoted escape processing are different. In--- unquoted part escape character is always removed. In quoted part it is--- removed only if followed by a special meaning character. This is consistent--- with how shell performs escape processing.---- Examples of quotes - "double quotes", 'single quotes', (parens), {braces},--- ((nested) brackets).------ Example:------ >>> :{--- >>> q x =--- >>> case x of--- >>> '"' -> Just x--- >>> '\'' -> Just x--- >>> _ -> Nothing--- >>> :}------ >>> p = Parser.wordKeepQuotes (== '\\') q isSpace Fold.toList--- >>> Stream.parse p $ Stream.fromList "a\"b'c\";'d\"e'f ghi"--- Right "a\"b'c\";'d\"e'f"------ Note that outer quotes and backslashes from the input string are consumed by--- Haskell, therefore, the actual input string passed to the parser is:--- a"b'c";'d"e'f ghi------ Similarly, when printing, double quotes are escaped by Haskell.------ Limitations:------ Shell like quote processing can be performed by using quote char specific--- escape processing, single quotes with no escapes, and double quotes with--- escapes.------ JSON string processing can also be achieved except the "\uXXXX" style--- escaping for Unicode characters.----{-# INLINE wordWithQuotes #-}-wordWithQuotes :: (Monad m, Eq a) =>- Bool -- ^ Retain the quotes and escape chars in the output- -> (a -> a -> Maybe a) -- ^ quote char -> escaped char -> translated char- -> a -- ^ Matches an escape elem?- -> (a -> Maybe a) -- ^ If left quote, return right quote, else Nothing.- -> (a -> Bool) -- ^ Matches a word separator?- -> Fold m a b- -> Parser a m b-wordWithQuotes keepQuotes tr escChar toRight isSep- (Fold fstep finitial fextract) =- Parser step initial extract-- where-- -- Can be used to generate parse error for a bracket end without a bracket- -- begin.- isInvalid = const False-- isEsc = (== escChar)-- initial = do- res <- finitial- return $- case res of- FL.Partial s -> IPartial (WordQuotedSkipPre s)- FL.Done _ ->- error "wordKeepQuotes: fold done without input"-- {-# INLINE processQuoted #-}- processQuoted s a n ql qr = do- res <- fstep s a- return- $ case res of- FL.Partial s1 -> Continue 0 (WordQuotedWord s1 n ql qr)- FL.Done b -> Done 0 b-- {-# INLINE processUnquoted #-}- processUnquoted s a = do- res <- fstep s a- return- $ case res of- FL.Partial s1 -> Continue 0 (WordUnquotedWord s1)- FL.Done b -> Done 0 b-- step (WordQuotedSkipPre s) a- | isEsc a = return $ Continue 0 $ WordUnquotedEsc s- | isSep a = return $ Partial 0 $ WordQuotedSkipPre s- | otherwise =- case toRight a of- Just qr ->- if keepQuotes- then processQuoted s a 1 a qr- else return $ Continue 0 $ WordQuotedWord s 1 a qr- Nothing- | isInvalid a ->- return $ Error "wordKeepQuotes: invalid unquoted char"- | otherwise -> processUnquoted s a- step (WordUnquotedWord s) a- | isEsc a = return $ Continue 0 $ WordUnquotedEsc s- | isSep a = do- b <- fextract s- return $ Partial 0 $ WordQuotedSkipPost b- | otherwise = do- case toRight a of- Just qr ->- if keepQuotes- then processQuoted s a 1 a qr- else return $ Continue 0 $ WordQuotedWord s 1 a qr- Nothing ->- if isInvalid a- then return $ Error "wordKeepQuotes: invalid unquoted char"- else processUnquoted s a- step (WordQuotedWord s n ql qr) a- | isEsc a = return $ Continue 0 $ WordQuotedEsc s n ql qr- {-- -- XXX Will this ever occur? Will n ever be 0?- | n == 0 && isSep a = do- b <- fextract s- return $ Partial 0 $ WordQuotedSkipPost b- -}- | otherwise = do- if a == qr- then- if n == 1- then if keepQuotes- then processUnquoted s a- else return $ Continue 0 $ WordUnquotedWord s- else processQuoted s a (n - 1) ql qr- else if a == ql- then processQuoted s a (n + 1) ql qr- else processQuoted s a n ql qr- step (WordUnquotedEsc s) a = processUnquoted s a- step (WordQuotedEsc s n ql qr) a =- case tr ql a of- Nothing -> do- res <- fstep s escChar- case res of- FL.Partial s1 -> processQuoted s1 a n ql qr- FL.Done b -> return $ Done 0 b- Just x -> processQuoted s x n ql qr- step (WordQuotedSkipPost b) a =- return- $ if not (isSep a)- then Done 1 b- else Partial 0 $ WordQuotedSkipPost b-- err = return . Error-- extract (WordQuotedSkipPre s) = fmap (Done 0) $ fextract s- extract (WordUnquotedWord s) = fmap (Done 0) $ fextract s- extract (WordQuotedWord s n _ _) =- if n == 0- then fmap (Done 0) $ fextract s- else err "wordWithQuotes: missing frame end"- extract WordQuotedEsc {} =- err "wordWithQuotes: trailing escape"- extract (WordUnquotedEsc _) =- err "wordWithQuotes: trailing escape"- extract (WordQuotedSkipPost b) = return (Done 0 b)---- | 'wordWithQuotes' without processing the quotes and escape function--- supplied to escape the quote char within a quote. Can be used to parse words--- keeping the quotes and escapes intact.------ >>> wordKeepQuotes = Parser.wordWithQuotes True (\_ _ -> Nothing)----{-# INLINE wordKeepQuotes #-}-wordKeepQuotes :: (Monad m, Eq a) =>- a -- ^ Escape char- -> (a -> Maybe a) -- ^ If left quote, return right quote, else Nothing.- -> (a -> Bool) -- ^ Matches a word separator?- -> Fold m a b- -> Parser a m b-wordKeepQuotes =- -- Escape the quote char itself- wordWithQuotes True (\q x -> if q == x then Just x else Nothing)---- See the "Quoting Rules" section in the "bash" manual page for a primer on--- how quotes are used by shells.---- | 'wordWithQuotes' with quote processing applied and escape function--- supplied to escape the quote char within a quote. Can be ysed to parse words--- and processing the quoting and escaping at the same time.------ >>> wordProcessQuotes = Parser.wordWithQuotes False (\_ _ -> Nothing)----{-# INLINE wordProcessQuotes #-}-wordProcessQuotes :: (Monad m, Eq a) =>- a -- ^ Escape char- -> (a -> Maybe a) -- ^ If left quote, return right quote, else Nothing.- -> (a -> Bool) -- ^ Matches a word separator?- -> Fold m a b- -> Parser a m b-wordProcessQuotes =- -- Escape the quote char itself- wordWithQuotes False (\q x -> if q == x then Just x else Nothing)--{-# ANN type GroupByState Fuse #-}-data GroupByState a s- = GroupByInit !s- | GroupByGrouping !a !s---- | Given an input stream @[a,b,c,...]@ and a comparison function @cmp@, the--- parser assigns the element @a@ to the first group, then if @a \`cmp` b@ is--- 'True' @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 the parser is terminated. Each group is folded using the 'Fold' @f@ and--- the result of the fold is the result of the parser.------ * Stops - when the comparison fails.--- * Fails - never.------ >>> :{--- runGroupsBy eq =--- Stream.fold Fold.toList--- . Stream.parseMany (Parser.groupBy eq Fold.toList)--- . Stream.fromList--- :}------ >>> runGroupsBy (<) []--- []------ >>> runGroupsBy (<) [1]--- [Right [1]]------ >>> runGroupsBy (<) [3, 5, 4, 1, 2, 0]--- [Right [3,5,4],Right [1,2],Right [0]]----{-# INLINE groupBy #-}-groupBy :: Monad m => (a -> a -> Bool) -> Fold m a b -> Parser a m b-groupBy eq (Fold fstep finitial fextract) = Parser step initial extract-- where-- {-# INLINE grouper #-}- grouper s a0 a = do- res <- fstep s a- return- $ case res of- FL.Done b -> Done 0 b- FL.Partial s1 -> Partial 0 (GroupByGrouping a0 s1)-- initial = do- res <- finitial- return- $ case res of- FL.Partial s -> IPartial $ GroupByInit s- FL.Done b -> IDone b-- step (GroupByInit s) a = grouper s a a- step (GroupByGrouping a0 s) a =- if eq a0 a- then grouper s a0 a- else Done 1 <$> fextract s-- extract (GroupByInit s) = fmap (Done 0) $ fextract s- extract (GroupByGrouping _ s) = fmap (Done 0) $ fextract s---- | Unlike 'groupBy' this combinator performs a rolling comparison of two--- successive elements in the input stream. Assuming the input stream--- is @[a,b,c,...]@ and the comparison function is @cmp@, the parser--- first assigns the element @a@ to the first group, then if @a \`cmp` b@ is--- 'True' @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 the parser is terminated. Each group is folded using the 'Fold' @f@ and--- the result of the fold is the result of the parser.------ * Stops - when the comparison fails.--- * Fails - never.------ >>> :{--- runGroupsByRolling eq =--- Stream.fold Fold.toList--- . Stream.parseMany (Parser.groupByRolling eq Fold.toList)--- . Stream.fromList--- :}------ >>> runGroupsByRolling (<) []--- []------ >>> runGroupsByRolling (<) [1]--- [Right [1]]------ >>> runGroupsByRolling (<) [3, 5, 4, 1, 2, 0]--- [Right [3,5],Right [4],Right [1,2],Right [0]]------ /Pre-release/----{-# INLINE groupByRolling #-}-groupByRolling :: Monad m => (a -> a -> Bool) -> Fold m a b -> Parser a m b-groupByRolling eq (Fold fstep finitial fextract) = Parser step initial extract-- where-- {-# INLINE grouper #-}- grouper s a = do- res <- fstep s a- return- $ case res of- FL.Done b -> Done 0 b- FL.Partial s1 -> Partial 0 (GroupByGrouping a s1)-- initial = do- res <- finitial- return- $ case res of- FL.Partial s -> IPartial $ GroupByInit s- FL.Done b -> IDone b-- step (GroupByInit s) a = grouper s a- step (GroupByGrouping a0 s) a =- if eq a0 a- then grouper s a- else Done 1 <$> fextract s-- extract (GroupByInit s) = fmap (Done 0) $ fextract s- extract (GroupByGrouping _ s) = fmap (Done 0) $ fextract s--{-# ANN type GroupByStatePair Fuse #-}-data GroupByStatePair a s1 s2- = GroupByInitPair !s1 !s2- | GroupByGroupingPair !a !s1 !s2- | GroupByGroupingPairL !a !s1 !s2- | GroupByGroupingPairR !a !s1 !s2---- | Like 'groupByRolling', but if the predicate is 'True' then collects using--- the first fold as long as the predicate holds 'True', if the predicate is--- 'False' collects using the second fold as long as it remains 'False'.--- Returns 'Left' for the first case and 'Right' for the second case.------ For example, if we want to detect sorted sequences in a stream, both--- ascending and descending cases we can use 'groupByRollingEither (<=)--- Fold.toList Fold.toList'.------ /Pre-release/-{-# INLINE groupByRollingEither #-}-groupByRollingEither :: Monad m =>- (a -> a -> Bool) -> Fold m a b -> Fold m a c -> Parser a m (Either b c)-groupByRollingEither- eq- (Fold fstep1 finitial1 fextract1)- (Fold fstep2 finitial2 fextract2) = Parser step initial extract-- where-- {-# INLINE grouper #-}- grouper s1 s2 a = do- return $ Continue 0 (GroupByGroupingPair a s1 s2)-- {-# INLINE grouperL2 #-}- grouperL2 s1 s2 a = do- res <- fstep1 s1 a- return- $ case res of- FL.Done b -> Done 0 (Left b)- FL.Partial s11 -> Partial 0 (GroupByGroupingPairL a s11 s2)-- {-# INLINE grouperL #-}- grouperL s1 s2 a0 a = do- res <- fstep1 s1 a0- case res of- FL.Done b -> return $ Done 0 (Left b)- FL.Partial s11 -> grouperL2 s11 s2 a-- {-# INLINE grouperR2 #-}- grouperR2 s1 s2 a = do- res <- fstep2 s2 a- return- $ case res of- FL.Done b -> Done 0 (Right b)- FL.Partial s21 -> Partial 0 (GroupByGroupingPairR a s1 s21)-- {-# INLINE grouperR #-}- grouperR s1 s2 a0 a = do- res <- fstep2 s2 a0- case res of- FL.Done b -> return $ Done 0 (Right b)- FL.Partial s21 -> grouperR2 s1 s21 a-- initial = do- res1 <- finitial1- res2 <- finitial2- return- $ case res1 of- FL.Partial s1 ->- case res2 of- FL.Partial s2 -> IPartial $ GroupByInitPair s1 s2- FL.Done b -> IDone (Right b)- FL.Done b -> IDone (Left b)-- step (GroupByInitPair s1 s2) a = grouper s1 s2 a-- step (GroupByGroupingPair a0 s1 s2) a =- if not (eq a0 a)- then grouperL s1 s2 a0 a- else grouperR s1 s2 a0 a-- step (GroupByGroupingPairL a0 s1 s2) a =- if not (eq a0 a)- then grouperL2 s1 s2 a- else Done 1 . Left <$> fextract1 s1-- step (GroupByGroupingPairR a0 s1 s2) a =- if eq a0 a- then grouperR2 s1 s2 a- else Done 1 . Right <$> fextract2 s2-- extract (GroupByInitPair s1 _) = Done 0 . Left <$> fextract1 s1- extract (GroupByGroupingPairL _ s1 _) = Done 0 . Left <$> fextract1 s1- extract (GroupByGroupingPairR _ _ s2) = Done 0 . Right <$> fextract2 s2- extract (GroupByGroupingPair a s1 _) = do- res <- fstep1 s1 a- case res of- FL.Done b -> return $ Done 0 (Left b)- FL.Partial s11 -> Done 0 . Left <$> fextract1 s11---- XXX use an Unfold instead of a list?--- XXX custom combinators for matching list, array and stream?--- XXX rename to listBy?---- | Match the given sequence of elements using the given comparison function.--- Returns the original sequence if successful.------ Definition:------ >>> listEqBy cmp xs = Parser.streamEqBy cmp (Stream.fromList xs) *> Parser.fromPure xs------ Examples:------ >>> Stream.parse (Parser.listEqBy (==) "string") $ Stream.fromList "string"--- Right "string"------ >>> Stream.parse (Parser.listEqBy (==) "mismatch") $ Stream.fromList "match"--- Left (ParseError "streamEqBy: mismtach occurred")----{-# INLINE listEqBy #-}-listEqBy :: Monad m => (a -> a -> Bool) -> [a] -> Parser a m [a]-listEqBy cmp xs = streamEqByInternal cmp (D.fromList xs) *> fromPure xs-{--listEqBy cmp str = Parser step initial extract-- where-- -- XXX Should return IDone in initial for [] case- initial = return $ IPartial str-- step [] _ = return $ Done 0 str- step [x] a =- return- $ if x `cmp` a- then Done 0 str- else Error "listEqBy: failed, yet to match the last element"- step (x:xs) a =- return- $ if x `cmp` a- then Continue 0 xs- else Error- $ "listEqBy: failed, yet to match "- ++ show (length xs + 1) ++ " elements"-- extract xs =- return- $ Error- $ "listEqBy: end of input, yet to match "- ++ show (length xs) ++ " elements"--}--{-# INLINE streamEqByInternal #-}-streamEqByInternal :: Monad m => (a -> a -> Bool) -> D.Stream m a -> Parser a m ()-streamEqByInternal cmp (D.Stream sstep state) = Parser step initial extract-- where-- initial = do- r <- sstep defState state- case r of- D.Yield x s -> return $ IPartial (Just' x, s)- D.Stop -> return $ IDone ()- -- Need Skip/Continue in initial to loop right here- D.Skip s -> return $ IPartial (Nothing', s)-- step (Just' x, st) a =- if x `cmp` a- then do- r <- sstep defState st- return- $ case r of- D.Yield x1 s -> Continue 0 (Just' x1, s)- D.Stop -> Done 0 ()- D.Skip s -> Continue 1 (Nothing', s)- else return $ Error "streamEqBy: mismtach occurred"- step (Nothing', st) a = do- r <- sstep defState st- return- $ case r of- D.Yield x s -> do- if x `cmp` a- then Continue 0 (Nothing', s)- else Error "streamEqBy: mismatch occurred"- D.Stop -> Done 1 ()- D.Skip s -> Continue 1 (Nothing', s)-- extract _ = return $ Error "streamEqBy: end of input"---- | Like 'listEqBy' but uses a stream instead of a list and does not return--- the stream.----{-# INLINE streamEqBy #-}-streamEqBy :: Monad m => (a -> a -> Bool) -> D.Stream m a -> Parser a m ()--- XXX Somehow composing this with "*>" is much faster on the microbenchmark.--- Need to investigate why.-streamEqBy cmp stream = streamEqByInternal cmp stream *> fromPure ()---- Rename to "list".--- | Match the input sequence with the supplied list and return it if--- successful.------ >>> listEq = Parser.listEqBy (==)----{-# INLINE listEq #-}-listEq :: (Monad m, Eq a) => [a] -> Parser a m [a]-listEq = listEqBy (==)---- | Match if the input stream is a subsequence of the argument stream i.e. all--- the elements of the input stream occur, in order, in the argument stream.--- The elements do not have to occur consecutively. A sequence is considered a--- subsequence of itself.-{-# INLINE subsequenceBy #-}-subsequenceBy :: -- Monad m =>- (a -> a -> Bool) -> Stream m a -> Parser a m ()-subsequenceBy = undefined--{---- Should go in Data.Parser.Regex in streamly package so that it can depend on--- regex backends.-{-# INLINE regexPosix #-}-regexPosix :: -- Monad m =>- Regex -> Parser m a (Maybe (Array (MatchOffset, MatchLength)))-regexPosix = undefined--{-# INLINE regexPCRE #-}-regexPCRE :: -- Monad m =>- Regex -> Parser m a (Maybe (Array (MatchOffset, MatchLength)))-regexPCRE = undefined--}------------------------------------------------------------------------------------ Transformations on input------------------------------------------------------------------------------------ Initial needs a "Continue" constructor to implement scans on parsers. As a--- parser can always return a Continue in initial when we feed the fold's--- initial result to it. We can work this around for postscan by introducing an--- initial state and calling "initial" only on the first input.---- | Stateful scan on the input of a parser using a Fold.------ /Unimplemented/----{-# INLINE postscan #-}-postscan :: -- Monad m =>- Fold m a b -> Parser b m c -> Parser a m c-postscan = undefined--{-# INLINE zipWithM #-}-zipWithM :: Monad m =>- (a -> b -> m c) -> D.Stream m a -> Fold m c x -> Parser b m x-zipWithM zf (D.Stream sstep state) (Fold fstep finitial fextract) =- Parser step initial extract-- where-- initial = do- fres <- finitial- case fres of- FL.Partial fs -> do- r <- sstep defState state- case r of- D.Yield x s -> return $ IPartial (Just' x, s, fs)- D.Stop -> do- x <- fextract fs- return $ IDone x- -- Need Skip/Continue in initial to loop right here- D.Skip s -> return $ IPartial (Nothing', s, fs)- FL.Done x -> return $ IDone x-- step (Just' a, st, fs) b = do- c <- zf a b- fres <- fstep fs c- case fres of- FL.Partial fs1 -> do- r <- sstep defState st- case r of- D.Yield x1 s -> return $ Continue 0 (Just' x1, s, fs1)- D.Stop -> do- x <- fextract fs1- return $ Done 0 x- D.Skip s -> return $ Continue 1 (Nothing', s, fs1)- FL.Done x -> return $ Done 0 x- step (Nothing', st, fs) b = do- r <- sstep defState st- case r of- D.Yield a s -> do- c <- zf a b- fres <- fstep fs c- case fres of- FL.Partial fs1 ->- return $ Continue 0 (Nothing', s, fs1)- FL.Done x -> return $ Done 0 x- D.Stop -> do- x <- fextract fs- return $ Done 1 x- D.Skip s -> return $ Continue 1 (Nothing', s, fs)-- extract _ = return $ Error "zipWithM: end of input"---- | Zip the input of a fold with a stream.------ /Pre-release/----{-# INLINE zip #-}-zip :: Monad m => D.Stream m a -> Fold m (a, b) x -> Parser b m x-zip = zipWithM (curry return)---- | Pair each element of a fold input with its index, starting from index 0.------ /Pre-release/-{-# INLINE indexed #-}-indexed :: forall m a b. Monad m => Fold m (Int, a) b -> Parser a m b-indexed = zip (D.enumerateFromIntegral 0 :: D.Stream m Int)---- | @makeIndexFilter indexer filter predicate@ generates a fold filtering--- function using a fold indexing function that attaches an index to each input--- element and a filtering function that filters using @(index, element) ->--- Bool) as predicate.------ For example:------ @--- filterWithIndex = makeIndexFilter indexed filter--- filterWithAbsTime = makeIndexFilter timestamped filter--- filterWithRelTime = makeIndexFilter timeIndexed filter--- @------ /Pre-release/-{-# INLINE makeIndexFilter #-}-makeIndexFilter ::- (Fold m (s, a) b -> Parser a m b)- -> (((s, a) -> Bool) -> Fold m (s, a) b -> Fold m (s, a) b)- -> (((s, a) -> Bool) -> Fold m a b -> Parser a m b)-makeIndexFilter f comb g = f . comb g . FL.lmap snd---- | @sampleFromthen offset stride@ samples the element at @offset@ index and--- then every element at strides of @stride@.------ /Pre-release/-{-# INLINE sampleFromthen #-}-sampleFromthen :: Monad m => Int -> Int -> Fold m a b -> Parser a m b-sampleFromthen offset size =- makeIndexFilter indexed FL.filter (\(i, _) -> (i + offset) `mod` size == 0)-------------------------------------------------------------------------------------- Spanning------------------------------------------------------------------------------------- | @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 = Stream.parse (Parser.span p Fold.toList Fold.toList) $ Stream.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],[])------ @------ /Pre-release/-{-# INLINE span #-}-span :: Monad m => (a -> Bool) -> Fold m a b -> Fold m a c -> Parser a m (b, c)-span p f1 f2 = noErrorUnsafeSplitWith (,) (takeWhile p f1) (fromFold f2)---- | 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.------ /Pre-release/----{-# INLINE spanBy #-}-spanBy ::- Monad m- => (a -> a -> Bool) -> Fold m a b -> Fold m a c -> Parser a m (b, c)-spanBy eq f1 f2 = noErrorUnsafeSplitWith (,) (groupBy eq f1) (fromFold f2)---- | Like 'spanBy' but applies the predicate in a rolling fashion i.e.--- predicate is applied to the previous and the next input elements.------ /Pre-release/-{-# INLINE spanByRolling #-}-spanByRolling ::- Monad m- => (a -> a -> Bool) -> Fold m a b -> Fold m a c -> Parser a m (b, c)-spanByRolling eq f1 f2 =- noErrorUnsafeSplitWith (,) (groupByRolling eq f1) (fromFold f2)------------------------------------------------------------------------------------ nested parsers------------------------------------------------------------------------------------ | Takes at-most @n@ input elements.------ * Stops - when the collecting parser stops.--- * Fails - when the collecting parser fails.------ >>> Stream.parse (Parser.takeP 4 (Parser.takeEQ 2 Fold.toList)) $ Stream.fromList [1, 2, 3, 4, 5]--- Right [1,2]------ >>> Stream.parse (Parser.takeP 4 (Parser.takeEQ 5 Fold.toList)) $ Stream.fromList [1, 2, 3, 4, 5]--- Left (ParseError "takeEQ: Expecting exactly 5 elements, input terminated on 4")------ /Internal/-{-# INLINE takeP #-}-takeP :: Monad m => Int -> Parser a m b -> Parser a m b-takeP lim (Parser pstep pinitial pextract) = Parser step initial extract-- where-- initial = do- res <- pinitial- case res of- IPartial s ->- if lim > 0- then return $ IPartial $ Tuple' 0 s- else iextract s- IDone b -> return $ IDone b- IError e -> return $ IError e-- step (Tuple' cnt r) a = do- assertM(cnt < lim)- res <- pstep r a- let cnt1 = cnt + 1- case res of- Partial 0 s -> do- assertM(cnt1 >= 0)- if cnt1 < lim- then return $ Partial 0 $ Tuple' cnt1 s- else do- r1 <- pextract s- return $ case r1 of- Done n b -> Done n b- Continue n s1 -> Continue n (Tuple' (cnt1 - n) s1)- Error err -> Error err- Partial _ _ -> error "takeP: Partial in extract"-- Continue 0 s -> do- assertM(cnt1 >= 0)- if cnt1 < lim- then return $ Continue 0 $ Tuple' cnt1 s- else do- r1 <- pextract s- return $ case r1 of- Done n b -> Done n b- Continue n s1 -> Continue n (Tuple' (cnt1 - n) s1)- Error err -> Error err- Partial _ _ -> error "takeP: Partial in extract"- Partial n s -> do- let taken = cnt1 - n- assertM(taken >= 0)- return $ Partial n $ Tuple' taken s- Continue n s -> do- let taken = cnt1 - n- assertM(taken >= 0)- return $ Continue n $ Tuple' taken s- Done n b -> return $ Done n b- Error str -> return $ Error str-- extract (Tuple' cnt r) = do- r1 <- pextract r- return $ case r1 of- Done n b -> Done n b- Continue n s1 -> Continue n (Tuple' (cnt - n) s1)- Error err -> Error err- Partial _ _ -> error "takeP: Partial in extract"-- -- XXX Need to make the Initial type Step to remove this- iextract s = do- r <- pextract s- return $ case r of- Done _ b -> IDone b- Error err -> IError err- _ -> error "Bug: takeP invalid state in initial"---- | Run a parser without consuming the input.----{-# INLINE lookAhead #-}-lookAhead :: Monad m => Parser a m b -> Parser a m b-lookAhead (Parser step1 initial1 _) = Parser step initial extract-- where-- initial = do- res <- initial1- return $ case res of- IPartial s -> IPartial (Tuple'Fused 0 s)- IDone b -> IDone b- IError e -> IError e-- step (Tuple'Fused cnt st) a = do- r <- step1 st a- let cnt1 = cnt + 1- return- $ case r of- Partial n s -> Continue n (Tuple'Fused (cnt1 - n) s)- Continue n s -> Continue n (Tuple'Fused (cnt1 - n) s)- Done _ b -> Done cnt1 b- Error err -> Error err-- -- XXX returning an error let's us backtrack. To implement it in a way so- -- that it terminates on eof without an error then we need a way to- -- backtrack on eof, that will require extract to return 'Step' type.- extract (Tuple'Fused n _) =- return- $ Error- $ "lookAhead: end of input after consuming "- ++ show n ++ " elements"------------------------------------------------------------------------------------ Interleaving-------------------------------------------------------------------------------------- To deinterleave we can chain two parsers one behind the other. The input is--- given to the first parser and the input definitively rejected by the first--- parser is given to the second parser.------ We can either have the parsers themselves buffer the input or use the shared--- global buffer to hold it until none of the parsers need it. When the first--- parser returns Skip (i.e. rewind) we let the second parser consume the--- rejected input and when it is done we move the cursor forward to the first--- parser again. This will require a "move forward" command as well.------ To implement grep we can use three parsers, one to find the pattern, one--- to store the context behind the pattern and one to store the context in--- front of the pattern. When a match occurs we need to emit the accumulator of--- all the three parsers. One parser can count the line numbers to provide the--- line number info.--{-# ANN type DeintercalateAllState Fuse #-}-data DeintercalateAllState fs sp ss =- DeintercalateAllInitL !fs- | DeintercalateAllL !fs !sp- | DeintercalateAllInitR !fs- | DeintercalateAllR !fs !ss---- XXX rename this to intercalate---- Having deintercalateAll for accepting or rejecting entire input could be--- useful. For example, in case of JSON parsing we get an entire block of--- key-value pairs which we need to verify. This version may be simpler, more--- efficient. We could implement this as a stream operation like parseMany.------ XXX Also, it may be a good idea to provide a parse driver for a fold. For--- example, in case of csv parsing as we are feeding a line to a fold we can--- parse it.---- | Like 'deintercalate' but the entire input must satisfy the pattern--- otherwise the parser fails. This is many times faster than deintercalate.------ >>> p1 = Parser.takeWhile1 (not . (== '+')) Fold.toList--- >>> p2 = Parser.satisfy (== '+')--- >>> p = Parser.deintercalateAll p1 p2 Fold.toList--- >>> Stream.parse p $ Stream.fromList ""--- Right []--- >>> Stream.parse p $ Stream.fromList "1"--- Right [Left "1"]--- >>> Stream.parse p $ Stream.fromList "1+"--- Left (ParseError "takeWhile1: end of input")--- >>> Stream.parse p $ Stream.fromList "1+2+3"--- Right [Left "1",Right '+',Left "2",Right '+',Left "3"]----{-# INLINE deintercalateAll #-}-deintercalateAll :: Monad m =>- Parser a m x- -> Parser a m y- -> Fold m (Either x y) z- -> Parser a m z-deintercalateAll- (Parser stepL initialL extractL)- (Parser stepR initialR _)- (Fold fstep finitial fextract) = Parser step initial extract-- where-- errMsg p status =- error $ "deintercalate: " ++ p ++ " parser cannot "- ++ status ++ " without input"-- initial = do- res <- finitial- case res of- FL.Partial fs -> return $ IPartial $ DeintercalateAllInitL fs- FL.Done c -> return $ IDone c-- {-# INLINE processL #-}- processL foldAction n nextState = do- fres <- foldAction- case fres of- FL.Partial fs1 -> return $ Partial n (nextState fs1)- FL.Done c -> return $ Done n c-- {-# INLINE runStepL #-}- runStepL fs sL a = do- r <- stepL sL a- case r of- Partial n s -> return $ Partial n (DeintercalateAllL fs s)- Continue n s -> return $ Continue n (DeintercalateAllL fs s)- Done n b ->- processL (fstep fs (Left b)) n DeintercalateAllInitR- Error err -> return $ Error err-- {-# INLINE processR #-}- processR foldAction n = do- fres <- foldAction- case fres of- FL.Partial fs1 -> do- res <- initialL- case res of- IPartial ps -> return $ Partial n (DeintercalateAllL fs1 ps)- IDone _ -> errMsg "left" "succeed"- IError _ -> errMsg "left" "fail"- FL.Done c -> return $ Done n c-- {-# INLINE runStepR #-}- runStepR fs sR a = do- r <- stepR sR a- case r of- Partial n s -> return $ Partial n (DeintercalateAllR fs s)- Continue n s -> return $ Continue n (DeintercalateAllR fs s)- Done n b -> processR (fstep fs (Right b)) n- Error err -> return $ Error err-- step (DeintercalateAllInitL fs) a = do- res <- initialL- case res of- IPartial s -> runStepL fs s a- IDone _ -> errMsg "left" "succeed"- IError _ -> errMsg "left" "fail"- step (DeintercalateAllL fs sL) a = runStepL fs sL a- step (DeintercalateAllInitR fs) a = do- res <- initialR- case res of- IPartial s -> runStepR fs s a- IDone _ -> errMsg "right" "succeed"- IError _ -> errMsg "right" "fail"- step (DeintercalateAllR fs sR) a = runStepR fs sR a-- {-# INLINE extractResult #-}- extractResult n fs r = do- res <- fstep fs r- case res of- FL.Partial fs1 -> fmap (Done n) $ fextract fs1- FL.Done c -> return (Done n c)- extract (DeintercalateAllInitL fs) = fmap (Done 0) $ fextract fs- extract (DeintercalateAllL fs sL) = do- r <- extractL sL- case r of- Done n b -> extractResult n fs (Left b)- Error err -> return $ Error err- Continue n s -> return $ Continue n (DeintercalateAllL fs s)- Partial _ _ -> error "Partial in extract"- extract (DeintercalateAllInitR fs) = fmap (Done 0) $ fextract fs- extract (DeintercalateAllR _ _) =- return $ Error "deintercalateAll: input ended at 'Right' value"--{-# ANN type DeintercalateState Fuse #-}-data DeintercalateState b fs sp ss =- DeintercalateInitL !fs- | DeintercalateL !Int !fs !sp- | DeintercalateInitR !fs- | DeintercalateR !Int !fs !ss- | DeintercalateRL !Int !b !fs !sp---- XXX Add tests that the next character that we take after running a parser is--- correct. Especially for the parsers that maintain a count. In the stream--- finished case (extract) as well as not finished case.---- | Apply two parsers alternately to an input stream. The input stream is--- considered an interleaving of two patterns. The two parsers represent the--- two patterns. Parsing starts at the first parser and stops at the first--- parser. It can be used to parse a infix style pattern e.g. p1 p2 p1 . Empty--- input or single parse of the first parser is accepted.------ >>> p1 = Parser.takeWhile1 (not . (== '+')) Fold.toList--- >>> p2 = Parser.satisfy (== '+')--- >>> p = Parser.deintercalate p1 p2 Fold.toList--- >>> Stream.parse p $ Stream.fromList ""--- Right []--- >>> Stream.parse p $ Stream.fromList "1"--- Right [Left "1"]--- >>> Stream.parse p $ Stream.fromList "1+"--- Right [Left "1"]--- >>> Stream.parse p $ Stream.fromList "1+2+3"--- Right [Left "1",Right '+',Left "2",Right '+',Left "3"]----{-# INLINE deintercalate #-}-deintercalate :: Monad m =>- Parser a m x- -> Parser a m y- -> Fold m (Either x y) z- -> Parser a m z-deintercalate- (Parser stepL initialL extractL)- (Parser stepR initialR _)- (Fold fstep finitial fextract) = Parser step initial extract-- where-- errMsg p status =- error $ "deintercalate: " ++ p ++ " parser cannot "- ++ status ++ " without input"-- initial = do- res <- finitial- case res of- FL.Partial fs -> return $ IPartial $ DeintercalateInitL fs- FL.Done c -> return $ IDone c-- {-# INLINE processL #-}- processL foldAction n nextState = do- fres <- foldAction- case fres of- FL.Partial fs1 -> return $ Partial n (nextState fs1)- FL.Done c -> return $ Done n c-- {-# INLINE runStepL #-}- runStepL cnt fs sL a = do- let cnt1 = cnt + 1- r <- stepL sL a- case r of- Partial n s -> return $ Continue n (DeintercalateL (cnt1 - n) fs s)- Continue n s -> return $ Continue n (DeintercalateL (cnt1 - n) fs s)- Done n b ->- processL (fstep fs (Left b)) n DeintercalateInitR- Error _ -> do- xs <- fextract fs- return $ Done cnt1 xs-- {-# INLINE processR #-}- processR cnt b fs n = do- res <- initialL- case res of- IPartial ps -> return $ Continue n (DeintercalateRL cnt b fs ps)- IDone _ -> errMsg "left" "succeed"- IError _ -> errMsg "left" "fail"-- {-# INLINE runStepR #-}- runStepR cnt fs sR a = do- let cnt1 = cnt + 1- r <- stepR sR a- case r of- Partial n s -> return $ Continue n (DeintercalateR (cnt1 - n) fs s)- Continue n s -> return $ Continue n (DeintercalateR (cnt1 - n) fs s)- Done n b -> processR (cnt1 - n) b fs n- Error _ -> do- xs <- fextract fs- return $ Done cnt1 xs-- step (DeintercalateInitL fs) a = do- res <- initialL- case res of- IPartial s -> runStepL 0 fs s a- IDone _ -> errMsg "left" "succeed"- IError _ -> errMsg "left" "fail"- step (DeintercalateL cnt fs sL) a = runStepL cnt fs sL a- step (DeintercalateInitR fs) a = do- res <- initialR- case res of- IPartial s -> runStepR 0 fs s a- IDone _ -> errMsg "right" "succeed"- IError _ -> errMsg "right" "fail"- step (DeintercalateR cnt fs sR) a = runStepR cnt fs sR a- step (DeintercalateRL cnt bR fs sL) a = do- let cnt1 = cnt + 1- r <- stepL sL a- case r of- Partial n s -> return $ Continue n (DeintercalateRL (cnt1 - n) bR fs s)- Continue n s -> return $ Continue n (DeintercalateRL (cnt1 - n) bR fs s)- Done n bL -> do- res <- fstep fs (Right bR)- case res of- FL.Partial fs1 -> do- fres <- fstep fs1 (Left bL)- case fres of- FL.Partial fs2 ->- return $ Partial n (DeintercalateInitR fs2)- FL.Done c -> return $ Done n c- -- XXX We could have the fold accept pairs of (bR, bL)- FL.Done _ -> error "Fold terminated consuming partial input"- Error _ -> do- xs <- fextract fs- return $ Done cnt1 xs-- {-# INLINE extractResult #-}- extractResult n fs r = do- res <- fstep fs r- case res of- FL.Partial fs1 -> fmap (Done n) $ fextract fs1- FL.Done c -> return (Done n c)-- extract (DeintercalateInitL fs) = fmap (Done 0) $ fextract fs- extract (DeintercalateL cnt fs sL) = do- r <- extractL sL- case r of- Done n b -> extractResult n fs (Left b)- Continue n s -> return $ Continue n (DeintercalateL (cnt - n) fs s)- Partial _ _ -> error "Partial in extract"- Error _ -> do- xs <- fextract fs- return $ Done cnt xs- extract (DeintercalateInitR fs) = fmap (Done 0) $ fextract fs- extract (DeintercalateR cnt fs _) = fmap (Done cnt) $ fextract fs- extract (DeintercalateRL cnt bR fs sL) = do- r <- extractL sL- case r of- Done n bL -> do- res <- fstep fs (Right bR)- case res of- FL.Partial fs1 -> extractResult n fs1 (Left bL)- FL.Done _ -> error "Fold terminated consuming partial input"- Continue n s -> return $ Continue n (DeintercalateRL (cnt - n) bR fs s)- Partial _ _ -> error "Partial in extract"- Error _ -> do- xs <- fextract fs- return $ Done cnt xs--{-# ANN type Deintercalate1State Fuse #-}-data Deintercalate1State b fs sp ss =- Deintercalate1InitL !Int !fs !sp- | Deintercalate1InitR !fs- | Deintercalate1R !Int !fs !ss- | Deintercalate1RL !Int !b !fs !sp---- | Apply two parsers alternately to an input stream. The input stream is--- considered an interleaving of two patterns. The two parsers represent the--- two patterns. Parsing starts at the first parser and stops at the first--- parser. It can be used to parse a infix style pattern e.g. p1 p2 p1 . Empty--- input or single parse of the first parser is accepted.------ >>> p1 = Parser.takeWhile1 (not . (== '+')) Fold.toList--- >>> p2 = Parser.satisfy (== '+')--- >>> p = Parser.deintercalate1 p1 p2 Fold.toList--- >>> Stream.parse p $ Stream.fromList ""--- Left (ParseError "takeWhile1: end of input")--- >>> Stream.parse p $ Stream.fromList "1"--- Right [Left "1"]--- >>> Stream.parse p $ Stream.fromList "1+"--- Right [Left "1"]--- >>> Stream.parse p $ Stream.fromList "1+2+3"--- Right [Left "1",Right '+',Left "2",Right '+',Left "3"]----{-# INLINE deintercalate1 #-}-deintercalate1 :: Monad m =>- Parser a m x- -> Parser a m y- -> Fold m (Either x y) z- -> Parser a m z-deintercalate1- (Parser stepL initialL extractL)- (Parser stepR initialR _)- (Fold fstep finitial fextract) = Parser step initial extract-- where-- errMsg p status =- error $ "deintercalate: " ++ p ++ " parser cannot "- ++ status ++ " without input"-- initial = do- res <- finitial- case res of- FL.Partial fs -> do- pres <- initialL- case pres of- IPartial s -> return $ IPartial $ Deintercalate1InitL 0 fs s- IDone _ -> errMsg "left" "succeed"- IError _ -> errMsg "left" "fail"- FL.Done c -> return $ IDone c-- {-# INLINE processL #-}- processL foldAction n nextState = do- fres <- foldAction- case fres of- FL.Partial fs1 -> return $ Partial n (nextState fs1)- FL.Done c -> return $ Done n c-- {-# INLINE runStepInitL #-}- runStepInitL cnt fs sL a = do- let cnt1 = cnt + 1- r <- stepL sL a- case r of- Partial n s -> return $ Continue n (Deintercalate1InitL (cnt1 - n) fs s)- Continue n s -> return $ Continue n (Deintercalate1InitL (cnt1 - n) fs s)- Done n b ->- processL (fstep fs (Left b)) n Deintercalate1InitR- Error err -> return $ Error err-- {-# INLINE processR #-}- processR cnt b fs n = do- res <- initialL- case res of- IPartial ps -> return $ Continue n (Deintercalate1RL cnt b fs ps)- IDone _ -> errMsg "left" "succeed"- IError _ -> errMsg "left" "fail"-- {-# INLINE runStepR #-}- runStepR cnt fs sR a = do- let cnt1 = cnt + 1- r <- stepR sR a- case r of- Partial n s -> return $ Continue n (Deintercalate1R (cnt1 - n) fs s)- Continue n s -> return $ Continue n (Deintercalate1R (cnt1 - n) fs s)- Done n b -> processR (cnt1 - n) b fs n- Error _ -> do- xs <- fextract fs- return $ Done cnt1 xs-- step (Deintercalate1InitL cnt fs sL) a = runStepInitL cnt fs sL a- step (Deintercalate1InitR fs) a = do- res <- initialR- case res of- IPartial s -> runStepR 0 fs s a- IDone _ -> errMsg "right" "succeed"- IError _ -> errMsg "right" "fail"- step (Deintercalate1R cnt fs sR) a = runStepR cnt fs sR a- step (Deintercalate1RL cnt bR fs sL) a = do- let cnt1 = cnt + 1- r <- stepL sL a- case r of- Partial n s -> return $ Continue n (Deintercalate1RL (cnt1 - n) bR fs s)- Continue n s -> return $ Continue n (Deintercalate1RL (cnt1 - n) bR fs s)- Done n bL -> do- res <- fstep fs (Right bR)- case res of- FL.Partial fs1 -> do- fres <- fstep fs1 (Left bL)- case fres of- FL.Partial fs2 ->- return $ Partial n (Deintercalate1InitR fs2)- FL.Done c -> return $ Done n c- -- XXX We could have the fold accept pairs of (bR, bL)- FL.Done _ -> error "Fold terminated consuming partial input"- Error _ -> do- xs <- fextract fs- return $ Done cnt1 xs-- {-# INLINE extractResult #-}- extractResult n fs r = do- res <- fstep fs r- case res of- FL.Partial fs1 -> fmap (Done n) $ fextract fs1- FL.Done c -> return (Done n c)-- extract (Deintercalate1InitL cnt fs sL) = do- r <- extractL sL- case r of- Done n b -> extractResult n fs (Left b)- Continue n s -> return $ Continue n (Deintercalate1InitL (cnt - n) fs s)- Partial _ _ -> error "Partial in extract"- Error err -> return $ Error err- extract (Deintercalate1InitR fs) = fmap (Done 0) $ fextract fs- extract (Deintercalate1R cnt fs _) = fmap (Done cnt) $ fextract fs- extract (Deintercalate1RL cnt bR fs sL) = do- r <- extractL sL- case r of- Done n bL -> do- res <- fstep fs (Right bR)- case res of- FL.Partial fs1 -> extractResult n fs1 (Left bL)- FL.Done _ -> error "Fold terminated consuming partial input"- Continue n s -> return $ Continue n (Deintercalate1RL (cnt - n) bR fs s)- Partial _ _ -> error "Partial in extract"- Error _ -> do- xs <- fextract fs- return $ Done cnt xs--{-# ANN type SepByState Fuse #-}-data SepByState fs sp ss =- SepByInitL !fs- | SepByL !Int !fs !sp- | SepByInitR !fs- | SepByR !Int !fs !ss---- | Apply two parsers alternately to an input stream. Parsing starts at the--- first parser and stops at the first parser. The output of the first parser--- is emiited and the output of the second parser is discarded. It can be used--- to parse a infix style pattern e.g. p1 p2 p1 . Empty input or single parse--- of the first parser is accepted.------ Definitions:------ >>> sepBy p1 p2 f = Parser.deintercalate p1 p2 (Fold.catLefts f)--- >>> sepBy p1 p2 f = Parser.sepBy1 p1 p2 f <|> Parser.fromEffect (Fold.extractM f)------ Examples:------ >>> p1 = Parser.takeWhile1 (not . (== '+')) Fold.toList--- >>> p2 = Parser.satisfy (== '+')--- >>> p = Parser.sepBy p1 p2 Fold.toList--- >>> Stream.parse p $ Stream.fromList ""--- Right []--- >>> Stream.parse p $ Stream.fromList "1"--- Right ["1"]--- >>> Stream.parse p $ Stream.fromList "1+"--- Right ["1"]--- >>> Stream.parse p $ Stream.fromList "1+2+3"--- Right ["1","2","3"]----{-# INLINE sepBy #-}-sepBy :: Monad m =>- Parser a m b -> Parser a m x -> Fold m b c -> Parser a m c--- This has similar performance as the custom impl below.--- sepBy p1 p2 f = deintercalate p1 p2 (FL.catLefts f)-sepBy- (Parser stepL initialL extractL)- (Parser stepR initialR _)- (Fold fstep finitial fextract) = Parser step initial extract-- where-- errMsg p status =- error $ "sepBy: " ++ p ++ " parser cannot "- ++ status ++ " without input"-- initial = do- res <- finitial- case res of- FL.Partial fs -> return $ IPartial $ SepByInitL fs- FL.Done c -> return $ IDone c-- {-# INLINE processL #-}- processL foldAction n nextState = do- fres <- foldAction- case fres of- FL.Partial fs1 -> return $ Partial n (nextState fs1)- FL.Done c -> return $ Done n c-- {-# INLINE runStepL #-}- runStepL cnt fs sL a = do- let cnt1 = cnt + 1- r <- stepL sL a- case r of- Partial n s -> return $ Continue n (SepByL (cnt1 - n) fs s)- Continue n s -> return $ Continue n (SepByL (cnt1 - n) fs s)- Done n b ->- processL (fstep fs b) n SepByInitR- Error _ -> do- xs <- fextract fs- return $ Done cnt1 xs-- {-# INLINE processR #-}- processR cnt fs n = do- res <- initialL- case res of- IPartial ps -> return $ Continue n (SepByL cnt fs ps)- IDone _ -> errMsg "left" "succeed"- IError _ -> errMsg "left" "fail"-- {-# INLINE runStepR #-}- runStepR cnt fs sR a = do- let cnt1 = cnt + 1- r <- stepR sR a- case r of- Partial n s -> return $ Continue n (SepByR (cnt1 - n) fs s)- Continue n s -> return $ Continue n (SepByR (cnt1 - n) fs s)- Done n _ -> processR (cnt1 - n) fs n- Error _ -> do- xs <- fextract fs- return $ Done cnt1 xs-- step (SepByInitL fs) a = do- res <- initialL- case res of- IPartial s -> runStepL 0 fs s a- IDone _ -> errMsg "left" "succeed"- IError _ -> errMsg "left" "fail"- step (SepByL cnt fs sL) a = runStepL cnt fs sL a- step (SepByInitR fs) a = do- res <- initialR- case res of- IPartial s -> runStepR 0 fs s a- IDone _ -> errMsg "right" "succeed"- IError _ -> errMsg "right" "fail"- step (SepByR cnt fs sR) a = runStepR cnt fs sR a-- {-# INLINE extractResult #-}- extractResult n fs r = do- res <- fstep fs r- case res of- FL.Partial fs1 -> fmap (Done n) $ fextract fs1- FL.Done c -> return (Done n c)-- extract (SepByInitL fs) = fmap (Done 0) $ fextract fs- extract (SepByL cnt fs sL) = do- r <- extractL sL- case r of- Done n b -> extractResult n fs b- Continue n s -> return $ Continue n (SepByL (cnt - n) fs s)- Partial _ _ -> error "Partial in extract"- Error _ -> do- xs <- fextract fs- return $ Done cnt xs- extract (SepByInitR fs) = fmap (Done 0) $ fextract fs- extract (SepByR cnt fs _) = fmap (Done cnt) $ fextract fs---- | Non-backtracking version of sepBy. Several times faster.-{-# INLINE sepByAll #-}-sepByAll :: Monad m =>- Parser a m b -> Parser a m x -> Fold m b c -> Parser a m c-sepByAll p1 p2 f = deintercalateAll p1 p2 (FL.catLefts f)---- XXX This can be implemented using refold, parse one and then continue--- collecting the rest in that.--{-# ANN type SepBy1State Fuse #-}-data SepBy1State fs sp ss =- SepBy1InitL !Int !fs sp- | SepBy1L !Int !fs !sp- | SepBy1InitR !fs- | SepBy1R !Int !fs !ss--{--{-# INLINE sepBy1 #-}-sepBy1 :: Monad m =>- Parser a m b -> Parser a m x -> Fold m b c -> Parser a m c-sepBy1 p sep sink = do- x <- p- f <- fromEffect $ FL.reduce sink- f1 <- fromEffect $ FL.snoc f x- many (sep >> p) f1--}---- | Like 'sepBy' but requires at least one successful parse.------ Definition:------ >>> sepBy1 p1 p2 f = Parser.deintercalate1 p1 p2 (Fold.catLefts f)------ Examples:------ >>> p1 = Parser.takeWhile1 (not . (== '+')) Fold.toList--- >>> p2 = Parser.satisfy (== '+')--- >>> p = Parser.sepBy1 p1 p2 Fold.toList--- >>> Stream.parse p $ Stream.fromList ""--- Left (ParseError "takeWhile1: end of input")--- >>> Stream.parse p $ Stream.fromList "1"--- Right ["1"]--- >>> Stream.parse p $ Stream.fromList "1+"--- Right ["1"]--- >>> Stream.parse p $ Stream.fromList "1+2+3"--- Right ["1","2","3"]----{-# INLINE sepBy1 #-}-sepBy1 :: Monad m =>- Parser a m b -> Parser a m x -> Fold m b c -> Parser a m c-sepBy1- (Parser stepL initialL extractL)- (Parser stepR initialR _)- (Fold fstep finitial fextract) = Parser step initial extract-- where-- errMsg p status =- error $ "sepBy: " ++ p ++ " parser cannot "- ++ status ++ " without input"-- initial = do- res <- finitial- case res of- FL.Partial fs -> do- pres <- initialL- case pres of- IPartial s -> return $ IPartial $ SepBy1InitL 0 fs s- IDone _ -> errMsg "left" "succeed"- IError _ -> errMsg "left" "fail"- FL.Done c -> return $ IDone c-- {-# INLINE processL #-}- processL foldAction n nextState = do- fres <- foldAction- case fres of- FL.Partial fs1 -> return $ Partial n (nextState fs1)- FL.Done c -> return $ Done n c-- {-# INLINE runStepInitL #-}- runStepInitL cnt fs sL a = do- let cnt1 = cnt + 1- r <- stepL sL a- case r of- Partial n s -> return $ Continue n (SepBy1InitL (cnt1 - n) fs s)- Continue n s -> return $ Continue n (SepBy1InitL (cnt1 - n) fs s)- Done n b ->- processL (fstep fs b) n SepBy1InitR- Error err -> return $ Error err-- {-# INLINE runStepL #-}- runStepL cnt fs sL a = do- let cnt1 = cnt + 1- r <- stepL sL a- case r of- Partial n s -> return $ Continue n (SepBy1L (cnt1 - n) fs s)- Continue n s -> return $ Continue n (SepBy1L (cnt1 - n) fs s)- Done n b ->- processL (fstep fs b) n SepBy1InitR- Error _ -> do- xs <- fextract fs- return $ Done cnt1 xs-- {-# INLINE processR #-}- processR cnt fs n = do- res <- initialL- case res of- IPartial ps -> return $ Continue n (SepBy1L cnt fs ps)- IDone _ -> errMsg "left" "succeed"- IError _ -> errMsg "left" "fail"-- {-# INLINE runStepR #-}- runStepR cnt fs sR a = do- let cnt1 = cnt + 1- r <- stepR sR a- case r of- Partial n s -> return $ Continue n (SepBy1R (cnt1 - n) fs s)- Continue n s -> return $ Continue n (SepBy1R (cnt1 - n) fs s)- Done n _ -> processR (cnt1 - n) fs n- Error _ -> do- xs <- fextract fs- return $ Done cnt1 xs-- step (SepBy1InitL cnt fs sL) a = runStepInitL cnt fs sL a- step (SepBy1L cnt fs sL) a = runStepL cnt fs sL a- step (SepBy1InitR fs) a = do- res <- initialR- case res of- IPartial s -> runStepR 0 fs s a- IDone _ -> errMsg "right" "succeed"- IError _ -> errMsg "right" "fail"- step (SepBy1R cnt fs sR) a = runStepR cnt fs sR a-- {-# INLINE extractResult #-}- extractResult n fs r = do- res <- fstep fs r- case res of- FL.Partial fs1 -> fmap (Done n) $ fextract fs1- FL.Done c -> return (Done n c)-- extract (SepBy1InitL cnt fs sL) = do- r <- extractL sL- case r of- Done n b -> extractResult n fs b- Continue n s -> return $ Continue n (SepBy1InitL (cnt - n) fs s)- Partial _ _ -> error "Partial in extract"- Error err -> return $ Error err- extract (SepBy1L cnt fs sL) = do- r <- extractL sL- case r of- Done n b -> extractResult n fs b- Continue n s -> return $ Continue n (SepBy1L (cnt - n) fs s)- Partial _ _ -> error "Partial in extract"- Error _ -> do- xs <- fextract fs- return $ Done cnt xs- extract (SepBy1InitR fs) = fmap (Done 0) $ fextract fs- extract (SepBy1R cnt fs _) = fmap (Done cnt) $ fextract fs------------------------------------------------------------------------------------ Interleaving a collection of parsers-------------------------------------------------------------------------------------- | Apply a collection of parsers to an input stream in a round robin fashion.--- Each parser is applied until it stops and then we repeat starting with the--- the first parser again.------ /Unimplemented/----{-# INLINE roundRobin #-}-roundRobin :: -- (Foldable t, Monad m) =>- t (Parser a m b) -> Fold m b c -> Parser a m c-roundRobin _ps _f = undefined------------------------------------------------------------------------------------ Sequential Collection------------------------------------------------------------------------------------ | @sequence f p@ collects sequential parses of parsers in a--- serial stream @p@ using the fold @f@. Fails if the input ends or any--- of the parsers fail.------ /Pre-release/----{-# INLINE sequence #-}-sequence :: Monad m =>- D.Stream m (Parser a m b) -> Fold m b c -> Parser a m c-sequence (D.Stream sstep sstate) (Fold fstep finitial fextract) =- Parser step initial extract-- where-- initial = do- fres <- finitial- case fres of- FL.Partial fs -> return $ IPartial (Nothing', sstate, fs)- FL.Done c -> return $ IDone c-- -- state does not contain any parser- -- yield a new parser from the stream- step (Nothing', ss, fs) _ = do- sres <- sstep defState ss- case sres of- D.Yield p ss1 -> return $ Continue 1 (Just' p, ss1, fs)- D.Stop -> do- c <- fextract fs- return $ Done 1 c- D.Skip ss1 -> return $ Continue 1 (Nothing', ss1, fs)-- -- state holds a parser that may or may not have been- -- initialized. pinit holds the initial parser state- -- or modified parser state respectively- step (Just' (Parser pstep pinit pextr), ss, fs) a = do- ps <- pinit- case ps of- IPartial ps1 -> do- pres <- pstep ps1 a- case pres of- Partial n ps2 ->- let newP =- Just' $ Parser pstep (return $ IPartial ps2) pextr- in return $ Partial n (newP, ss, fs)- Continue n ps2 ->- let newP =- Just' $ Parser pstep (return $ IPartial ps2) pextr- in return $ Continue n (newP, ss, fs)- Done n b -> do- fres <- fstep fs b- case fres of- FL.Partial fs1 ->- return $ Partial n (Nothing', ss, fs1)- FL.Done c -> return $ Done n c- Error msg -> return $ Error msg- IDone b -> do- fres <- fstep fs b- case fres of- FL.Partial fs1 ->- return $ Partial 1 (Nothing', ss, fs1)- FL.Done c -> return $ Done 1 c- IError err -> return $ Error err-- extract (Nothing', _, fs) = fmap (Done 0) $ fextract fs- extract (Just' (Parser pstep pinit pextr), ss, fs) = do- ps <- pinit- case ps of- IPartial ps1 -> do- r <- pextr ps1- case r of- Done n b -> do- res <- fstep fs b- case res of- FL.Partial fs1 -> fmap (Done n) $ fextract fs1- FL.Done c -> return (Done n c)- Error err -> return $ Error err- Continue n s -> return $ Continue n (Just' (Parser pstep (return (IPartial s)) pextr), ss, fs)- Partial _ _ -> error "Partial in extract"- IDone b -> do- fres <- fstep fs b- case fres of- FL.Partial fs1 -> fmap (Done 0) $ fextract fs1- FL.Done c -> return (Done 0 c)- IError err -> return $ Error err------------------------------------------------------------------------------------ Alternative Collection----------------------------------------------------------------------------------{---- | @choice parsers@ applies the @parsers@ in order and returns the first--- successful parse.------ This is same as 'asum' but more efficient.------ /Broken/----{-# INLINE choice #-}-choice :: (MonadCatch m, Foldable t) => t (Parser a m b) -> Parser a m b-choice = foldl1 shortest--}------------------------------------------------------------------------------------ Sequential Repetition------------------------------------------------------------------------------------ | Like 'many' but uses a 'Parser' instead of a 'Fold' to collect the--- results. Parsing stops or fails if the collecting parser stops or fails.------ /Unimplemented/----{-# INLINE manyP #-}-manyP :: -- MonadCatch m =>- Parser a m b -> Parser b m c -> Parser a m c-manyP _p _f = undefined---- | Collect zero or more parses. Apply the supplied parser repeatedly on the--- input stream and push the parse results to a downstream fold.------ Stops: when the downstream fold stops or the parser fails.--- Fails: never, produces zero or more results.------ >>> many = Parser.countBetween 0 maxBound------ Compare with 'Control.Applicative.many'.----{-# INLINE many #-}-many :: Monad m => Parser a m b -> Fold m b c -> Parser a m c-many = splitMany--- many = countBetween 0 maxBound---- Note: many1 would perhaps be a better name for this and consistent with--- other names like takeWhile1. But we retain the name "some" for--- compatibility.---- | Collect one or more parses. Apply the supplied parser repeatedly on the--- input stream and push the parse results to a downstream fold.------ Stops: when the downstream fold stops or the parser fails.--- Fails: if it stops without producing a single result.------ >>> some p f = Parser.manyP p (Parser.takeGE 1 f)--- >>> some = Parser.countBetween 1 maxBound------ Compare with 'Control.Applicative.some'.----{-# INLINE some #-}-some :: Monad m => Parser a m b -> Fold m b c -> Parser a m c-some = splitSome--- some p f = manyP p (takeGE 1 f)--- some = countBetween 1 maxBound---- | @countBetween m n f p@ collects between @m@ and @n@ sequential parses of--- parser @p@ using the fold @f@. Stop after collecting @n@ results. Fails if--- the input ends or the parser fails before @m@ results are collected.------ >>> countBetween m n p f = Parser.manyP p (Parser.takeBetween m n f)------ /Unimplemented/----{-# INLINE countBetween #-}-countBetween :: -- MonadCatch m =>- Int -> Int -> Parser a m b -> Fold m b c -> Parser a m c-countBetween _m _n _p = undefined--- countBetween m n p f = manyP p (takeBetween m n f)---- | @count n f p@ collects exactly @n@ sequential parses of parser @p@ using--- the fold @f@. Fails if the input ends or the parser fails before @n@--- results are collected.------ >>> count n = Parser.countBetween n n--- >>> count n p f = Parser.manyP p (Parser.takeEQ n f)------ /Unimplemented/----{-# INLINE count #-}-count :: -- MonadCatch m =>- Int -> Parser a m b -> Fold m b c -> Parser a m c-count n = countBetween n n--- count n p f = manyP p (takeEQ n f)---- | Like 'manyTill' but uses a 'Parser' to collect the results instead of a--- 'Fold'. Parsing stops or fails if the collecting parser stops or fails.------ We can implemnent parsers like the following using 'manyTillP':------ @--- countBetweenTill m n f p = manyTillP (takeBetween m n f) p--- @------ /Unimplemented/----{-# INLINE manyTillP #-}-manyTillP :: -- Monad m =>- Parser a m b -> Parser a m x -> Parser b m c -> Parser a m c-manyTillP _p1 _p2 _f = undefined- -- D.toParserK $ D.manyTillP (D.fromParserK p1) (D.fromParserK p2) f--{-# ANN type ManyTillState Fuse #-}-data ManyTillState fs sr sl- = ManyTillR !Int !fs !sr- | ManyTillL !fs !sl---- | @manyTill chunking test f@ tries the parser @test@ on the input, if @test@--- fails it backtracks and tries @chunking@, after @chunking@ succeeds @test@ is--- tried again and so on. The parser stops when @test@ succeeds. The output of--- @test@ is discarded and the output of @chunking@ is accumulated by the--- supplied fold. The parser fails if @chunking@ fails.------ Stops when the fold @f@ stops.----{-# INLINE manyTill #-}-manyTill :: Monad m- => Parser a m b -> Parser a m x -> Fold m b c -> Parser a m c-manyTill (Parser stepL initialL extractL)- (Parser stepR initialR _)- (Fold fstep finitial fextract) =- Parser step initial extract-- where-- -- Caution: Mutual recursion-- scrutL fs p c d e = do- resL <- initialL- case resL of- IPartial sl -> return $ c (ManyTillL fs sl)- IDone bl -> do- fr <- fstep fs bl- case fr of- FL.Partial fs1 -> scrutR fs1 p c d e- FL.Done fb -> return $ d fb- IError err -> return $ e err-- scrutR fs p c d e = do- resR <- initialR- case resR of- IPartial sr -> return $ p (ManyTillR 0 fs sr)- IDone _ -> d <$> fextract fs- IError _ -> scrutL fs p c d e-- initial = do- res <- finitial- case res of- FL.Partial fs -> scrutR fs IPartial IPartial IDone IError- FL.Done b -> return $ IDone b-- step (ManyTillR cnt fs st) a = do- r <- stepR st a- case r of- Partial n s -> return $ Partial n (ManyTillR 0 fs s)- Continue n s -> do- assertM(cnt + 1 - n >= 0)- return $ Continue n (ManyTillR (cnt + 1 - n) fs s)- Done n _ -> do- b <- fextract fs- return $ Done n b- Error _ -> do- resL <- initialL- case resL of- IPartial sl ->- return $ Continue (cnt + 1) (ManyTillL fs sl)- IDone bl -> do- fr <- fstep fs bl- let cnt1 = cnt + 1- case fr of- FL.Partial fs1 ->- scrutR- fs1- (Partial cnt1)- (Continue cnt1)- (Done cnt1)- Error- FL.Done fb -> return $ Done cnt1 fb- IError err -> return $ Error err- step (ManyTillL fs st) a = do- r <- stepL st a- case r of- Partial n s -> return $ Partial n (ManyTillL fs s)- Continue n s -> return $ Continue n (ManyTillL fs s)- Done n b -> do- fs1 <- fstep fs b- case fs1 of- FL.Partial s ->- scrutR s (Partial n) (Continue n) (Done n) Error- FL.Done b1 -> return $ Done n b1- Error err -> return $ Error err-- extract (ManyTillL fs sR) = do- res <- extractL sR- case res of- Done n b -> do- r <- fstep fs b- case r of- FL.Partial fs1 -> fmap (Done n) $ fextract fs1- FL.Done c -> return (Done n c)- Error err -> return $ Error err- Continue n s -> return $ Continue n (ManyTillL fs s)- Partial _ _ -> error "Partial in extract"- extract (ManyTillR _ fs _) = fmap (Done 0) $ fextract fs---- | @manyThen f collect recover@ repeats the parser @collect@ on the input and--- collects the output in the supplied fold. If the the parser @collect@ fails,--- parser @recover@ is run until it stops and then we start repeating the--- parser @collect@ again. The parser fails if the recovery parser fails.------ For example, this can be used to find a key frame in a video stream after an--- error.------ /Unimplemented/----{-# INLINE manyThen #-}-manyThen :: -- (Foldable t, Monad m) =>- Parser a m b -> Parser a m x -> Fold m b c -> Parser a m c-manyThen _parser _recover _f = undefined------------------------------------------------------------------------------------ Repeated Alternatives------------------------------------------------------------------------------------ | Keep trying a parser up to a maximum of @n@ failures. When the parser--- fails the input consumed till now is dropped and the new instance is tried--- on the fresh input.------ /Unimplemented/----{-# INLINE retryMaxTotal #-}-retryMaxTotal :: -- (Monad m) =>- Int -> Parser a m b -> Fold m b c -> Parser a m c-retryMaxTotal _n _p _f = undefined---- | Like 'retryMaxTotal' but aborts after @n@ successive failures.------ /Unimplemented/----{-# INLINE retryMaxSuccessive #-}-retryMaxSuccessive :: -- (Monad m) =>- Int -> Parser a m b -> Fold m b c -> Parser a m c-retryMaxSuccessive _n _p _f = undefined---- | Keep trying a parser until it succeeds. When the parser fails the input--- consumed till now is dropped and the new instance is tried on the fresh--- input.------ /Unimplemented/----{-# INLINE retry #-}-retry :: -- (Monad m) =>- Parser a m b -> Parser a m b-retry _p = undefined
− src/Streamly/Internal/Data/Parser/ParserD/Tee.hs
@@ -1,617 +0,0 @@-{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}--#include "inline.hs"---- |--- Module : Streamly.Internal.Data.Parser.ParserD.Tee--- Copyright : (c) 2020 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ Parallel parsers. Distributing the input to multiple parsers at the same--- time.------ For simplicity, we are using code where a particular state is unreachable--- but it is not prevented by types. Somehow uni-pattern match using "let"--- produces better optimized code compared to using @case@ match and using--- explicit error messages in unreachable cases.------ There seem to be no way to silence individual warnings so we use a global--- incomplete uni-pattern match warning suppression option for the file.--- Disabling the warning for other code as well has the potential to mask off--- some legit warnings, therefore, we have segregated only the code that uses--- uni-pattern matches in this module.--module Streamly.Internal.Data.Parser.ParserD.Tee- (- {-- -- Parallel zipped- teeWith- , teeWithFst- , teeWithMin-- -- Parallel alternatives- , shortest- , longest- -}- )-where--{--import Control.Exception (assert)-import Control.Monad.Catch (MonadCatch, try)-import Prelude- hiding (any, all, takeWhile)--import Fusion.Plugin.Types (Fuse(..))-import Streamly.Internal.Data.Parser.ParserD.Type- (Initial(..), Parser(..), Step(..), ParseError)------------------------------------------------------------------------------------ Distribute input to two parsers and collect both results------------------------------------------------------------------------------------ When the input stream is distributed to two parsers, both the parsers can--- backtrack independently. Therefore, we need separate buffer state for each--- parser.------ ParserK------ We can keep the state of each parser in the zipper and pass around that--- zipper to the parsers. Each parser can consume from the zipper and then pass--- around the zipper to the other parser.------ ParserD------ In the approach we have taken here, the driver pushes one element at a time--- to the tee and each of the parsers in the tee may buffer it independently--- for backtracking. So they do not need to depend on the original stream--- source for individual parser backtracking. Problem arises when both the--- parsers backtrack and they do not need any input from the driver rather they--- must consume from their buffers. For such situation we may need a--- "Continue" style driver command from the tee so that the driver runs--- the tee without providing it any input. Or we may need a local driver loop--- until new input is to be demanded from the input stream.------ When the tee errors out or stops, the tee driver may have to backtrack by--- the specified amount (or the tee must return the leftover input). Therefore,--- the tee driver also has to buffer, this leads to triple buffering.------ When the tee stops we need to determine the backtracking amount from the--- leftover of both the parsers. Since both the parsers may have consumed--- different lengths of the stream we consider the maximum of the two as--- consumed.---- -- XXX We can use Initial instead of StepState-{-# ANN type StepState Fuse #-}-data StepState s a = StepState s | StepResult a---- | State of the pair of parsers in a tee composition--- Note: strictness annotation is important for fusing the constructors-{-# ANN type TeeState Fuse #-}-data TeeState sL sR x a b =--- @TeePair (past buffer, parser state, future-buffer1, future-buffer2) ...@- TeePair !([x], StepState sL a, [x], [x]) !([x], StepState sR b, [x], [x])--{-# ANN type Res Fuse #-}-data Res = Yld Int | Stp Int | Skp | Err String---- | See 'Streamly.Internal.Data.Parser.teeWith'.------ /Broken/----{-# INLINE teeWith #-}-teeWith :: Monad m- => (a -> b -> c) -> Parser x m a -> Parser x m b -> Parser x m c-teeWith zf (Parser stepL initialL extractL) (Parser stepR initialR extractR) =- Parser step initial extract-- where-- {-# INLINE_LATE initial #-}- initial = do- resL <- initialL- resR <- initialR- return $ case resL of- IPartial sl ->- case resR of- IPartial sr -> IPartial $ TeePair ([], StepState sl, [], [])- ([], StepState sr, [], [])- IDone br -> IPartial $ TeePair ([], StepState sl, [], [])- ([], StepResult br, [], [])- IError err -> IError err- IDone bl ->- case resR of- IPartial sr ->- IPartial $ TeePair ([], StepResult bl, [], [])- ([], StepState sr, [], [])- IDone br -> IDone $ zf bl br- IError err -> IError err- IError err -> IError err-- {-# INLINE consume #-}- consume buf inp1 inp2 stp st y = do- let (x, inp11, inp21) =- case inp1 of- [] -> (y, [], [])- z : [] -> (z, reverse (x:inp2), [])- z : zs -> (z, zs, x:inp2)- r <- stp st x- let buf1 = x:buf- return (buf1, r, inp11, inp21)-- -- XXX This is currently broken, even though both the parsers need to- -- consume from their buffers after backtracking the driver would still be- -- pushing more input to the buffers.- --- -- consume one input item and return the next state of the fold- {-# INLINE useStream #-}- useStream buf inp1 inp2 stp st y = do- (buf1, r, inp11, inp21) <- consume buf inp1 inp2 stp st y- case r of- Partial 0 s ->- let state = ([], StepState s, inp11, inp21)- in return (state, Yld 0)- Partial n s ->- let src0 = Prelude.take n buf1- src = Prelude.reverse src0- state = ([], StepState s, src ++ inp11, inp21)- in assert (n <= length buf1) (return (state, Yld n))- Done n b ->- let state = (Prelude.take n buf1, StepResult b, inp11, inp21)- in assert (n <= length buf1) (return (state, Stp n))- -- Continue 0 s -> (buf1, Right s, inp11, inp21)- Continue n s ->- let (src0, buf2) = splitAt n buf1- src = Prelude.reverse src0- state = (buf2, StepState s, src ++ inp11, inp21)- in assert (n <= length buf1) (return (state, Skp))- Error err -> return (undefined, Err err)-- {-# INLINE_LATE step #-}- step (TeePair (bufL, StepState sL, inpL1, inpL2)- (bufR, StepState sR, inpR1, inpR2)) x = do- (l,stL) <- useStream bufL inpL1 inpL2 stepL sL x- (r,stR) <- useStream bufR inpR1 inpR2 stepR sR x- let next = TeePair l r- return $ case (stL,stR) of- (Yld n1, Yld n2) -> Partial (min n1 n2) next- (Yld n1, Stp n2) -> Partial (min n1 n2) next- (Stp n1, Yld n2) -> Partial (min n1 n2) next- (Stp n1, Stp n2) ->- -- Uni-pattern match results in better optimized code compared- -- to a case match.- let (_, StepResult rL, _, _) = l- (_, StepResult rR, _, _) = r- in Done (min n1 n2) (zf rL rR)- (Err err, _) -> Error err- (_, Err err) -> Error err- _ -> Continue 0 next-- step (TeePair (bufL, StepState sL, inpL1, inpL2)- r@(_, StepResult rR, _, _)) x = do- (l,stL) <- useStream bufL inpL1 inpL2 stepL sL x- let next = TeePair l r- -- XXX If the unused count of this stream is lower than the unused- -- count of the stopped stream, only then this will be correct. We need- -- to fix the other case. We need to keep incrementing the unused count- -- of the stopped stream and take the min of the two.- return $ case stL of- Yld n -> Partial n next- Stp n ->- let (_, StepResult rL, _, _) = l- in Done n (zf rL rR)- Skp -> Continue 0 next- Err err -> Error err-- step (TeePair l@(_, StepResult rL, _, _)- (bufR, StepState sR, inpR1, inpR2)) x = do- (r, stR) <- useStream bufR inpR1 inpR2 stepR sR x- let next = TeePair l r- -- XXX If the unused count of this stream is lower than the unused- -- count of the stopped stream, only then this will be correct. We need- -- to fix the other case. We need to keep incrementing the unused count- -- of the stopped stream and take the min of the two.- return $ case stR of- Yld n -> Partial n next- Stp n ->- let (_, StepResult rR, _, _) = r- in Done n (zf rL rR)- Skp -> Continue 0 next- Err err -> Error err-- step _ _ = undefined-- {-# INLINE_LATE extract #-}- extract st =- case st of- TeePair (_, StepState sL, _, _) (_, StepState sR, _, _) -> do- rL <- extractL sL- rR <- extractR sR- return $ zf rL rR- TeePair (_, StepState sL, _, _) (_, StepResult rR, _, _) -> do- rL <- extractL sL- return $ zf rL rR- TeePair (_, StepResult rL, _, _) (_, StepState sR, _, _) -> do- rR <- extractR sR- return $ zf rL rR- TeePair (_, StepResult rL, _, _) (_, StepResult rR, _, _) ->- return $ zf rL rR---- | See 'Streamly.Internal.Data.Parser.teeWithFst'.------ /Broken/----{-# INLINE teeWithFst #-}-teeWithFst :: Monad m- => (a -> b -> c) -> Parser x m a -> Parser x m b -> Parser x m c-teeWithFst zf (Parser stepL initialL extractL)- (Parser stepR initialR extractR) =- Parser step initial extract-- where-- {-# INLINE_LATE initial #-}- initial = do- resL <- initialL- resR <- initialR- case resL of- IPartial sl ->- return $ case resR of- IPartial sr -> IPartial $ TeePair ([], StepState sl, [], [])- ([], StepState sr, [], [])- IDone br -> IPartial $ TeePair ([], StepState sl, [], [])- ([], StepResult br, [], [])- IError err -> IError err- IDone bl ->- case resR of- IPartial sr -> IDone . zf bl <$> extractR sr- IDone br -> return $ IDone $ zf bl br- IError err -> return $ IError err- IError err -> return $ IError err-- {-# INLINE consume #-}- consume buf inp1 inp2 stp st y = do- let (x, inp11, inp21) =- case inp1 of- [] -> (y, [], [])- z : [] -> (z, reverse (x:inp2), [])- z : zs -> (z, zs, x:inp2)- r <- stp st x- let buf1 = x:buf- return (buf1, r, inp11, inp21)-- -- consume one input item and return the next state of the fold- {-# INLINE useStream #-}- useStream buf inp1 inp2 stp st y = do- (buf1, r, inp11, inp21) <- consume buf inp1 inp2 stp st y- case r of- Partial 0 s ->- let state = ([], StepState s, inp11, inp21)- in return (state, Yld 0)- Partial n _ -> return (undefined, Yld n) -- Not implemented- Done n b ->- let state = (Prelude.take n buf1, StepResult b, inp11, inp21)- in assert (n <= length buf1) (return (state, Stp n))- -- Continue 0 s -> (buf1, Right s, inp11, inp21)- Continue n s ->- let (src0, buf2) = splitAt n buf1- src = Prelude.reverse src0- state = (buf2, StepState s, src ++ inp11, inp21)- in assert (n <= length buf1) (return (state, Skp))- Error err -> return (undefined, Err err)-- {-# INLINE_LATE step #-}- step (TeePair (bufL, StepState sL, inpL1, inpL2)- (bufR, StepState sR, inpR1, inpR2)) x = do- (l,stL) <- useStream bufL inpL1 inpL2 stepL sL x- (r,stR) <- useStream bufR inpR1 inpR2 stepR sR x- let next = TeePair l r- case (stL,stR) of- -- XXX what if the first parser returns an unused count which is- -- more than the second parser's unused count? It does not make- -- sense for the second parser to consume more than the first- -- parser. We reset the input cursor based on the first parser.- -- Error out if the second one has consumed more then the first?- (Stp n1, Stp _) ->- -- Uni-pattern match results in better optimized code compared- -- to a case match.- let (_, StepResult rL, _, _) = l- (_, StepResult rR, _, _) = r- in return $ Done n1 (zf rL rR)- (Stp n1, Yld _) ->- let (_, StepResult rL, _, _) = l- (_, StepState ssR, _, _) = r- in do- rR <- extractR ssR- return $ Done n1 (zf rL rR)- (Yld n1, Yld n2) -> return $ Partial (min n1 n2) next- (Yld n1, Stp n2) -> return $ Partial (min n1 n2) next- (Err err, _) -> return $ Error err- (_, Err err) -> return $ Error err- _ -> return $ Continue 0 next-- step (TeePair (bufL, StepState sL, inpL1, inpL2)- r@(_, StepResult rR, _, _)) x = do- (l,stL) <- useStream bufL inpL1 inpL2 stepL sL x- let next = TeePair l r- -- XXX If the unused count of this stream is lower than the unused- -- count of the stopped stream, only then this will be correct. We need- -- to fix the other case. We need to keep incrementing the unused count- -- of the stopped stream and take the min of the two.- return $ case stL of- Yld n -> Partial n next- Stp n ->- let (_, StepResult rL, _, _) = l- in Done n (zf rL rR)- Skp -> Continue 0 next- Err err -> Error err-- step _ _ = undefined-- {-# INLINE_LATE extract #-}- extract st =- case st of- TeePair (_, StepState sL, _, _) (_, StepState sR, _, _) -> do- rL <- extractL sL- rR <- extractR sR- return $ zf rL rR- TeePair (_, StepState sL, _, _) (_, StepResult rR, _, _) -> do- rL <- extractL sL- return $ zf rL rR- _ -> error "unreachable"---- | See 'Streamly.Internal.Data.Parser.teeWithMin'.------ /Unimplemented/----{-# INLINE teeWithMin #-}-teeWithMin ::- -- Monad m =>- (a -> b -> c) -> Parser x m a -> Parser x m b -> Parser x m c-teeWithMin = undefined------------------------------------------------------------------------------------ Distribute input to two parsers and choose one result------------------------------------------------------------------------------------ | See 'Streamly.Internal.Data.Parser.shortest'.------ /Broken/----{-# INLINE shortest #-}-shortest :: Monad m => Parser x m a -> Parser x m a -> Parser x m a-shortest (Parser stepL initialL extractL) (Parser stepR initialR _) =- Parser step initial extract-- where-- {-# INLINE_LATE initial #-}- initial = do- resL <- initialL- resR <- initialR- return $ case resL of- IPartial sl ->- case resR of- IPartial sr -> IPartial $ TeePair ([], StepState sl, [], [])- ([], StepState sr, [], [])- IDone br -> IDone br- IError err -> IError err- IDone bl -> IDone bl- IError errL ->- case resR of- IPartial _ -> IError errL- IDone br -> IDone br- IError errR -> IError errR-- {-# INLINE consume #-}- consume buf inp1 inp2 stp st y = do- let (x, inp11, inp21) =- case inp1 of- [] -> (y, [], [])- z : [] -> (z, reverse (x:inp2), [])- z : zs -> (z, zs, x:inp2)- r <- stp st x- let buf1 = x:buf- return (buf1, r, inp11, inp21)-- -- consume one input item and return the next state of the fold- {-# INLINE useStream #-}- useStream buf inp1 inp2 stp st y = do- (buf1, r, inp11, inp21) <- consume buf inp1 inp2 stp st y- case r of- Partial 0 s ->- let state = ([], StepState s, inp11, inp21)- in return (state, Yld 0)- Partial n _ -> return (undefined, Yld n) -- Not implemented- Done n b ->- let state = (Prelude.take n buf1, StepResult b, inp11, inp21)- in assert (n <= length buf1) (return (state, Stp n))- -- Continue 0 s -> (buf1, Right s, inp11, inp21)- Continue n s ->- let (src0, buf2) = splitAt n buf1- src = Prelude.reverse src0- state = (buf2, StepState s, src ++ inp11, inp21)- in assert (n <= length buf1) (return (state, Skp))- Error err -> return (undefined, Err err)-- -- XXX Even if a parse finished earlier it may not be shortest if the other- -- parser finishes later but returns a lot of unconsumed input. Our current- -- criterion of shortest is whichever parse decided to stop earlier.- {-# INLINE_LATE step #-}- step (TeePair (bufL, StepState sL, inpL1, inpL2)- (bufR, StepState sR, inpR1, inpR2)) x = do- (l,stL) <- useStream bufL inpL1 inpL2 stepL sL x- (r,stR) <- useStream bufR inpR1 inpR2 stepR sR x- let next = TeePair l r- return $ case (stL,stR) of- (Stp n1, _) ->- let (_, StepResult rL, _, _) = l- in Done n1 rL- (_, Stp n2) ->- let (_, StepResult rR, _, _) = r- in Done n2 rR- (Yld n1, Yld n2) -> Partial (min n1 n2) next- (Err err, _) -> Error err- (_, Err err) -> Error err- _ -> Continue 0 next-- step _ _ = undefined-- {-# INLINE_LATE extract #-}- extract st =- case st of- TeePair (_, StepState sL, _, _) _ -> extractL sL- _ -> error "unreachable"---- | See 'Streamly.Internal.Data.Parser.longest'.------ /Broken/----{-# INLINE longest #-}-longest :: MonadCatch m => Parser x m a -> Parser x m a -> Parser x m a-longest (Parser stepL initialL extractL) (Parser stepR initialR extractR) =- Parser step initial extract-- where--- {-# INLINE_LATE initial #-}- initial = do- resL <- initialL- resR <- initialR- return $ case resL of- IPartial sl ->- case resR of- IPartial sr -> IPartial $ TeePair ([], StepState sl, [], [])- ([], StepState sr, [], [])- IDone br -> IPartial $ TeePair ([], StepState sl, [], [])- ([], StepResult br, [], [])- IError _ ->- IPartial $ TeePair ([], StepState sl, [], [])- ([], StepResult undefined, [], [])- IDone bl ->- case resR of- IPartial sr ->- IPartial $ TeePair ([], StepResult bl, [], [])- ([], StepState sr, [], [])- IDone _ -> IDone bl- IError _ -> IDone bl- IError _ ->- case resR of- IPartial sr ->- IPartial $ TeePair ([], StepResult undefined, [], [])- ([], StepState sr, [], [])- IDone br -> IDone br- IError err -> IError err-- {-# INLINE consume #-}- consume buf inp1 inp2 stp st y = do- let (x, inp11, inp21) =- case inp1 of- [] -> (y, [], [])- z : [] -> (z, reverse (x:inp2), [])- z : zs -> (z, zs, x:inp2)- r <- stp st x- let buf1 = x:buf- return (buf1, r, inp11, inp21)-- -- consume one input item and return the next state of the fold- {-# INLINE useStream #-}- useStream buf inp1 inp2 stp st y = do- (buf1, r, inp11, inp21) <- consume buf inp1 inp2 stp st y- case r of- Partial 0 s ->- let state = ([], StepState s, inp11, inp21)- in return (state, Yld 0)- Partial n _ -> return (undefined, Yld n) -- Not implemented- Done n b ->- let state = (Prelude.take n buf1, StepResult b, inp11, inp21)- in assert (n <= length buf1) (return (state, Stp n))- -- Continue 0 s -> (buf1, Right s, inp11, inp21)- Continue n s ->- let (src0, buf2) = splitAt n buf1- src = Prelude.reverse src0- state = (buf2, StepState s, src ++ inp11, inp21)- in assert (n <= length buf1) (return (state, Skp))- Error err -> return (undefined, Err err)-- {-# INLINE_LATE step #-}- step (TeePair (bufL, StepState sL, inpL1, inpL2)- (bufR, StepState sR, inpR1, inpR2)) x = do- (l,stL) <- useStream bufL inpL1 inpL2 stepL sL x- (r,stR) <- useStream bufR inpR1 inpR2 stepR sR x- let next = TeePair l r- return $ case (stL,stR) of- (Yld n1, Yld n2) -> Partial (min n1 n2) next- (Yld n1, Stp n2) -> Partial (min n1 n2) next- (Stp n1, Yld n2) -> Partial (min n1 n2) next- (Stp n1, Stp n2) ->- let (_, StepResult rL, _, _) = l- (_, StepResult rR, _, _) = r- in Done (max n1 n2) (if n1 >= n2 then rL else rR)- (Err err, _) -> Error err- (_, Err err) -> Error err- _ -> Continue 0 next-- -- XXX the parser that finishes last may not be the longest because it may- -- return a lot of unused input which makes it shorter. Our current- -- criterion of deciding longest is based on whoever decides to finish- -- last and not whoever consumed more input.- --- -- To actually know who made more progress we need to keep an account of- -- how many items are unconsumed since the last yield.- --- step (TeePair (bufL, StepState sL, inpL1, inpL2)- r@(_, StepResult _, _, _)) x = do- (l,stL) <- useStream bufL inpL1 inpL2 stepL sL x- let next = TeePair l r- return $ case stL of- Yld n -> Partial n next- Stp n ->- let (_, StepResult rL, _, _) = l- in Done n rL- Skp -> Continue 0 next- Err err -> Error err-- step (TeePair l@(_, StepResult _, _, _)- (bufR, StepState sR, inpR1, inpR2)) x = do- (r, stR) <- useStream bufR inpR1 inpR2 stepR sR x- let next = TeePair l r- return $ case stR of- Yld n -> Partial n next- Stp n ->- let (_, StepResult rR, _, _) = r- in Done n rR- Skp -> Continue 0 next- Err err -> Error err-- step _ _ = undefined-- {-# INLINE_LATE extract #-}- extract st =- -- XXX When results are partial we may not be able to precisely compare- -- which parser has made more progress till now. One way to do that is- -- to figure out the actually consumed input up to the last yield.- --- case st of- TeePair (_, StepState sL, _, _) (_, StepState sR, _, _) -> do- r <- try $ extractL sL- case r of- Left (_ :: ParseError) -> extractR sR- Right b -> return b- TeePair (_, StepState sL, _, _) (_, StepResult rR, _, _) -> do- r <- try $ extractL sL- case r of- Left (_ :: ParseError) -> return rR- Right b -> return b- TeePair (_, StepResult rL, _, _) (_, StepState sR, _, _) -> do- r <- try $ extractR sR- case r of- Left (_ :: ParseError) -> return rL- Right b -> return b- TeePair (_, StepResult _, _, _) (_, StepResult _, _, _) ->- error "unreachable"--}
− src/Streamly/Internal/Data/Parser/ParserD/Type.hs
@@ -1,1429 +0,0 @@-{-# LANGUAGE CPP #-}--- |--- Module : Streamly.Internal.Data.Parser.ParserD.Type--- Copyright : (c) 2020 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ Streaming and backtracking parsers.------ Parsers just extend folds. Please read the 'Fold' design notes in--- "Streamly.Internal.Data.Fold.Type" for background on the design.------ = Parser Design------ The 'Parser' type or a parsing fold is a generalization of the 'Fold' type.--- The 'Fold' type /always/ succeeds on each input. Therefore, it does not need--- to buffer the input. In contrast, a 'Parser' may fail and backtrack to--- replay the input again to explore another branch of the parser. Therefore,--- it needs to buffer the input. Therefore, a 'Parser' is a fold with some--- additional requirements. To summarize, unlike a 'Fold', a 'Parser':------ 1. may not generate a new value of the accumulator on every input, it may--- generate a new accumulator only after consuming multiple input elements--- (e.g. takeEQ).--- 2. on success may return some unconsumed input (e.g. takeWhile)--- 3. may fail and return all input without consuming it (e.g. satisfy)--- 4. backtrack and start inspecting the past input again (e.g. alt)------ These use cases require buffering and replaying of input. To facilitate--- this, the step function of the 'Fold' is augmented to return the next state--- of the fold along with a command tag using a 'Step' functor, the tag tells--- the fold driver to manipulate the future input as the parser wishes. The--- 'Step' functor provides the following commands to the fold driver--- corresponding to the use cases outlined in the previous para:------ 1. 'Continue': buffer the current input and optionally go back to a previous--- position in the stream--- 2. 'Partial': buffer the current input and optionally go back to a previous--- position in the stream, drop the buffer before that position.--- 3. 'Done': parser succeeded, returns how much input was leftover--- 4. 'Error': indicates that the parser has failed without a result------ = How a Parser Works?------ A parser is just like a fold, it keeps consuming inputs from the stream and--- accumulating them in an accumulator. The accumulator of the parser could be--- a singleton value or it could be a collection of values e.g. a list.------ The parser may build a new output value from multiple input items. When it--- consumes an input item but needs more input to build a complete output item--- it uses @Continue 0 s@, yielding the intermediate state @s@ and asking the--- driver to provide more input. When the parser determines that a new output--- value is complete it can use a @Done n b@ to terminate the parser with @n@--- items of input unused and the final value of the accumulator returned as--- @b@. If at any time the parser determines that the parse has failed it can--- return @Error err@.------ A parser building a collection of values (e.g. a list) can use the @Partial@--- constructor whenever a new item in the output collection is generated. If a--- parser building a collection of values has yielded at least one value then--- it is considered successful and cannot fail after that. In the current--- implementation, this is not automatically enforced, there is a rule that the--- parser MUST use only @Done@ for termination after the first @Partial@, it--- cannot use @Error@. It may be possible to change the implementation so that--- this rule is not required, but there may be some performance cost to it.------ 'Streamly.Internal.Data.Parser.takeWhile' and--- 'Streamly.Internal.Data.Parser.some' combinators are good examples of--- efficient implementations using all features of this representation. It is--- possible to idiomatically build a collection of parsed items using a--- singleton parser and @Alternative@ instance instead of using a--- multi-yield parser. However, this implementation is amenable to stream--- fusion and can therefore be much faster.------ = Error Handling------ When a parser's @step@ function is invoked it may terminate by either a--- 'Done' or an 'Error' return value. In an 'Alternative' composition an error--- return can make the composed parser backtrack and try another parser.------ If the stream stops before a parser could terminate then we use the--- @extract@ function of the parser to retrieve the last yielded value of the--- parser. If the parser has yielded at least one value then @extract@ MUST--- return a value without throwing an error, otherwise it uses the 'ParseError'--- exception to throw an error.------ We chose the exception throwing mechanism for @extract@ instead of using an--- explicit error return via an 'Either' type for keeping the interface simple--- as most of the time we do not need to catch the error in intermediate--- layers. Note that we cannot use exception throwing mechanism in @step@--- function because of performance reasons. 'Error' constructor in that case--- allows loop fusion and better performance.------ = Optimizing backtracking------ == Applicative Composition------ If a parser once returned 'Partial' it can never fail after that. This is--- used to reduce the buffering. A 'Partial' results in dropping the buffer and--- we cannot backtrack before that point.------ Parsers can be composed using an Alternative, if we are in an alternative--- composition we may have to backtrack to try the other branch. When we--- compose two parsers using applicative @f <$> p1 <*> p2@ we can return a--- 'Partial' result only after both the parsers have succeeded. While running--- @p1@ we have to ensure that the input is not dropped until we have run @p2@,--- therefore we have to return a Continue instead of a Partial.------ However, if we know they both cannot fail then we know that the composed--- parser can never fail. For this reason we should have "backtracking folds"--- as a separate type so that we can compose them in an efficient manner. In p1--- itself we can drop the buffer as soon as a 'Partial' result arrives. In--- fact, there is no Alternative composition for folds because they cannot--- fail.------ == Alternative Composition------ In @p1 <|> p2@ as soon as the parser p1 returns 'Partial' we know that it--- will not fail and we can immediately drop the buffer.------ If we are not using the parser in an alternative composition we can--- downgrade the parser to a backtracking fold and use the "backtracking--- fold"'s applicative for more efficient implementation. To downgrade we can--- translate the "Error" of parser to an exception. This gives us best of both--- worlds, the applicative as well as alternative would have optimal--- backtracking buffer.------ The "many" for parsers would be different than "many" for folds. In case of--- folds an error would be propagated. In case of parsers the error would be--- ignored.------ = Implementation Approach------ Backtracking folds have an issue with tee style composition because each--- fold can backtrack independently, we will need independent buffers. Though--- this may be possible to implement it may not be efficient especially for--- folds that do not backtrack at all. Three types are possible, optimized for--- different use cases:------ * Non-backtracking folds: efficient Tee--- * Backtracking folds: efficient applicative--- * Parsers: alternative------ Downgrade parsers to backtracking folds for applicative used without--- alternative. Upgrade backtracking folds to parsers when we have to use them--- as the last alternative.------ = Future Work------ It may make sense to move "takeWhile" type of parsers, which cannot fail but--- need some lookahead, to splitting folds. This will allow such combinators--- to be accepted where we need an unfailing "Fold" type.------ Based on application requirements it should be possible to design even a--- richer interface to manipulate the input stream/buffer. For example, we--- could randomly seek into the stream in the forward or reverse directions or--- we can even seek to the end or from the end or seek from the beginning.------ We can distribute and scan/parse a stream using both folds and parsers and--- merge the resulting streams using different merge strategies (e.g.--- interleaving or serial).------ == Naming------ As far as possible, try that the names of the combinators in this module are--- consistent with:------ * <https://hackage.haskell.org/package/base/docs/Text-ParserCombinators-ReadP.html base/Text.ParserCombinators.ReadP>--- * <http://hackage.haskell.org/package/parser-combinators parser-combinators>--- * <http://hackage.haskell.org/package/megaparsec megaparsec>--- * <http://hackage.haskell.org/package/attoparsec attoparsec>--- * <http://hackage.haskell.org/package/parsec parsec>--module Streamly.Internal.Data.Parser.ParserD.Type- (- -- * Setup- -- $setup-- -- * Types- Initial (..)- , Step (..)- , extractStep- , bimapOverrideCount- , Parser (..)- , ParseError (..)- , rmapM-- -- * Constructors-- , fromPure- , fromEffect- , splitWith- , split_-- , die- , dieM- , splitSome -- parseSome?- , splitMany -- parseMany?- , splitManyPost- , alt- , concatMap-- -- * Input transformation- , lmap- , lmapM- , filter-- , noErrorUnsafeSplitWith- , noErrorUnsafeSplit_- , noErrorUnsafeConcatMap- )-where--#include "inline.hs"-#include "assert.hs"--import Control.Applicative (Alternative(..), liftA2)-import Control.Exception (Exception(..))--- import Control.Monad (MonadPlus(..), (>=>))-import Control.Monad ((>=>))-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Bifunctor (Bifunctor(..))-import Fusion.Plugin.Types (Fuse(..))-import Streamly.Internal.Data.Fold.Type (Fold(..), toList)--import qualified Control.Monad.Fail as Fail-import qualified Streamly.Internal.Data.Fold.Type as FL--import Prelude hiding (concatMap, filter)--#include "DocTestDataParser.hs"---- XXX The only differences between Initial and Step types are:------ * There are no backtracking counts in Initial--- * Continue and Partial are the same. Ideally Partial should mean that an--- empty result is valid and can be extracted; and Continue should mean that--- empty would result in an error on extraction. We can possibly distinguish--- the two cases.------ If we ignore the backtracking counts we can represent the Initial type using--- Step itself. That will also simplify the implementation of various parsers--- where the processing in intiial is just a sepcial case of step, see--- takeBetween for example.---- | The type of a 'Parser''s initial action.------ /Internal/----{-# ANN type Initial Fuse #-}-data Initial s b- = IPartial !s -- ^ Wait for step function to be called with state @s@.- | IDone !b -- ^ Return a result right away without an input.- | IError !String -- ^ Return an error right away without an input.---- | @first@ maps on 'IPartial' and @second@ maps on 'IDone'.------ /Internal/----instance Bifunctor Initial where- {-# INLINE bimap #-}- bimap f _ (IPartial a) = IPartial (f a)- bimap _ g (IDone b) = IDone (g b)- bimap _ _ (IError err) = IError err---- | Maps a function over the result held by 'IDone'.------ >>> fmap = second------ /Internal/----instance Functor (Initial s) where- {-# INLINE fmap #-}- fmap = second---- We can simplify the Step type as follows:------ Partial Int (Either s (s, b)) -- Left continue, right partial result--- Done Int (Either String b)------ In this case Error may also have a "leftover" return. This means that after--- several successful partial results the last segment parsing failed and we--- are returning the leftover of that. The driver may choose to restart from--- the last segment where this parser failed or from the beginning.------ Folds can only return the right values. Parsers can also return lefts.---- | The return type of a 'Parser' step.------ The parse operation feeds the input stream to the parser one element at a--- time, representing a parse 'Step'. The parser may or may not consume the--- item and returns a result. If the result is 'Partial' we can either extract--- the result or feed more input to the parser. If the result is 'Continue', we--- must feed more input in order to get a result. If the parser returns 'Done'--- then the parser can no longer take any more input.------ If the result is 'Continue', the parse operation retains the input in a--- backtracking buffer, in case the parser may ask to backtrack in future.--- Whenever a 'Partial n' result is returned we first backtrack by @n@ elements--- in the input and then release any remaining backtracking buffer. Similarly,--- 'Continue n' backtracks to @n@ elements before the current position and--- starts feeding the input from that point for future invocations of the--- parser.------ If parser is not yet done, we can use the @extract@ operation on the @state@--- of the parser to extract a result. If the parser has not yet yielded a--- result, the operation fails with a 'ParseError' exception. If the parser--- yielded a 'Partial' result in the past the last partial result is returned.--- Therefore, if a parser yields a partial result once it cannot fail later on.------ The parser can never backtrack beyond the position where the last partial--- result left it at. The parser must ensure that the backtrack position is--- always after that.------ /Pre-release/----{-# ANN type Step Fuse #-}-data Step s b =- Partial !Int !s- -- ^ @Partial count state@. The following hold on Partial result:- --- -- 1. @extract@ on @state@ would succeed and give a result.- -- 2. Input stream position is reset to @current position - count@.- -- 3. All input before the new position is dropped. The parser can- -- never backtrack beyond this position.-- | Continue !Int !s- -- ^ @Continue count state@. The following hold on a Continue result:- --- -- 1. If there was a 'Partial' result in past, @extract@ on @state@ would- -- give that result as 'Done' otherwise it may return 'Error' or- -- 'Continue'.- -- 2. Input stream position is reset to @current position - count@.- -- 3. the input is retained in a backtrack buffer.-- | Done !Int !b- -- ^ Done with leftover input count and result.- --- -- @Done count result@ means the parser has finished, it will accept no- -- more input, last @count@ elements from the input are unused and the- -- result of the parser is in @result@.-- | Error !String- -- ^ Parser failed without generating any output.- --- -- The parsing operation may backtrack to the beginning and try another- -- alternative.---- | Map first function over the state and second over the result.-instance Bifunctor Step where- {-# INLINE bimap #-}- bimap f g step =- case step of- Partial n s -> Partial n (f s)- Continue n s -> Continue n (f s)- Done n b -> Done n (g b)- Error err -> Error err---- | Bimap discarding the count, and using the supplied count instead.-bimapOverrideCount :: Int -> (s -> s1) -> (b -> b1) -> Step s b -> Step s1 b1-bimapOverrideCount n f g step =- case step of- Partial _ s -> Partial n (f s)- Continue _ s -> Continue n (f s)- Done _ b -> Done n (g b)- Error err -> Error err---- | fmap = second-instance Functor (Step s) where- {-# INLINE fmap #-}- fmap = second--{-# INLINE assertStepCount #-}-assertStepCount :: Int -> Step s b -> Step s b-assertStepCount i step =- case step of- Partial n _ -> assert (i == n) step- Continue n _ -> assert (i == n) step- Done n _ -> assert (i == n) step- Error _ -> step---- | Map an extract function over the state of Step----{-# INLINE extractStep #-}-extractStep :: Monad m => (s -> m (Step s1 b)) -> Step s b -> m (Step s1 b)-extractStep f res =- case res of- Partial n s1 -> assertStepCount n <$> f s1- Done n b -> return $ Done n b- Continue n s1 -> assertStepCount n <$> f s1- Error err -> return $ Error err---- | Map a monadic function over the result @b@ in @Step s b@.------ /Internal/-{-# INLINE mapMStep #-}-mapMStep :: Applicative m => (a -> m b) -> Step s a -> m (Step s b)-mapMStep f res =- case res of- Partial n s -> pure $ Partial n s- Done n b -> Done n <$> f b- Continue n s -> pure $ Continue n s- Error err -> pure $ Error err---- | A parser is a fold that can fail and is represented as @Parser step--- initial extract@. Before we drive a parser we call the @initial@ action to--- retrieve the initial state of the fold. The parser driver invokes @step@--- with the state returned by the previous step and the next input element. It--- results into a new state and a command to the driver represented by 'Step'--- type. The driver keeps invoking the step function until it stops or fails.--- At any point of time the driver can call @extract@ to inspect the result of--- the fold. If the parser hits the end of input 'extract' is called.--- It may result in an error or an output value.------ /Pre-release/----data Parser a m b =- forall s. Parser- (s -> a -> m (Step s b))- -- Initial cannot return "Partial/Done n" or "Continue". Continue 0 is- -- same as Partial 0. In other words it cannot backtrack.- (m (Initial s b))- -- Extract can only return Partial or Continue n. In other words it can- -- only backtrack or return partial result/error. But we do not return- -- result in Partial, therefore, we have to use Done instead of Partial.- (s -> m (Step s b))---- | This exception is used when a parser ultimately fails, the user of the--- parser is intimated via this exception.------ /Pre-release/----newtype ParseError = ParseError String- deriving Show--instance Exception ParseError where- displayException (ParseError err) = err--instance Functor m => Functor (Parser a m) where- {-# INLINE fmap #-}- fmap f (Parser step1 initial1 extract) =- Parser step initial (fmap3 f extract)-- where-- initial = fmap2 f initial1- step s b = fmap2 f (step1 s b)- fmap2 g = fmap (fmap g)- fmap3 g = fmap2 (fmap g)----------------------------------------------------------------------------------- Mapping on the output----------------------------------------------------------------------------------- | @rmapM f parser@ maps the monadic function @f@ on the output of the parser.------ >>> rmap = fmap-{-# INLINE rmapM #-}-rmapM :: Monad m => (b -> m c) -> Parser a m b -> Parser a m c-rmapM f (Parser step initial extract) =- Parser step1 initial1 (extract >=> mapMStep f)-- where-- initial1 = do- res <- initial- -- this is mapM f over result- case res of- IPartial x -> return $ IPartial x- IDone a -> IDone <$> f a- IError err -> return $ IError err- step1 s a = step s a >>= mapMStep f---- | A parser that always yields a pure value without consuming any input.----{-# INLINE_NORMAL fromPure #-}-fromPure :: Monad m => b -> Parser a m b-fromPure b = Parser undefined (pure $ IDone b) undefined---- | A parser that always yields the result of an effectful action without--- consuming any input.----{-# INLINE fromEffect #-}-fromEffect :: Monad m => m b -> Parser a m b-fromEffect b = Parser undefined (IDone <$> b) undefined------------------------------------------------------------------------------------ Sequential applicative----------------------------------------------------------------------------------{-# ANN type SeqParseState Fuse #-}-data SeqParseState sl f sr = SeqParseL !sl | SeqParseR !f !sr---- Note: this implementation of splitWith is fast because of stream fusion but--- has quadratic time complexity, because each composition adds a new branch--- that each subsequent parse's input element has to go through, therefore, it--- cannot scale to a large number of compositions. After around 100--- compositions the performance starts dipping rapidly beyond a CPS style--- unfused implementation.------ Note: This is a parsing dual of appending streams using--- 'Streamly.Data.Stream.append', it splits the streams using two parsers and--- zips the results.---- | Sequential parser application.------ Apply two parsers sequentially to an input stream. The first parser runs and--- processes the input, the remaining input is then passed to the second--- parser. If both parsers succeed, their outputs are combined using the--- supplied function. If either parser fails, the operation fails.------ This implementation is strict in the second argument, therefore, the--- following will fail:------ >>> Stream.parse (Parser.splitWith const (Parser.satisfy (> 0)) undefined) $ Stream.fromList [1]--- *** Exception: Prelude.undefined--- ...------ Although this implementation allows stream fusion, it has quadratic--- complexity, making it suitable only for a small number of compositions.--- As a thumb rule use it for less than 8 compositions, use ParserK otherwise.------ Below are some common idioms that can be expressed using 'splitWith' and--- other parser primitives:------ >>> span p f1 f2 = Parser.splitWith (,) (Parser.takeWhile p f1) (Parser.fromFold f2)--- >>> spanBy eq f1 f2 = Parser.splitWith (,) (Parser.groupBy eq f1) (Parser.fromFold f2)------ /Pre-release/----{-# INLINE splitWith #-}-splitWith :: Monad m- => (a -> b -> c) -> Parser x m a -> Parser x m b -> Parser x m c-splitWith func (Parser stepL initialL extractL)- (Parser stepR initialR extractR) =- Parser step initial extract-- where-- initial = do- -- XXX We can use bimap here if we make this a Step type- resL <- initialL- case resL of- IPartial sl -> return $ IPartial $ SeqParseL sl- IDone bl -> do- resR <- initialR- -- XXX We can use bimap here if we make this a Step type- return $ case resR of- IPartial sr -> IPartial $ SeqParseR (func bl) sr- IDone br -> IDone (func bl br)- IError err -> IError err- IError err -> return $ IError err-- -- Note: For the composed parse to terminate, the left parser has to be- -- a terminating parser returning a Done at some point.- step (SeqParseL st) a = do- -- Important: Please do not use Applicative here. See- -- https://github.com/composewell/streamly/issues/1033 and the problem- -- defined in split_ for more info.- -- XXX Use bimap- resL <- stepL st a- case resL of- -- Note: We need to buffer the input for a possible Alternative- -- e.g. in ((,) <$> p1 <*> p2) <|> p3, if p2 fails we have to- -- backtrack and start running p3. So we need to keep the input- -- buffered until we know that the applicative cannot fail.- Partial n s -> return $ Continue n (SeqParseL s)- Continue n s -> return $ Continue n (SeqParseL s)- Done n b -> do- -- XXX Use bimap if we make this a Step type- -- fmap (bimap (SeqParseR (func b)) (func b)) initialR- initR <- initialR- return $ case initR of- IPartial sr -> Continue n $ SeqParseR (func b) sr- IDone br -> Done n (func b br)- IError err -> Error err- Error err -> return $ Error err-- step (SeqParseR f st) a = fmap (bimap (SeqParseR f) f) (stepR st a)-- extract (SeqParseR f sR) = fmap (bimap (SeqParseR f) f) (extractR sR)- extract (SeqParseL sL) = do- -- XXX Use bimap here- rL <- extractL sL- case rL of- Done n bL -> do- -- XXX Use bimap here if we use Step type in Initial- iR <- initialR- case iR of- IPartial sR -> do- fmap- (bimap (SeqParseR (func bL)) (func bL))- (extractR sR)- IDone bR -> return $ Done n $ func bL bR- IError err -> return $ Error err- Error err -> return $ Error err- Partial _ _ -> error "Bug: splitWith extract 'Partial'"- Continue n s -> return $ Continue n (SeqParseL s)------------------------------------------------------------------------------------ Sequential applicative for backtracking folds------------------------------------------------------------------------------------ XXX Create a newtype for nonfailing parsers and downgrade the parser to that--- type before this operation and then upgrade.------ We can do an inspection testing to reject unwanted constructors at compile--- time.------ We can use the compiler to automatically annotate accumulators, terminating--- folds, non-failing parsers and failing parsers.---- | Works correctly only if both the parsers are guaranteed to never fail.-{-# INLINE noErrorUnsafeSplitWith #-}-noErrorUnsafeSplitWith :: Monad m- => (a -> b -> c) -> Parser x m a -> Parser x m b -> Parser x m c-noErrorUnsafeSplitWith func (Parser stepL initialL extractL)- (Parser stepR initialR extractR) =- Parser step initial extract-- where-- errMsg e = error $ "noErrorUnsafeSplitWith: unreachable: " ++ e-- initial = do- resL <- initialL- case resL of- IPartial sl -> return $ IPartial $ SeqParseL sl- IDone bl -> do- resR <- initialR- return $ bimap (SeqParseR (func bl)) (func bl) resR- IError err -> errMsg err-- -- Note: For the composed parse to terminate, the left parser has to be- -- a terminating parser returning a Done at some point.- step (SeqParseL st) a = do- r <- stepL st a- case r of- -- Assume that the parser can never fail, therefore, we do not- -- need to keep the input for backtracking.- Partial n s -> return $ Partial n (SeqParseL s)- Continue n s -> return $ Continue n (SeqParseL s)- Done n b -> do- res <- initialR- return- $ case res of- IPartial sr -> Partial n $ SeqParseR (func b) sr- IDone br -> Done n (func b br)- IError err -> errMsg err- Error err -> errMsg err-- step (SeqParseR f st) a = fmap (bimap (SeqParseR f) f) (stepR st a)-- extract (SeqParseR f sR) = fmap (bimap (SeqParseR f) f) (extractR sR)-- extract (SeqParseL sL) = do- rL <- extractL sL- case rL of- Done n bL -> do- iR <- initialR- case iR of- IPartial sR -> do- rR <- extractR sR- return- $ bimapOverrideCount- n (SeqParseR (func bL)) (func bL) rR- IDone bR -> return $ Done n $ func bL bR- IError err -> errMsg err- Error err -> errMsg err- Partial _ _ -> errMsg "Partial"- Continue n s -> return $ Continue n (SeqParseL s)--{-# ANN type SeqAState Fuse #-}-data SeqAState sl sr = SeqAL !sl | SeqAR !sr---- This turns out to be slightly faster than splitWith---- | Sequential parser application ignoring the output of the first parser.--- Apply two parsers sequentially to an input stream. The input is provided to--- the first parser, when it is done the remaining input is provided to the--- second parser. The output of the parser is the output of the second parser.--- The operation fails if any of the parsers fail.------ This implementation is strict in the second argument, therefore, the--- following will fail:------ >>> Stream.parse (Parser.split_ (Parser.satisfy (> 0)) undefined) $ Stream.fromList [1]--- *** Exception: Prelude.undefined--- ...------ Although this implementation allows stream fusion, it has quadratic--- complexity, making it suitable only for a small number of compositions.--- As a thumb rule use it for less than 8 compositions, use ParserK otherwise.------ /Pre-release/----{-# INLINE split_ #-}-split_ :: Monad m => Parser x m a -> Parser x m b -> Parser x m b-split_ (Parser stepL initialL extractL) (Parser stepR initialR extractR) =- Parser step initial extract-- where-- initial = do- resL <- initialL- case resL of- IPartial sl -> return $ IPartial $ SeqAL sl- IDone _ -> do- resR <- initialR- return $ first SeqAR resR- IError err -> return $ IError err-- -- Note: For the composed parse to terminate, the left parser has to be- -- a terminating parser returning a Done at some point.- step (SeqAL st) a = do- -- Important: Do not use Applicative here. Applicative somehow caused- -- the right action to run many times, not sure why though.- resL <- stepL st a- case resL of- -- Note: this leads to buffering even if we are not in an- -- Alternative composition.- Partial n s -> return $ Continue n (SeqAL s)- Continue n s -> return $ Continue n (SeqAL s)- Done n _ -> do- initR <- initialR- return $ case initR of- IPartial s -> Continue n (SeqAR s)- IDone b -> Done n b- IError err -> Error err- Error err -> return $ Error err-- step (SeqAR st) a = first SeqAR <$> stepR st a-- extract (SeqAR sR) = fmap (first SeqAR) (extractR sR)- extract (SeqAL sL) = do- rL <- extractL sL- case rL of- Done n _ -> do- iR <- initialR- -- XXX For initial we can have a bimap with leftover.- case iR of- IPartial sR ->- fmap (bimapOverrideCount n SeqAR id) (extractR sR)- IDone bR -> return $ Done n bR- IError err -> return $ Error err- Error err -> return $ Error err- Partial _ _ -> error "split_: Partial"- Continue n s -> return $ Continue n (SeqAL s)---- For backtracking folds-{-# INLINE noErrorUnsafeSplit_ #-}-noErrorUnsafeSplit_ :: Monad m => Parser x m a -> Parser x m b -> Parser x m b-noErrorUnsafeSplit_- (Parser stepL initialL extractL) (Parser stepR initialR extractR) =- Parser step initial extract-- where-- errMsg e = error $ "noErrorUnsafeSplit_: unreachable: " ++ e-- initial = do- resL <- initialL- case resL of- IPartial sl -> return $ IPartial $ SeqAL sl- IDone _ -> do- resR <- initialR- return $ first SeqAR resR- IError err -> errMsg err-- -- Note: For the composed parse to terminate, the left parser has to be- -- a terminating parser returning a Done at some point.- step (SeqAL st) a = do- -- Important: Please do not use Applicative here. Applicative somehow- -- caused the next action to run many times in the "tar" parsing code,- -- not sure why though.- resL <- stepL st a- case resL of- Partial n s -> return $ Partial n (SeqAL s)- Continue n s -> return $ Continue n (SeqAL s)- Done n _ -> do- initR <- initialR- return $ case initR of- IPartial s -> Partial n (SeqAR s)- IDone b -> Done n b- IError err -> errMsg err- Error err -> errMsg err-- step (SeqAR st) a = first SeqAR <$> stepR st a-- extract (SeqAR sR) = fmap (first SeqAR) (extractR sR)- extract (SeqAL sL) = do- rL <- extractL sL- case rL of- Done n _ -> do- iR <- initialR- case iR of- IPartial sR -> do- fmap (bimapOverrideCount n SeqAR id) (extractR sR)- IDone bR -> return $ Done n bR- IError err -> errMsg err- Error err -> errMsg err- Partial _ _ -> error "split_: Partial"- Continue n s -> return $ Continue n (SeqAL s)---- | 'Applicative' form of 'splitWith'.-instance Monad m => Applicative (Parser a m) where- {-# INLINE pure #-}- pure = fromPure-- {-# INLINE (<*>) #-}- (<*>) = splitWith id-- {-# INLINE (*>) #-}- (*>) = split_-- {-# INLINE liftA2 #-}- liftA2 f x = (<*>) (fmap f x)------------------------------------------------------------------------------------ Sequential Alternative----------------------------------------------------------------------------------{-# ANN type AltParseState Fuse #-}-data AltParseState sl sr = AltParseL !Int !sl | AltParseR !sr---- Note: this implementation of alt is fast because of stream fusion but has--- quadratic time complexity, because each composition adds a new branch that--- each subsequent alternative's input element has to go through, therefore, it--- cannot scale to a large number of compositions---- | Sequential alternative. The input is first passed to the first parser, and--- if it succeeds, the result is returned. However, if the first parser fails,--- the parser driver backtracks and tries the same input on the second parser,--- returning the result if it succeeds.------ Note: This implementation is not lazy in the second argument. The following--- will fail:------ >> Stream.parse (Parser.satisfy (> 0) `Parser.alt` undefined) $ Stream.fromList [1..10]--- *** Exception: Prelude.undefined------ Although this implementation allows stream fusion, it has quadratic--- complexity, making it suitable only for a small number of compositions.--- As a thumb rule use it for less than 8 compositions, use ParserK otherwise.------ /Time Complexity:/ O(n^2) where n is the number of compositions.------ /Pre-release/----{-# INLINE alt #-}-alt :: Monad m => Parser x m a -> Parser x m a -> Parser x m a-alt (Parser stepL initialL extractL) (Parser stepR initialR extractR) =- Parser step initial extract-- where-- initial = do- resL <- initialL- case resL of- IPartial sl -> return $ IPartial $ AltParseL 0 sl- IDone bl -> return $ IDone bl- IError _ -> do- resR <- initialR- return $ case resR of- IPartial sr -> IPartial $ AltParseR sr- IDone br -> IDone br- IError err -> IError err-- -- Once a parser yields at least one value it cannot fail. This- -- restriction helps us make backtracking more efficient, as we do not need- -- to keep the consumed items buffered after a yield. Note that we do not- -- enforce this and if a misbehaving parser does not honor this then we can- -- get unexpected results. XXX Can we detect and flag this?- step (AltParseL cnt st) a = do- r <- stepL st a- case r of- Partial n s -> return $ Partial n (AltParseL 0 s)- Continue n s -> do- assertM(cnt + 1 - n >= 0)- return $ Continue n (AltParseL (cnt + 1 - n) s)- Done n b -> return $ Done n b- Error _ -> do- res <- initialR- return- $ case res of- IPartial rR -> Continue (cnt + 1) (AltParseR rR)- IDone b -> Done (cnt + 1) b- IError err -> Error err-- step (AltParseR st) a = do- r <- stepR st a- return $ case r of- Partial n s -> Partial n (AltParseR s)- Continue n s -> Continue n (AltParseR s)- Done n b -> Done n b- Error err -> Error err-- extract (AltParseR sR) = fmap (first AltParseR) (extractR sR)-- extract (AltParseL cnt sL) = do- rL <- extractL sL- case rL of- Done n b -> return $ Done n b- Error _ -> do- res <- initialR- return- $ case res of- IPartial rR -> Continue cnt (AltParseR rR)- IDone b -> Done cnt b- IError err -> Error err- Partial _ _ -> error "Bug: alt: extractL 'Partial'"- Continue n s -> do- assertM(n == cnt)- return $ Continue n (AltParseL 0 s)--{-# ANN type Fused3 Fuse #-}-data Fused3 a b c = Fused3 !a !b !c---- | See documentation of 'Streamly.Internal.Data.Parser.many'.------ /Pre-release/----{-# INLINE splitMany #-}-splitMany :: Monad m => Parser a m b -> Fold m b c -> Parser a m c-splitMany (Parser step1 initial1 extract1) (Fold fstep finitial fextract) =- Parser step initial extract-- where-- -- Caution! There is mutual recursion here, inlining the right functions is- -- important.-- handleCollect partial done fres =- case fres of- FL.Partial fs -> do- pres <- initial1- case pres of- IPartial ps -> return $ partial $ Fused3 ps 0 fs- IDone pb ->- runCollectorWith (handleCollect partial done) fs pb- IError _ -> done <$> fextract fs- FL.Done fb -> return $ done fb-- runCollectorWith cont fs pb = fstep fs pb >>= cont-- -- See notes in Fold.many for the reason why the parser must be initialized- -- right away instead of on first input.- initial = finitial >>= handleCollect IPartial IDone-- {-# INLINE step #-}- step (Fused3 st cnt fs) a = do- r <- step1 st a- let cnt1 = cnt + 1- case r of- Partial n s -> do- assertM(cnt1 - n >= 0)- return $ Continue n (Fused3 s (cnt1 - n) fs)- Continue n s -> do- assertM(cnt1 - n >= 0)- return $ Continue n (Fused3 s (cnt1 - n) fs)- Done n b -> do- assertM(cnt1 - n >= 0)- fstep fs b >>= handleCollect (Partial n) (Done n)- Error _ -> do- xs <- fextract fs- return $ Done cnt xs-- extract (Fused3 _ 0 fs) = fmap (Done 0) (fextract fs)- extract (Fused3 s cnt fs) = do- r <- extract1 s- case r of- Error _ -> fmap (Done cnt) (fextract fs)- Done n b -> do- assertM(n <= cnt)- fs1 <- fstep fs b- case fs1 of- FL.Partial s1 -> fmap (Done n) (fextract s1)- FL.Done b1 -> return (Done n b1)- Partial _ _ -> error "splitMany: Partial in extract"- Continue n s1 -> do- assertM(n == cnt)- return (Continue n (Fused3 s1 0 fs))---- | Like splitMany, but inner fold emits an output at the end even if no input--- is received.------ /Internal/----{-# INLINE splitManyPost #-}-splitManyPost :: Monad m => Parser a m b -> Fold m b c -> Parser a m c-splitManyPost (Parser step1 initial1 extract1) (Fold fstep finitial fextract) =- Parser step initial extract-- where-- -- Caution! There is mutual recursion here, inlining the right functions is- -- important.-- handleCollect partial done fres =- case fres of- FL.Partial fs -> do- pres <- initial1- case pres of- IPartial ps -> return $ partial $ Fused3 ps 0 fs- IDone pb ->- runCollectorWith (handleCollect partial done) fs pb- IError _ -> done <$> fextract fs- FL.Done fb -> return $ done fb-- runCollectorWith cont fs pb = fstep fs pb >>= cont-- initial = finitial >>= handleCollect IPartial IDone-- {-# INLINE step #-}- step (Fused3 st cnt fs) a = do- r <- step1 st a- let cnt1 = cnt + 1- case r of- Partial n s -> do- assertM(cnt1 - n >= 0)- return $ Continue n (Fused3 s (cnt1 - n) fs)- Continue n s -> do- assertM(cnt1 - n >= 0)- return $ Continue n (Fused3 s (cnt1 - n) fs)- Done n b -> do- assertM(cnt1 - n >= 0)- fstep fs b >>= handleCollect (Partial n) (Done n)- Error _ -> do- xs <- fextract fs- return $ Done cnt1 xs-- extract (Fused3 s cnt fs) = do- r <- extract1 s- case r of- Error _ -> fmap (Done cnt) (fextract fs)- Done n b -> do- assertM(n <= cnt)- fs1 <- fstep fs b- case fs1 of- FL.Partial s1 -> fmap (Done n) (fextract s1)- FL.Done b1 -> return (Done n b1)- Partial _ _ -> error "splitMany: Partial in extract"- Continue n s1 -> do- assertM(n == cnt)- return (Continue n (Fused3 s1 0 fs))---- | See documentation of 'Streamly.Internal.Data.Parser.some'.------ /Pre-release/----{-# INLINE splitSome #-}-splitSome :: Monad m => Parser a m b -> Fold m b c -> Parser a m c-splitSome (Parser step1 initial1 extract1) (Fold fstep finitial fextract) =- Parser step initial extract-- where-- -- Caution! There is mutual recursion here, inlining the right functions is- -- important.-- handleCollect partial done fres =- case fres of- FL.Partial fs -> do- pres <- initial1- case pres of- IPartial ps -> return $ partial $ Fused3 ps 0 $ Right fs- IDone pb ->- runCollectorWith (handleCollect partial done) fs pb- IError _ -> done <$> fextract fs- FL.Done fb -> return $ done fb-- runCollectorWith cont fs pb = fstep fs pb >>= cont-- initial = do- fres <- finitial- case fres of- FL.Partial fs -> do- pres <- initial1- case pres of- IPartial ps -> return $ IPartial $ Fused3 ps 0 $ Left fs- IDone pb ->- runCollectorWith (handleCollect IPartial IDone) fs pb- IError err -> return $ IError err- FL.Done _ ->- return- $ IError- $ "splitSome: The collecting fold terminated without"- ++ " consuming any elements."-- {-# INLINE step #-}- step (Fused3 st cnt (Left fs)) a = do- r <- step1 st a- -- In the Left state, count is used only for the assert- let cnt1 = cnt + 1- case r of- Partial n s -> do- assertM(cnt1 - n >= 0)- return $ Continue n (Fused3 s (cnt1 - n) (Left fs))- Continue n s -> do- assertM(cnt1 - n >= 0)- return $ Continue n (Fused3 s (cnt1 - n) (Left fs))- Done n b -> do- assertM(cnt1 - n >= 0)- fstep fs b >>= handleCollect (Partial n) (Done n)- Error err -> return $ Error err- step (Fused3 st cnt (Right fs)) a = do- r <- step1 st a- let cnt1 = cnt + 1- case r of- Partial n s -> do- assertM(cnt1 - n >= 0)- return $ Partial n (Fused3 s (cnt1 - n) (Right fs))- Continue n s -> do- assertM(cnt1 - n >= 0)- return $ Continue n (Fused3 s (cnt1 - n) (Right fs))- Done n b -> do- assertM(cnt1 - n >= 0)- fstep fs b >>= handleCollect (Partial n) (Done n)- Error _ -> Done cnt1 <$> fextract fs-- extract (Fused3 s cnt (Left fs)) = do- r <- extract1 s- case r of- Error err -> return (Error err)- Done n b -> do- assertM(n <= cnt)- fs1 <- fstep fs b- case fs1 of- FL.Partial s1 -> fmap (Done n) (fextract s1)- FL.Done b1 -> return (Done n b1)- Partial _ _ -> error "splitSome: Partial in extract"- Continue n s1 -> do- assertM(n == cnt)- return (Continue n (Fused3 s1 0 (Left fs)))- extract (Fused3 s cnt (Right fs)) = do- r <- extract1 s- case r of- Error _ -> fmap (Done cnt) (fextract fs)- Done n b -> do- assertM(n <= cnt)- fs1 <- fstep fs b- case fs1 of- FL.Partial s1 -> fmap (Done n) (fextract s1)- FL.Done b1 -> return (Done n b1)- Partial _ _ -> error "splitSome: Partial in extract"- Continue n s1 -> do- assertM(n == cnt)- return (Continue n (Fused3 s1 0 (Right fs)))---- | A parser that always fails with an error message without consuming--- any input.----{-# INLINE_NORMAL die #-}-die :: Monad m => String -> Parser a m b-die err = Parser undefined (pure (IError err)) undefined---- | A parser that always fails with an effectful error message and without--- consuming any input.------ /Pre-release/----{-# INLINE dieM #-}-dieM :: Monad m => m String -> Parser a m b-dieM err = Parser undefined (IError <$> err) undefined---- Note: The default implementations of "some" and "many" loop infinitely--- because of the strict pattern match on both the arguments in applicative and--- alternative. With the direct style parser type we cannot use the mutually--- recursive definitions of "some" and "many".------ Note: With the direct style parser type, the list in "some" and "many" is--- accumulated strictly, it cannot be consumed lazily.---- | Sequential alternative. The input is first passed to the first parser, and--- if it succeeds, the result is returned. However, if the first parser fails,--- the parser driver backtracks and tries the same input on the second parser,--- returning the result if it succeeds.------ Note: The implementation of '<|>' is not lazy in the second--- argument. The following code will fail:------ >>> Stream.parse (Parser.satisfy (> 0) <|> undefined) $ Stream.fromList [1..10]--- *** Exception: Prelude.undefined--- ...------ WARNING! this is not suitable for large scale use. As a thumb rule stream--- fusion works well for less than 8 compositions of this operation, otherwise--- consider using 'ParserK'. Do not use recursive parser implementations based--- on this Alternative instance.--instance Monad m => Alternative (Parser a m) where- {-# INLINE empty #-}- empty = die "empty"-- {-# INLINE (<|>) #-}- (<|>) = alt-- {-# INLINE many #-}- many = flip splitMany toList-- {-# INLINE some #-}- some = flip splitSome toList--{-# ANN type ConcatParseState Fuse #-}-data ConcatParseState sl m a b =- ConcatParseL !sl- | forall s. ConcatParseR (s -> a -> m (Step s b)) s (s -> m (Step s b))---- | Map a 'Parser' returning function on the result of a 'Parser'.------ /Pre-release/----{-# INLINE concatMap #-}-concatMap :: Monad m =>- (b -> Parser a m c) -> Parser a m b -> Parser a m c-concatMap func (Parser stepL initialL extractL) = Parser step initial extract-- where-- {-# INLINE initializeR #-}- initializeR (Parser stepR initialR extractR) = do- resR <- initialR- return $ case resR of- IPartial sr -> IPartial $ ConcatParseR stepR sr extractR- IDone br -> IDone br- IError err -> IError err-- initial = do- res <- initialL- case res of- IPartial s -> return $ IPartial $ ConcatParseL s- IDone b -> initializeR (func b)- IError err -> return $ IError err-- {-# INLINE initializeRL #-}- initializeRL n (Parser stepR initialR extractR) = do- resR <- initialR- return $ case resR of- IPartial sr -> Continue n $ ConcatParseR stepR sr extractR- IDone br -> Done n br- IError err -> Error err-- step (ConcatParseL st) a = do- r <- stepL st a- case r of- Partial n s -> return $ Continue n (ConcatParseL s)- Continue n s -> return $ Continue n (ConcatParseL s)- Done n b -> initializeRL n (func b)- Error err -> return $ Error err-- step (ConcatParseR stepR st extractR) a = do- r <- stepR st a- return $ case r of- Partial n s -> Partial n $ ConcatParseR stepR s extractR- Continue n s -> Continue n $ ConcatParseR stepR s extractR- Done n b -> Done n b- Error err -> Error err-- {-# INLINE extractP #-}- extractP n (Parser stepR initialR extractR) = do- res <- initialR- case res of- IPartial s ->- fmap- (first (\s1 -> ConcatParseR stepR s1 extractR))- (extractR s)- IDone b -> return (Done n b)- IError err -> return $ Error err-- extract (ConcatParseR stepR s extractR) =- fmap (first (\s1 -> ConcatParseR stepR s1 extractR)) (extractR s)- extract (ConcatParseL sL) = do- rL <- extractL sL- case rL of- Error err -> return $ Error err- Done n b -> extractP n $ func b- Partial _ _ -> error "concatMap: extract Partial"- Continue n s -> return $ Continue n (ConcatParseL s)--{-# INLINE noErrorUnsafeConcatMap #-}-noErrorUnsafeConcatMap :: Monad m =>- (b -> Parser a m c) -> Parser a m b -> Parser a m c-noErrorUnsafeConcatMap func (Parser stepL initialL extractL) =- Parser step initial extract-- where-- {-# INLINE initializeR #-}- initializeR (Parser stepR initialR extractR) = do- resR <- initialR- return $ case resR of- IPartial sr -> IPartial $ ConcatParseR stepR sr extractR- IDone br -> IDone br- IError err -> IError err-- initial = do- res <- initialL- case res of- IPartial s -> return $ IPartial $ ConcatParseL s- IDone b -> initializeR (func b)- IError err -> return $ IError err-- {-# INLINE initializeRL #-}- initializeRL n (Parser stepR initialR extractR) = do- resR <- initialR- return $ case resR of- IPartial sr -> Partial n $ ConcatParseR stepR sr extractR- IDone br -> Done n br- IError err -> Error err-- step (ConcatParseL st) a = do- r <- stepL st a- case r of- Partial n s -> return $ Partial n (ConcatParseL s)- Continue n s -> return $ Continue n (ConcatParseL s)- Done n b -> initializeRL n (func b)- Error err -> return $ Error err-- step (ConcatParseR stepR st extractR) a = do- r <- stepR st a- return $ case r of- Partial n s -> Partial n $ ConcatParseR stepR s extractR- Continue n s -> Continue n $ ConcatParseR stepR s extractR- Done n b -> Done n b- Error err -> Error err-- {-# INLINE extractP #-}- extractP n (Parser stepR initialR extractR) = do- res <- initialR- case res of- IPartial s ->- fmap- (first (\s1 -> ConcatParseR stepR s1 extractR))- (extractR s)- IDone b -> return (Done n b)- IError err -> return $ Error err-- extract (ConcatParseR stepR s extractR) =- fmap (first (\s1 -> ConcatParseR stepR s1 extractR)) (extractR s)- extract (ConcatParseL sL) = do- rL <- extractL sL- case rL of- Error err -> return $ Error err- Done n b -> extractP n $ func b- Partial _ _ -> error "concatMap: extract Partial"- Continue n s -> return $ Continue n (ConcatParseL s)---- Note: The monad instance has quadratic performance complexity. It works fine--- for small number of compositions but for a scalable implementation we need a--- CPS version.---- | See documentation of 'Streamly.Internal.Data.Parser.ParserK.Type.Parser'.------ Although this implementation allows stream fusion, it has quadratic--- complexity, making it suitable only for a small number of compositions. As a--- thumb rule use it for less than 8 compositions, use 'ParserK' otherwise.----instance Monad m => Monad (Parser a m) where- {-# INLINE return #-}- return = pure-- {-# INLINE (>>=) #-}- (>>=) = flip concatMap-- {-# INLINE (>>) #-}- (>>) = (*>)--instance Monad m => Fail.MonadFail (Parser a m) where- {-# INLINE fail #-}- fail = die--{---- | See documentation of 'Streamly.Internal.Data.Parser.ParserK.Type.Parser'.----instance Monad m => MonadPlus (Parser a m) where- {-# INLINE mzero #-}- mzero = die "mzero"-- {-# INLINE mplus #-}- mplus = alt--}--instance (Monad m, MonadIO m) => MonadIO (Parser a m) where- {-# INLINE liftIO #-}- liftIO = fromEffect . liftIO----------------------------------------------------------------------------------- Mapping on input----------------------------------------------------------------------------------- | @lmap f parser@ maps the function @f@ on the input of the parser.------ >>> Stream.parse (Parser.lmap (\x -> x * x) (Parser.fromFold Fold.sum)) (Stream.enumerateFromTo 1 100)--- Right 338350------ > lmap = Parser.lmapM return----{-# INLINE lmap #-}-lmap :: (a -> b) -> Parser b m r -> Parser a m r-lmap f (Parser step begin done) = Parser step1 begin done-- where-- step1 x a = step x (f a)---- | @lmapM f parser@ maps the monadic function @f@ on the input of the parser.----{-# INLINE lmapM #-}-lmapM :: Monad m => (a -> m b) -> Parser b m r -> Parser a m r-lmapM f (Parser step begin done) = Parser step1 begin done-- where-- step1 x a = f a >>= step x---- | Include only those elements that pass a predicate.------ >>> Stream.parse (Parser.filter (> 5) (Parser.fromFold Fold.sum)) $ Stream.fromList [1..10]--- Right 40----{-# INLINE filter #-}-filter :: Monad m => (a -> Bool) -> Parser a m b -> Parser a m b-filter f (Parser step initial extract) = Parser step1 initial extract-- where-- step1 x a = if f a then step x a else return $ Partial 0 x
− src/Streamly/Internal/Data/Parser/ParserK/Type.hs
@@ -1,545 +0,0 @@--- |--- Module : Streamly.Internal.Data.Parser.ParserK.Type--- Copyright : (c) 2020 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ CPS style implementation of parsers.------ The CPS representation allows linear performance for Applicative, sequence,--- Monad, Alternative, and choice operations compared to the quadratic--- complexity of the corresponding direct style operations. However, direct--- style operations allow fusion with ~10x better performance than CPS.------ The direct style representation does not allow for recursive definitions of--- "some" and "many" whereas CPS allows that.------ 'Applicative' and 'Control.Applicative.Alternative' type class based--- combinators from the--- <http://hackage.haskell.org/package/parser-combinators parser-combinators>--- package can also be used with the 'ParserK' type.--module Streamly.Internal.Data.Parser.ParserK.Type- (- Step (..)- , Input (..)- , ParseResult (..)- , ParserK (..)- , fromParser- -- , toParser- , fromPure- , fromEffect- , die- )-where--#include "ArrayMacros.h"-#include "assert.hs"-#include "inline.hs"--import Control.Applicative (Alternative(..), liftA2)-import Control.Monad (MonadPlus(..), ap)-import Control.Monad.IO.Class (MonadIO, liftIO)--- import Control.Monad.Trans.Class (MonadTrans(lift))-import Data.Proxy (Proxy(..))-import GHC.Types (SPEC(..))-import Streamly.Internal.Data.Array.Type (Array(..))-import Streamly.Internal.Data.Unboxed (peekWith, sizeOf, Unbox)-import Streamly.Internal.System.IO (unsafeInlineIO)--import qualified Control.Monad.Fail as Fail-import qualified Streamly.Internal.Data.Array.Type as Array-import qualified Streamly.Internal.Data.Parser.ParserD.Type as ParserD--data Input a = None | Chunk {-# UNPACK #-} !(Array a)---- | The intermediate result of running a parser step. The parser driver may--- stop with a final result, pause with a continuation to resume, or fail with--- an error.------ See ParserD docs. This is the same as the ParserD Step except that it uses a--- continuation in Partial and Continue constructors instead of a state in case--- of ParserD.------ /Pre-release/----data Step a m r =- -- The Int is the current stream position index wrt to the start of the- -- array.- Done !Int r- -- XXX we can use a "resume" and a "stop" continuations instead of Maybe.- -- measure if that works any better.- -- Array a -> m (Step a m r), m (Step a m r)- | Partial !Int (Input a -> m (Step a m r))- | Continue !Int (Input a -> m (Step a m r))- | Error !Int String--instance Functor m => Functor (Step a m) where- fmap f (Done n r) = Done n (f r)- fmap f (Partial n k) = Partial n (fmap (fmap f) . k)- fmap f (Continue n k) = Continue n (fmap (fmap f) . k)- fmap _ (Error n e) = Error n e---- Note: Passing position index separately instead of passing it with the--- result causes huge regression in expression parsing becnhmarks.---- | The parser's result.------ Int is the position index into the current input array. Could be negative.--- Cannot be beyond the input array max bound.------ /Pre-release/----data ParseResult b =- Success !Int !b -- Position index, result- | Failure !Int !String -- Position index, error---- | Map a function over 'Success'.-instance Functor ParseResult where- fmap f (Success n b) = Success n (f b)- fmap _ (Failure n e) = Failure n e---- XXX Change the type to the shape (a -> m r -> m r) -> (m r -> m r) -> m r------ The parse continuation would be: Array a -> m (Step a m r) -> m (Step a m r)--- The extract continuation would be: m (Step a m r) -> m (Step a m r)------ Use Step itself in place of ParseResult.---- | A continuation passing style parser representation. A continuation of--- 'Step's, each step passes a state and a parse result to the next 'Step'. The--- resulting 'Step' may carry a continuation that consumes input 'a' and--- results in another 'Step'. Essentially, the continuation may either consume--- input without a result or return a result with no further input to be--- consumed.----newtype ParserK a m b = MkParser- { runParser :: forall r.- -- Using "Input" in runParser is not necessary but it avoids making- -- one more function call to get the input. This could be helpful- -- for cases where we process just one element per call.- --- -- Do not eta reduce the applications of this continuation.- --- (ParseResult b -> Int -> Input a -> m (Step a m r))- -- XXX Maintain and pass the original position in the stream. that- -- way we can also report better errors. Use a Context structure for- -- passing the state.-- -- Stream position index wrt to the current input array start. If- -- negative then backtracking is required before using the array.- -- The parser should use "Continue -n" in this case if it needs to- -- consume input. Negative value cannot be beyond the current- -- backtrack buffer. Positive value cannot be beyond array length.- -- If the parser needs to advance beyond the array length it should- -- use "Continue +n".- -> Int- -- used elem count, a count of elements consumed by the parser. If- -- an Alternative fails we need to backtrack by this amount.- -> Int- -- The second argument is the used count as described above. The- -- current input position is carried as part of 'Success'- -- constructor of 'ParseResult'.- -- XXX Use Array a, determine eof by using a nil array- -> Input a- -> m (Step a m r)- }------------------------------------------------------------------------------------ Functor------------------------------------------------------------------------------------ XXX rewrite this using ParserD, expose rmapM from ParserD.--- | Maps a function over the output of the parser.----instance Functor m => Functor (ParserK a m) where- {-# INLINE fmap #-}- fmap f parser = MkParser $ \k n st arr ->- let k1 res = k (fmap f res)- in runParser parser k1 n st arr------------------------------------------------------------------------------------ Sequential applicative------------------------------------------------------------------------------------ This is the dual of stream "fromPure".------ | A parser that always yields a pure value without consuming any input.------ /Pre-release/----{-# INLINE fromPure #-}-fromPure :: b -> ParserK a m b-fromPure b = MkParser $ \k n st arr -> k (Success n b) st arr---- | See 'Streamly.Internal.Data.Parser.fromEffect'.------ /Pre-release/----{-# INLINE fromEffect #-}-fromEffect :: Monad m => m b -> ParserK a m b-fromEffect eff =- MkParser $ \k n st arr -> eff >>= \b -> k (Success n b) st arr---- | 'Applicative' form of 'Streamly.Internal.Data.Parser.splitWith'. Note that--- this operation does not fuse, use 'Streamly.Internal.Data.Parser.splitWith'--- when fusion is important.----instance Monad m => Applicative (ParserK a m) where- {-# INLINE pure #-}- pure = fromPure-- {-# INLINE (<*>) #-}- (<*>) = ap-- {-# INLINE (*>) #-}- p1 *> p2 = MkParser $ \k n st arr ->- let k1 (Success n1 _) s input = runParser p2 k n1 s input- k1 (Failure n1 e) s input = k (Failure n1 e) s input- in runParser p1 k1 n st arr-- {-# INLINE (<*) #-}- p1 <* p2 = MkParser $ \k n st arr ->- let k1 (Success n1 b) s1 input =- let k2 (Success n2 _) s2 input2 = k (Success n2 b) s2 input2- k2 (Failure n2 e) s2 input2 = k (Failure n2 e) s2 input2- in runParser p2 k2 n1 s1 input- k1 (Failure n1 e) s1 input = k (Failure n1 e) s1 input- in runParser p1 k1 n st arr-- {-# INLINE liftA2 #-}- liftA2 f p = (<*>) (fmap f p)------------------------------------------------------------------------------------ Monad------------------------------------------------------------------------------------ This is the dual of "nil".------ | A parser that always fails with an error message without consuming--- any input.------ /Pre-release/----{-# INLINE die #-}-die :: String -> ParserK a m b-die err = MkParser (\k n st arr -> k (Failure n err) st arr)---- | Monad composition can be used for lookbehind parsers, we can make the--- future parses depend on the previously parsed values.------ If we have to parse "a9" or "9a" but not "99" or "aa" we can use the--- following parser:------ @--- backtracking :: MonadCatch m => PR.Parser Char m String--- backtracking =--- sequence [PR.satisfy isDigit, PR.satisfy isAlpha]--- '<|>'--- sequence [PR.satisfy isAlpha, PR.satisfy isDigit]--- @------ We know that if the first parse resulted in a digit at the first place then--- the second parse is going to fail. However, we waste that information and--- parse the first character again in the second parse only to know that it is--- not an alphabetic char. By using lookbehind in a 'Monad' composition we can--- avoid redundant work:------ @--- data DigitOrAlpha = Digit Char | Alpha Char------ lookbehind :: MonadCatch m => PR.Parser Char m String--- lookbehind = do--- x1 \<- Digit '<$>' PR.satisfy isDigit--- '<|>' Alpha '<$>' PR.satisfy isAlpha------ -- Note: the parse depends on what we parsed already--- x2 <- case x1 of--- Digit _ -> PR.satisfy isAlpha--- Alpha _ -> PR.satisfy isDigit------ return $ case x1 of--- Digit x -> [x,x2]--- Alpha x -> [x,x2]--- @------ See also 'Streamly.Internal.Data.Parser.concatMap'. This monad instance--- does not fuse, use 'Streamly.Internal.Data.Parser.concatMap' when you need--- fusion.----instance Monad m => Monad (ParserK a m) where- {-# INLINE return #-}- return = pure-- {-# INLINE (>>=) #-}- p >>= f = MkParser $ \k n st arr ->- let k1 (Success n1 b) s1 inp = runParser (f b) k n1 s1 inp- k1 (Failure n1 e) s1 inp = k (Failure n1 e) s1 inp- in runParser p k1 n st arr-- {-# INLINE (>>) #-}- (>>) = (*>)--#if !(MIN_VERSION_base(4,13,0))- -- This is redefined instead of just being Fail.fail to be- -- compatible with base 4.8.- {-# INLINE fail #-}- fail = die-#endif-instance Monad m => Fail.MonadFail (ParserK a m) where- {-# INLINE fail #-}- fail = die--instance MonadIO m => MonadIO (ParserK a m) where- {-# INLINE liftIO #-}- liftIO = fromEffect . liftIO------------------------------------------------------------------------------------ Alternative------------------------------------------------------------------------------------ | 'Alternative' form of 'Streamly.Internal.Data.Parser.alt'. Backtrack and--- run the second parser if the first one fails.------ The "some" and "many" operations of alternative accumulate results in a pure--- list which is not scalable and streaming. Instead use--- 'Streamly.Internal.Data.Parser.some' and--- 'Streamly.Internal.Data.Parser.many' for fusible operations with composable--- accumulation of results.------ See also 'Streamly.Internal.Data.Parser.alt'. This 'Alternative' instance--- does not fuse, use 'Streamly.Internal.Data.Parser.alt' when you need--- fusion.----instance Monad m => Alternative (ParserK a m) where- {-# INLINE empty #-}- empty = die "empty"-- {-# INLINE (<|>) #-}- p1 <|> p2 = MkParser $ \k n _ arr ->- let- k1 (Failure pos _) used input = runParser p2 k (pos - used) 0 input- k1 success _ input = k success 0 input- in runParser p1 k1 n 0 arr-- -- some and many are implemented here instead of using default definitions- -- so that we can use INLINE on them. It gives 50% performance improvement.-- {-# INLINE many #-}- many v = many_v-- where-- many_v = some_v <|> pure []- some_v = (:) <$> v <*> many_v-- {-# INLINE some #-}- some v = some_v-- where-- many_v = some_v <|> pure []- some_v = (:) <$> v <*> many_v---- | 'mzero' is same as 'empty', it aborts the parser. 'mplus' is same as--- '<|>', it selects the first succeeding parser.----instance Monad m => MonadPlus (ParserK a m) where- {-# INLINE mzero #-}- mzero = die "mzero"-- {-# INLINE mplus #-}- mplus = (<|>)--{--instance MonadTrans (ParserK a) where- {-# INLINE lift #-}- lift = fromEffect--}------------------------------------------------------------------------------------ Convert ParserD to ParserK----------------------------------------------------------------------------------{-# INLINE parseDToK #-}-parseDToK- :: forall m a s b r. (Monad m, Unbox a)- => (s -> a -> m (ParserD.Step s b))- -> m (ParserD.Initial s b)- -> (s -> m (ParserD.Step s b))- -> (ParseResult b -> Int -> Input a -> m (Step a m r))- -> Int- -> Int- -> Input a- -> m (Step a m r)-parseDToK pstep initial extract cont !offset0 !usedCount !input = do- res <- initial- case res of- ParserD.IPartial pst -> do- case input of- Chunk arr -> parseContChunk usedCount offset0 pst arr- None -> parseContNothing usedCount pst- ParserD.IDone b -> cont (Success offset0 b) usedCount input- ParserD.IError err -> cont (Failure offset0 err) usedCount input-- where-- -- XXX We can maintain an absolute position instead of relative that will- -- help in reporting of error location in the stream.- {-# NOINLINE parseContChunk #-}- parseContChunk !count !offset !state arr@(Array contents start end) = do- if offset >= 0- then go SPEC (start + offset * SIZE_OF(a)) state- else return $ Continue offset (parseCont count state)-- where-- {-# INLINE onDone #-}- onDone n b =- assert (n <= Array.length arr)- (cont (Success n b) (count + n - offset) (Chunk arr))-- {-# INLINE callParseCont #-}- callParseCont constr n pst1 =- assert (n < 0 || n >= Array.length arr)- (return $ constr n (parseCont (count + n - offset) pst1))-- {-# INLINE onPartial #-}- onPartial = callParseCont Partial-- {-# INLINE onContinue #-}- onContinue = callParseCont Continue-- {-# INLINE onError #-}- onError n err =- cont (Failure n err) (count + n - offset) (Chunk arr)-- {-# INLINE onBack #-}- onBack offset1 elemSize constr pst = do- let pos = offset1 - start- in if pos >= 0- then go SPEC offset1 pst- else constr (pos `div` elemSize) pst-- -- Note: div may be expensive but the alternative is to maintain an element- -- offset in addition to a byte offset or just the element offset and use- -- multiplication to get the byte offset every time, both these options- -- turned out to be more expensive than using div.- go !_ !cur !pst | cur >= end =- onContinue ((end - start) `div` SIZE_OF(a)) pst- go !_ !cur !pst = do- let !x = unsafeInlineIO $ peekWith contents cur- pRes <- pstep pst x- let elemSize = SIZE_OF(a)- next = INDEX_NEXT(cur,a)- back n = next - n * elemSize- curOff = (cur - start) `div` elemSize- nextOff = (next - start) `div` elemSize- -- The "n" here is stream position index wrt the array start, and- -- not the backtrack count as returned by byte stream parsers.- case pRes of- ParserD.Done 0 b ->- onDone nextOff b- ParserD.Done 1 b ->- onDone curOff b- ParserD.Done n b ->- onDone ((back n - start) `div` elemSize) b- ParserD.Partial 0 pst1 ->- go SPEC next pst1- ParserD.Partial 1 pst1 ->- go SPEC cur pst1- ParserD.Partial n pst1 ->- onBack (back n) elemSize onPartial pst1- ParserD.Continue 0 pst1 ->- go SPEC next pst1- ParserD.Continue 1 pst1 ->- go SPEC cur pst1- ParserD.Continue n pst1 ->- onBack (back n) elemSize onContinue pst1- ParserD.Error err ->- onError curOff err-- {-# NOINLINE parseContNothing #-}- parseContNothing !count !pst = do- r <- extract pst- case r of- -- IMPORTANT: the n here is from the byte stream parser, that means- -- it is the backtrack element count and not the stream position- -- index into the current input array.- ParserD.Done n b ->- assert (n >= 0)- (cont (Success (- n) b) (count - n) None)- ParserD.Continue n pst1 ->- assert (n >= 0)- (return $ Continue (- n) (parseCont (count - n) pst1))- ParserD.Error err ->- -- XXX It is called only when there is no input arr. So using 0- -- as the position is correct?- cont (Failure 0 err) count None- ParserD.Partial _ _ -> error "Bug: parseDToK Partial unreachable"-- -- XXX Maybe we can use two separate continuations instead of using- -- Just/Nothing cases here. That may help in avoiding the parseContJust- -- function call.- {-# INLINE parseCont #-}- parseCont !cnt !pst (Chunk arr) = parseContChunk cnt 0 pst arr- parseCont !cnt !pst None = parseContNothing cnt pst---- | Convert a raw byte 'Parser' to a chunked 'ParserK'.------ /Pre-release/----{-# INLINE_LATE fromParser #-}-fromParser :: (Monad m, Unbox a) => ParserD.Parser a m b -> ParserK a m b-fromParser (ParserD.Parser step initial extract) =- MkParser $ parseDToK step initial extract--{------------------------------------------------------------------------------------ Convert CPS style 'Parser' to direct style 'D.Parser'------------------------------------------------------------------------------------ | A continuation to extract the result when a CPS parser is done.-{-# INLINE parserDone #-}-parserDone :: Monad m => ParseResult b -> Int -> Input a -> m (Step a m b)-parserDone (Success n b) _ None = return $ Done n b-parserDone (Failure n e) _ None = return $ Error n e-parserDone _ _ _ = error "Bug: toParser: called with input"---- | Convert a CPS style 'ParserK' to a direct style 'ParserD.Parser'.------ /Pre-release/----{-# INLINE_LATE toParser #-}-toParser :: Monad m => ParserK a m b -> ParserD.Parser a m b-toParser parser = ParserD.Parser step initial extract-- where-- initial = pure (ParserD.IPartial (\x -> runParser parser 0 0 x parserDone))-- step cont a = do- r <- cont (Single a)- return $ case r of- Done n b -> ParserD.Done n b- Error _ e -> ParserD.Error e- Partial n cont1 -> ParserD.Partial n cont1- Continue n cont1 -> ParserD.Continue n cont1-- extract cont = do- r <- cont None- case r of- Done n b -> return $ ParserD.Done n b- Error _ e -> return $ ParserD.Error e- Partial _ cont1 -> extract cont1- Continue n cont1 -> return $ ParserD.Continue n cont1--#ifndef DISABLE_FUSION-{-# RULES "fromParser/toParser fusion" [2]- forall s. toParser (fromParser s) = s #-}-{-# RULES "toParser/fromParser fusion" [2]- forall s. fromParser (toParser s) = s #-}-#endif--}
+ src/Streamly/Internal/Data/Parser/Tee.hs view
@@ -0,0 +1,617 @@+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}++#include "inline.hs"++-- |+-- Module : Streamly.Internal.Data.Parser.ParserD.Tee+-- Copyright : (c) 2020 Composewell Technologies+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- Parallel parsers. Distributing the input to multiple parsers at the same+-- time.+--+-- For simplicity, we are using code where a particular state is unreachable+-- but it is not prevented by types. Somehow uni-pattern match using "let"+-- produces better optimized code compared to using @case@ match and using+-- explicit error messages in unreachable cases.+--+-- There seem to be no way to silence individual warnings so we use a global+-- incomplete uni-pattern match warning suppression option for the file.+-- Disabling the warning for other code as well has the potential to mask off+-- some legit warnings, therefore, we have segregated only the code that uses+-- uni-pattern matches in this module.++module Streamly.Internal.Data.Parser.Tee+ (+ {-+ -- Parallel zipped+ teeWith+ , teeWithFst+ , teeWithMin++ -- Parallel alternatives+ , shortest+ , longest+ -}+ )+where++{-+import Control.Exception (assert)+import Control.Monad.Catch (MonadCatch, try)+import Prelude+ hiding (any, all, takeWhile)++import Fusion.Plugin.Types (Fuse(..))+import Streamly.Internal.Data.Parser.ParserD.Type+ (Initial(..), Parser(..), Step(..), ParseError)++-------------------------------------------------------------------------------+-- Distribute input to two parsers and collect both results+-------------------------------------------------------------------------------++-- When the input stream is distributed to two parsers, both the parsers can+-- backtrack independently. Therefore, we need separate buffer state for each+-- parser.+--+-- ParserK+--+-- We can keep the state of each parser in the zipper and pass around that+-- zipper to the parsers. Each parser can consume from the zipper and then pass+-- around the zipper to the other parser.+--+-- ParserD+--+-- In the approach we have taken here, the driver pushes one element at a time+-- to the tee and each of the parsers in the tee may buffer it independently+-- for backtracking. So they do not need to depend on the original stream+-- source for individual parser backtracking. Problem arises when both the+-- parsers backtrack and they do not need any input from the driver rather they+-- must consume from their buffers. For such situation we may need a+-- "Continue" style driver command from the tee so that the driver runs+-- the tee without providing it any input. Or we may need a local driver loop+-- until new input is to be demanded from the input stream.+--+-- When the tee errors out or stops, the tee driver may have to backtrack by+-- the specified amount (or the tee must return the leftover input). Therefore,+-- the tee driver also has to buffer, this leads to triple buffering.+--+-- When the tee stops we need to determine the backtracking amount from the+-- leftover of both the parsers. Since both the parsers may have consumed+-- different lengths of the stream we consider the maximum of the two as+-- consumed.+--+ -- XXX We can use Initial instead of StepState+{-# ANN type StepState Fuse #-}+data StepState s a = StepState s | StepResult a++-- | State of the pair of parsers in a tee composition+-- Note: strictness annotation is important for fusing the constructors+{-# ANN type TeeState Fuse #-}+data TeeState sL sR x a b =+-- @TeePair (past buffer, parser state, future-buffer1, future-buffer2) ...@+ TeePair !([x], StepState sL a, [x], [x]) !([x], StepState sR b, [x], [x])++{-# ANN type Res Fuse #-}+data Res = Yld Int | Stp Int | Skp | Err String++-- | See 'Streamly.Internal.Data.Parser.teeWith'.+--+-- /Broken/+--+{-# INLINE teeWith #-}+teeWith :: Monad m+ => (a -> b -> c) -> Parser x m a -> Parser x m b -> Parser x m c+teeWith zf (Parser stepL initialL extractL) (Parser stepR initialR extractR) =+ Parser step initial extract++ where++ {-# INLINE_LATE initial #-}+ initial = do+ resL <- initialL+ resR <- initialR+ return $ case resL of+ IPartial sl ->+ case resR of+ IPartial sr -> IPartial $ TeePair ([], StepState sl, [], [])+ ([], StepState sr, [], [])+ IDone br -> IPartial $ TeePair ([], StepState sl, [], [])+ ([], StepResult br, [], [])+ IError err -> IError err+ IDone bl ->+ case resR of+ IPartial sr ->+ IPartial $ TeePair ([], StepResult bl, [], [])+ ([], StepState sr, [], [])+ IDone br -> IDone $ zf bl br+ IError err -> IError err+ IError err -> IError err++ {-# INLINE consume #-}+ consume buf inp1 inp2 stp st y = do+ let (x, inp11, inp21) =+ case inp1 of+ [] -> (y, [], [])+ z : [] -> (z, reverse (x:inp2), [])+ z : zs -> (z, zs, x:inp2)+ r <- stp st x+ let buf1 = x:buf+ return (buf1, r, inp11, inp21)++ -- XXX This is currently broken, even though both the parsers need to+ -- consume from their buffers after backtracking the driver would still be+ -- pushing more input to the buffers.+ --+ -- consume one input item and return the next state of the fold+ {-# INLINE useStream #-}+ useStream buf inp1 inp2 stp st y = do+ (buf1, r, inp11, inp21) <- consume buf inp1 inp2 stp st y+ case r of+ Partial 0 s ->+ let state = ([], StepState s, inp11, inp21)+ in return (state, Yld 0)+ Partial n s ->+ let src0 = Prelude.take n buf1+ src = Prelude.reverse src0+ state = ([], StepState s, src ++ inp11, inp21)+ in assert (n <= length buf1) (return (state, Yld n))+ Done n b ->+ let state = (Prelude.take n buf1, StepResult b, inp11, inp21)+ in assert (n <= length buf1) (return (state, Stp n))+ -- Continue 0 s -> (buf1, Right s, inp11, inp21)+ Continue n s ->+ let (src0, buf2) = splitAt n buf1+ src = Prelude.reverse src0+ state = (buf2, StepState s, src ++ inp11, inp21)+ in assert (n <= length buf1) (return (state, Skp))+ SError err -> return (undefined, Err err)++ {-# INLINE_LATE step #-}+ step (TeePair (bufL, StepState sL, inpL1, inpL2)+ (bufR, StepState sR, inpR1, inpR2)) x = do+ (l,stL) <- useStream bufL inpL1 inpL2 stepL sL x+ (r,stR) <- useStream bufR inpR1 inpR2 stepR sR x+ let next = TeePair l r+ return $ case (stL,stR) of+ (Yld n1, Yld n2) -> Partial (min n1 n2) next+ (Yld n1, Stp n2) -> Partial (min n1 n2) next+ (Stp n1, Yld n2) -> Partial (min n1 n2) next+ (Stp n1, Stp n2) ->+ -- Uni-pattern match results in better optimized code compared+ -- to a case match.+ let (_, StepResult rL, _, _) = l+ (_, StepResult rR, _, _) = r+ in Done (min n1 n2) (zf rL rR)+ (Err err, _) -> SError err+ (_, Err err) -> SError err+ _ -> Continue 0 next++ step (TeePair (bufL, StepState sL, inpL1, inpL2)+ r@(_, StepResult rR, _, _)) x = do+ (l,stL) <- useStream bufL inpL1 inpL2 stepL sL x+ let next = TeePair l r+ -- XXX If the unused count of this stream is lower than the unused+ -- count of the stopped stream, only then this will be correct. We need+ -- to fix the other case. We need to keep incrementing the unused count+ -- of the stopped stream and take the min of the two.+ return $ case stL of+ Yld n -> Partial n next+ Stp n ->+ let (_, StepResult rL, _, _) = l+ in Done n (zf rL rR)+ Skp -> Continue 0 next+ Err err -> SError err++ step (TeePair l@(_, StepResult rL, _, _)+ (bufR, StepState sR, inpR1, inpR2)) x = do+ (r, stR) <- useStream bufR inpR1 inpR2 stepR sR x+ let next = TeePair l r+ -- XXX If the unused count of this stream is lower than the unused+ -- count of the stopped stream, only then this will be correct. We need+ -- to fix the other case. We need to keep incrementing the unused count+ -- of the stopped stream and take the min of the two.+ return $ case stR of+ Yld n -> Partial n next+ Stp n ->+ let (_, StepResult rR, _, _) = r+ in Done n (zf rL rR)+ Skp -> Continue 0 next+ Err err -> SError err++ step _ _ = undefined++ {-# INLINE_LATE extract #-}+ extract st =+ case st of+ TeePair (_, StepState sL, _, _) (_, StepState sR, _, _) -> do+ rL <- extractL sL+ rR <- extractR sR+ return $ zf rL rR+ TeePair (_, StepState sL, _, _) (_, StepResult rR, _, _) -> do+ rL <- extractL sL+ return $ zf rL rR+ TeePair (_, StepResult rL, _, _) (_, StepState sR, _, _) -> do+ rR <- extractR sR+ return $ zf rL rR+ TeePair (_, StepResult rL, _, _) (_, StepResult rR, _, _) ->+ return $ zf rL rR++-- | See 'Streamly.Internal.Data.Parser.teeWithFst'.+--+-- /Broken/+--+{-# INLINE teeWithFst #-}+teeWithFst :: Monad m+ => (a -> b -> c) -> Parser x m a -> Parser x m b -> Parser x m c+teeWithFst zf (Parser stepL initialL extractL)+ (Parser stepR initialR extractR) =+ Parser step initial extract++ where++ {-# INLINE_LATE initial #-}+ initial = do+ resL <- initialL+ resR <- initialR+ case resL of+ IPartial sl ->+ return $ case resR of+ IPartial sr -> IPartial $ TeePair ([], StepState sl, [], [])+ ([], StepState sr, [], [])+ IDone br -> IPartial $ TeePair ([], StepState sl, [], [])+ ([], StepResult br, [], [])+ IError err -> IError err+ IDone bl ->+ case resR of+ IPartial sr -> IDone . zf bl <$> extractR sr+ IDone br -> return $ IDone $ zf bl br+ IError err -> return $ IError err+ IError err -> return $ IError err++ {-# INLINE consume #-}+ consume buf inp1 inp2 stp st y = do+ let (x, inp11, inp21) =+ case inp1 of+ [] -> (y, [], [])+ z : [] -> (z, reverse (x:inp2), [])+ z : zs -> (z, zs, x:inp2)+ r <- stp st x+ let buf1 = x:buf+ return (buf1, r, inp11, inp21)++ -- consume one input item and return the next state of the fold+ {-# INLINE useStream #-}+ useStream buf inp1 inp2 stp st y = do+ (buf1, r, inp11, inp21) <- consume buf inp1 inp2 stp st y+ case r of+ Partial 0 s ->+ let state = ([], StepState s, inp11, inp21)+ in return (state, Yld 0)+ Partial n _ -> return (undefined, Yld n) -- Not implemented+ Done n b ->+ let state = (Prelude.take n buf1, StepResult b, inp11, inp21)+ in assert (n <= length buf1) (return (state, Stp n))+ -- Continue 0 s -> (buf1, Right s, inp11, inp21)+ Continue n s ->+ let (src0, buf2) = splitAt n buf1+ src = Prelude.reverse src0+ state = (buf2, StepState s, src ++ inp11, inp21)+ in assert (n <= length buf1) (return (state, Skp))+ SError err -> return (undefined, Err err)++ {-# INLINE_LATE step #-}+ step (TeePair (bufL, StepState sL, inpL1, inpL2)+ (bufR, StepState sR, inpR1, inpR2)) x = do+ (l,stL) <- useStream bufL inpL1 inpL2 stepL sL x+ (r,stR) <- useStream bufR inpR1 inpR2 stepR sR x+ let next = TeePair l r+ case (stL,stR) of+ -- XXX what if the first parser returns an unused count which is+ -- more than the second parser's unused count? It does not make+ -- sense for the second parser to consume more than the first+ -- parser. We reset the input cursor based on the first parser.+ -- SError out if the second one has consumed more then the first?+ (Stp n1, Stp _) ->+ -- Uni-pattern match results in better optimized code compared+ -- to a case match.+ let (_, StepResult rL, _, _) = l+ (_, StepResult rR, _, _) = r+ in return $ Done n1 (zf rL rR)+ (Stp n1, Yld _) ->+ let (_, StepResult rL, _, _) = l+ (_, StepState ssR, _, _) = r+ in do+ rR <- extractR ssR+ return $ Done n1 (zf rL rR)+ (Yld n1, Yld n2) -> return $ Partial (min n1 n2) next+ (Yld n1, Stp n2) -> return $ Partial (min n1 n2) next+ (Err err, _) -> return $ SError err+ (_, Err err) -> return $ SError err+ _ -> return $ Continue 0 next++ step (TeePair (bufL, StepState sL, inpL1, inpL2)+ r@(_, StepResult rR, _, _)) x = do+ (l,stL) <- useStream bufL inpL1 inpL2 stepL sL x+ let next = TeePair l r+ -- XXX If the unused count of this stream is lower than the unused+ -- count of the stopped stream, only then this will be correct. We need+ -- to fix the other case. We need to keep incrementing the unused count+ -- of the stopped stream and take the min of the two.+ return $ case stL of+ Yld n -> Partial n next+ Stp n ->+ let (_, StepResult rL, _, _) = l+ in Done n (zf rL rR)+ Skp -> Continue 0 next+ Err err -> SError err++ step _ _ = undefined++ {-# INLINE_LATE extract #-}+ extract st =+ case st of+ TeePair (_, StepState sL, _, _) (_, StepState sR, _, _) -> do+ rL <- extractL sL+ rR <- extractR sR+ return $ zf rL rR+ TeePair (_, StepState sL, _, _) (_, StepResult rR, _, _) -> do+ rL <- extractL sL+ return $ zf rL rR+ _ -> error "unreachable"++-- | See 'Streamly.Internal.Data.Parser.teeWithMin'.+--+-- /Unimplemented/+--+{-# INLINE teeWithMin #-}+teeWithMin ::+ -- Monad m =>+ (a -> b -> c) -> Parser x m a -> Parser x m b -> Parser x m c+teeWithMin = undefined++-------------------------------------------------------------------------------+-- Distribute input to two parsers and choose one result+-------------------------------------------------------------------------------++-- | See 'Streamly.Internal.Data.Parser.shortest'.+--+-- /Broken/+--+{-# INLINE shortest #-}+shortest :: Monad m => Parser x m a -> Parser x m a -> Parser x m a+shortest (Parser stepL initialL extractL) (Parser stepR initialR _) =+ Parser step initial extract++ where++ {-# INLINE_LATE initial #-}+ initial = do+ resL <- initialL+ resR <- initialR+ return $ case resL of+ IPartial sl ->+ case resR of+ IPartial sr -> IPartial $ TeePair ([], StepState sl, [], [])+ ([], StepState sr, [], [])+ IDone br -> IDone br+ IError err -> IError err+ IDone bl -> IDone bl+ IError errL ->+ case resR of+ IPartial _ -> IError errL+ IDone br -> IDone br+ IError errR -> IError errR++ {-# INLINE consume #-}+ consume buf inp1 inp2 stp st y = do+ let (x, inp11, inp21) =+ case inp1 of+ [] -> (y, [], [])+ z : [] -> (z, reverse (x:inp2), [])+ z : zs -> (z, zs, x:inp2)+ r <- stp st x+ let buf1 = x:buf+ return (buf1, r, inp11, inp21)++ -- consume one input item and return the next state of the fold+ {-# INLINE useStream #-}+ useStream buf inp1 inp2 stp st y = do+ (buf1, r, inp11, inp21) <- consume buf inp1 inp2 stp st y+ case r of+ Partial 0 s ->+ let state = ([], StepState s, inp11, inp21)+ in return (state, Yld 0)+ Partial n _ -> return (undefined, Yld n) -- Not implemented+ Done n b ->+ let state = (Prelude.take n buf1, StepResult b, inp11, inp21)+ in assert (n <= length buf1) (return (state, Stp n))+ -- Continue 0 s -> (buf1, Right s, inp11, inp21)+ Continue n s ->+ let (src0, buf2) = splitAt n buf1+ src = Prelude.reverse src0+ state = (buf2, StepState s, src ++ inp11, inp21)+ in assert (n <= length buf1) (return (state, Skp))+ SError err -> return (undefined, Err err)++ -- XXX Even if a parse finished earlier it may not be shortest if the other+ -- parser finishes later but returns a lot of unconsumed input. Our current+ -- criterion of shortest is whichever parse decided to stop earlier.+ {-# INLINE_LATE step #-}+ step (TeePair (bufL, StepState sL, inpL1, inpL2)+ (bufR, StepState sR, inpR1, inpR2)) x = do+ (l,stL) <- useStream bufL inpL1 inpL2 stepL sL x+ (r,stR) <- useStream bufR inpR1 inpR2 stepR sR x+ let next = TeePair l r+ return $ case (stL,stR) of+ (Stp n1, _) ->+ let (_, StepResult rL, _, _) = l+ in Done n1 rL+ (_, Stp n2) ->+ let (_, StepResult rR, _, _) = r+ in Done n2 rR+ (Yld n1, Yld n2) -> Partial (min n1 n2) next+ (Err err, _) -> SError err+ (_, Err err) -> SError err+ _ -> Continue 0 next++ step _ _ = undefined++ {-# INLINE_LATE extract #-}+ extract st =+ case st of+ TeePair (_, StepState sL, _, _) _ -> extractL sL+ _ -> error "unreachable"++-- | See 'Streamly.Internal.Data.Parser.longest'.+--+-- /Broken/+--+{-# INLINE longest #-}+longest :: MonadCatch m => Parser x m a -> Parser x m a -> Parser x m a+longest (Parser stepL initialL extractL) (Parser stepR initialR extractR) =+ Parser step initial extract++ where+++ {-# INLINE_LATE initial #-}+ initial = do+ resL <- initialL+ resR <- initialR+ return $ case resL of+ IPartial sl ->+ case resR of+ IPartial sr -> IPartial $ TeePair ([], StepState sl, [], [])+ ([], StepState sr, [], [])+ IDone br -> IPartial $ TeePair ([], StepState sl, [], [])+ ([], StepResult br, [], [])+ IError _ ->+ IPartial $ TeePair ([], StepState sl, [], [])+ ([], StepResult undefined, [], [])+ IDone bl ->+ case resR of+ IPartial sr ->+ IPartial $ TeePair ([], StepResult bl, [], [])+ ([], StepState sr, [], [])+ IDone _ -> IDone bl+ IError _ -> IDone bl+ IError _ ->+ case resR of+ IPartial sr ->+ IPartial $ TeePair ([], StepResult undefined, [], [])+ ([], StepState sr, [], [])+ IDone br -> IDone br+ IError err -> IError err++ {-# INLINE consume #-}+ consume buf inp1 inp2 stp st y = do+ let (x, inp11, inp21) =+ case inp1 of+ [] -> (y, [], [])+ z : [] -> (z, reverse (x:inp2), [])+ z : zs -> (z, zs, x:inp2)+ r <- stp st x+ let buf1 = x:buf+ return (buf1, r, inp11, inp21)++ -- consume one input item and return the next state of the fold+ {-# INLINE useStream #-}+ useStream buf inp1 inp2 stp st y = do+ (buf1, r, inp11, inp21) <- consume buf inp1 inp2 stp st y+ case r of+ Partial 0 s ->+ let state = ([], StepState s, inp11, inp21)+ in return (state, Yld 0)+ Partial n _ -> return (undefined, Yld n) -- Not implemented+ Done n b ->+ let state = (Prelude.take n buf1, StepResult b, inp11, inp21)+ in assert (n <= length buf1) (return (state, Stp n))+ -- Continue 0 s -> (buf1, Right s, inp11, inp21)+ Continue n s ->+ let (src0, buf2) = splitAt n buf1+ src = Prelude.reverse src0+ state = (buf2, StepState s, src ++ inp11, inp21)+ in assert (n <= length buf1) (return (state, Skp))+ SError err -> return (undefined, Err err)++ {-# INLINE_LATE step #-}+ step (TeePair (bufL, StepState sL, inpL1, inpL2)+ (bufR, StepState sR, inpR1, inpR2)) x = do+ (l,stL) <- useStream bufL inpL1 inpL2 stepL sL x+ (r,stR) <- useStream bufR inpR1 inpR2 stepR sR x+ let next = TeePair l r+ return $ case (stL,stR) of+ (Yld n1, Yld n2) -> Partial (min n1 n2) next+ (Yld n1, Stp n2) -> Partial (min n1 n2) next+ (Stp n1, Yld n2) -> Partial (min n1 n2) next+ (Stp n1, Stp n2) ->+ let (_, StepResult rL, _, _) = l+ (_, StepResult rR, _, _) = r+ in Done (max n1 n2) (if n1 >= n2 then rL else rR)+ (Err err, _) -> SError err+ (_, Err err) -> SError err+ _ -> Continue 0 next++ -- XXX the parser that finishes last may not be the longest because it may+ -- return a lot of unused input which makes it shorter. Our current+ -- criterion of deciding longest is based on whoever decides to finish+ -- last and not whoever consumed more input.+ --+ -- To actually know who made more progress we need to keep an account of+ -- how many items are unconsumed since the last yield.+ --+ step (TeePair (bufL, StepState sL, inpL1, inpL2)+ r@(_, StepResult _, _, _)) x = do+ (l,stL) <- useStream bufL inpL1 inpL2 stepL sL x+ let next = TeePair l r+ return $ case stL of+ Yld n -> Partial n next+ Stp n ->+ let (_, StepResult rL, _, _) = l+ in Done n rL+ Skp -> Continue 0 next+ Err err -> SError err++ step (TeePair l@(_, StepResult _, _, _)+ (bufR, StepState sR, inpR1, inpR2)) x = do+ (r, stR) <- useStream bufR inpR1 inpR2 stepR sR x+ let next = TeePair l r+ return $ case stR of+ Yld n -> Partial n next+ Stp n ->+ let (_, StepResult rR, _, _) = r+ in Done n rR+ Skp -> Continue 0 next+ Err err -> SError err++ step _ _ = undefined++ {-# INLINE_LATE extract #-}+ extract st =+ -- XXX When results are partial we may not be able to precisely compare+ -- which parser has made more progress till now. One way to do that is+ -- to figure out the actually consumed input up to the last yield.+ --+ case st of+ TeePair (_, StepState sL, _, _) (_, StepState sR, _, _) -> do+ r <- try $ extractL sL+ case r of+ Left (_ :: ParseError) -> extractR sR+ Right b -> return b+ TeePair (_, StepState sL, _, _) (_, StepResult rR, _, _) -> do+ r <- try $ extractL sL+ case r of+ Left (_ :: ParseError) -> return rR+ Right b -> return b+ TeePair (_, StepResult rL, _, _) (_, StepState sR, _, _) -> do+ r <- try $ extractR sR+ case r of+ Left (_ :: ParseError) -> return rL+ Right b -> return b+ TeePair (_, StepResult _, _, _) (_, StepResult _, _, _) ->+ error "unreachable"+-}
+ src/Streamly/Internal/Data/Parser/Type.hs view
@@ -0,0 +1,1548 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE NoMonoLocalBinds #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE ViewPatterns #-}+-- |+-- Module : Streamly.Internal.Data.Parser.ParserD.Type+-- Copyright : (c) 2020 Composewell Technologies+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- Streaming and backtracking parsers.+--+-- Parsers just extend folds. Please read the 'Fold' design notes in+-- "Streamly.Internal.Data.Fold.Type" for background on the design.+--+-- = Parser Design+--+-- The 'Parser' type or a parsing fold is a generalization of the 'Fold' type.+-- The 'Fold' type /always/ succeeds on each input. Therefore, it does not need+-- to buffer the input. In contrast, a 'Parser' may fail and backtrack to+-- replay the input again to explore another branch of the parser. Therefore,+-- it needs to buffer the input. Therefore, a 'Parser' is a fold with some+-- additional requirements. To summarize, unlike a 'Fold', a 'Parser':+--+-- 1. may not generate a new value of the accumulator on every input, it may+-- generate a new accumulator only after consuming multiple input elements+-- (e.g. takeEQ).+-- 2. on success may return some unconsumed input (e.g. takeWhile)+-- 3. may fail and return all input without consuming it (e.g. satisfy)+-- 4. backtrack and start inspecting the past input again (e.g. alt)+--+-- These use cases require buffering and replaying of input. To facilitate+-- this, the step function of the 'Fold' is augmented to return the next state+-- of the fold along with a command tag using a 'Step' functor, the tag tells+-- the fold driver to manipulate the future input as the parser wishes. The+-- 'Step' functor provides the following commands to the fold driver+-- corresponding to the use cases outlined in the previous para:+--+-- 1. 'Continue': buffer the current input and optionally go back to a previous+-- position in the stream+-- 2. 'Partial': buffer the current input and optionally go back to a previous+-- position in the stream, drop the buffer before that position.+-- 3. 'Done': parser succeeded, returns how much input was leftover+-- 4. 'SError': indicates that the parser has failed without a result+--+-- = How a Parser Works?+--+-- A parser is just like a fold, it keeps consuming inputs from the stream and+-- accumulating them in an accumulator. The accumulator of the parser could be+-- a singleton value or it could be a collection of values e.g. a list.+--+-- The parser may build a new output value from multiple input items. When it+-- consumes an input item but needs more input to build a complete output item+-- it uses @Continue 0 s@, yielding the intermediate state @s@ and asking the+-- driver to provide more input. When the parser determines that a new output+-- value is complete it can use a @Done n b@ to terminate the parser with @n@+-- items of input unused and the final value of the accumulator returned as+-- @b@. If at any time the parser determines that the parse has failed it can+-- return @SError err@.+--+-- A parser building a collection of values (e.g. a list) can use the @Partial@+-- constructor whenever a new item in the output collection is generated. If a+-- parser building a collection of values has yielded at least one value then+-- it is considered successful and cannot fail after that. In the current+-- implementation, this is not automatically enforced, there is a rule that the+-- parser MUST use only @Done@ for termination after the first @Partial@, it+-- cannot use @SError@. It may be possible to change the implementation so that+-- this rule is not required, but there may be some performance cost to it.+--+-- 'Streamly.Internal.Data.Parser.takeWhile' and+-- 'Streamly.Internal.Data.Parser.some' combinators are good examples of+-- efficient implementations using all features of this representation. It is+-- possible to idiomatically build a collection of parsed items using a+-- singleton parser and @Alternative@ instance instead of using a+-- multi-yield parser. However, this implementation is amenable to stream+-- fusion and can therefore be much faster.+--+-- = SError Handling+--+-- When a parser's @step@ function is invoked it may terminate by either a+-- 'Done' or an 'SError' return value. In an 'Alternative' composition an error+-- return can make the composed parser backtrack and try another parser.+--+-- If the stream stops before a parser could terminate then we use the+-- @extract@ function of the parser to retrieve the last yielded value of the+-- parser. If the parser has yielded at least one value then @extract@ MUST+-- return a value without throwing an error, otherwise it uses the 'ParseError'+-- exception to throw an error.+--+-- We chose the exception throwing mechanism for @extract@ instead of using an+-- explicit error return via an 'Either' type for keeping the interface simple+-- as most of the time we do not need to catch the error in intermediate+-- layers. Note that we cannot use exception throwing mechanism in @step@+-- function because of performance reasons. 'SError' constructor in that case+-- allows loop fusion and better performance.+--+-- = Optimizing backtracking+--+-- == Applicative Composition+--+-- If a parser once returned 'Partial' it can never fail after that. This is+-- used to reduce the buffering. A 'Partial' results in dropping the buffer and+-- we cannot backtrack before that point.+--+-- Parsers can be composed using an Alternative, if we are in an alternative+-- composition we may have to backtrack to try the other branch. When we+-- compose two parsers using applicative @f <$> p1 <*> p2@ we can return a+-- 'Partial' result only after both the parsers have succeeded. While running+-- @p1@ we have to ensure that the input is not dropped until we have run @p2@,+-- therefore we have to return a Continue instead of a Partial.+--+-- However, if we know they both cannot fail then we know that the composed+-- parser can never fail. For this reason we should have "backtracking folds"+-- as a separate type so that we can compose them in an efficient manner. In p1+-- itself we can drop the buffer as soon as a 'Partial' result arrives. In+-- fact, there is no Alternative composition for folds because they cannot+-- fail.+--+-- == Alternative Composition+--+-- In @p1 <|> p2@ as soon as the parser p1 returns 'Partial' we know that it+-- will not fail and we can immediately drop the buffer.+--+-- If we are not using the parser in an alternative composition we can+-- downgrade the parser to a backtracking fold and use the "backtracking+-- fold"'s applicative for more efficient implementation. To downgrade we can+-- translate the "SError" of parser to an exception. This gives us best of both+-- worlds, the applicative as well as alternative would have optimal+-- backtracking buffer.+--+-- The "many" for parsers would be different than "many" for folds. In case of+-- folds an error would be propagated. In case of parsers the error would be+-- ignored.+--+-- = Implementation Approach+--+-- Backtracking folds have an issue with tee style composition because each+-- fold can backtrack independently, we will need independent buffers. Though+-- this may be possible to implement it may not be efficient especially for+-- folds that do not backtrack at all. Three types are possible, optimized for+-- different use cases:+--+-- * Non-backtracking folds: efficient Tee+-- * Backtracking folds: efficient applicative+-- * Parsers: alternative+--+-- Downgrade parsers to backtracking folds for applicative used without+-- alternative. Upgrade backtracking folds to parsers when we have to use them+-- as the last alternative.+--+-- = Future Work+--+-- It may make sense to move "takeWhile" type of parsers, which cannot fail but+-- need some lookahead, to splitting folds. This will allow such combinators+-- to be accepted where we need an unfailing "Fold" type.+--+-- Based on application requirements it should be possible to design even a+-- richer interface to manipulate the input stream/buffer. For example, we+-- could randomly seek into the stream in the forward or reverse directions or+-- we can even seek to the end or from the end or seek from the beginning.+--+-- We can distribute and scan/parse a stream using both folds and parsers and+-- merge the resulting streams using different merge strategies (e.g.+-- interleaving or serial).+--+-- == Naming+--+-- As far as possible, try that the names of the combinators in this module are+-- consistent with:+--+-- * <https://hackage.haskell.org/package/base/docs/Text-ParserCombinators-ReadP.html base/Text.ParserCombinators.ReadP>+-- * <http://hackage.haskell.org/package/parser-combinators parser-combinators>+-- * <http://hackage.haskell.org/package/megaparsec megaparsec>+-- * <http://hackage.haskell.org/package/attoparsec attoparsec>+-- * <http://hackage.haskell.org/package/parsec parsec>++module Streamly.Internal.Data.Parser.Type+ (+ -- * Types+ Initial (..)+ -- (..) does not seem to export patterns yet the compiler complains it does.+ , Step(Partial, Continue, Done, Error, SPartial, SContinue, SDone, SError)+ , Final(..)+ , mapCount+ , bimapOverrideCount+ , bimapMorphOverrideCount+ , Parser (..)+ , ParseError (..)+ , ParseErrorPos (..)+ , rmapM++ -- * Constructors++ , fromPure+ , fromEffect+ , splitWith+ , split_++ , die+ , dieM+ , splitSome -- parseSome?+ , splitMany -- parseMany?+ , splitManyPost+ , alt+ , concatMap++ -- * Input transformation+ , lmap+ , lmapM+ , filter++ , noErrorUnsafeSplitWith+ , noErrorUnsafeSplit_+ , noErrorUnsafeConcatMap++ , localReaderT+ )+where++#include "inline.hs"+#include "assert.hs"++#if !MIN_VERSION_base(4,18,0)+import Control.Applicative (liftA2)+#endif+import Control.Applicative (Alternative(..))+import Control.Exception (Exception(..))+-- import Control.Monad (MonadPlus(..), (>=>))+import Control.Monad ((>=>))+import Control.Monad.IO.Class (MonadIO, liftIO)+import Control.Monad.Trans.Reader (ReaderT, local)+import Data.Bifunctor (Bifunctor(..))+import Fusion.Plugin.Types (Fuse(..))+import Streamly.Internal.Data.Fold.Type (Fold(..), toList)++import qualified Control.Monad.Fail as Fail+import qualified Streamly.Internal.Data.Fold.Type as FL++import Prelude hiding (concatMap, filter)++#include "DocTestDataParser.hs"++-- XXX The only differences between Initial and Step types are:+--+-- * There are no backtracking counts in Initial+-- * Continue and Partial are the same. Ideally Partial should mean that an+-- empty result is valid and can be extracted; and Continue should mean that+-- empty would result in an error on extraction. We can possibly distinguish+-- the two cases.+--+-- If we ignore the backtracking counts we can represent the Initial type using+-- Step itself. That will also simplify the implementation of various parsers+-- where the processing in intiial is just a sepcial case of step, see+-- takeBetween for example.++-- XXX IPartial indicates that the parser has a default result and cannot fail.+-- Such parsers should rather be written as Parslets? We should use IContinue+-- in initial.++-- | The type of a 'Parser''s initial action.+--+-- /Internal/+--+{-# ANN type Initial Fuse #-}+data Initial s b+ = IPartial !s -- ^ Wait for step function to be called with state @s@.+ | IDone !b -- ^ Return a result right away without an input.+ | IError !String -- ^ Return an error right away without an input.++-- | @first@ maps on 'IPartial' and @second@ maps on 'IDone'.+--+-- /Internal/+--+instance Bifunctor Initial where+ {-# INLINE bimap #-}+ bimap f _ (IPartial a) = IPartial (f a)+ bimap _ g (IDone b) = IDone (g b)+ bimap _ _ (IError err) = IError err++-- | Maps a function over the result held by 'IDone'.+--+-- >>> fmap = second+--+-- /Internal/+--+instance Functor (Initial s) where+ {-# INLINE fmap #-}+ fmap = second++-- We can simplify the Step type as follows:+--+-- Partial Int (Either s (s, b)) -- Left continue, right partial result+-- Done Int (Either String b)+--+-- In this case SError may also have a "leftover" return. This means that after+-- several successful partial results the last segment parsing failed and we+-- are returning the leftover of that. The driver may choose to restart from+-- the last segment where this parser failed or from the beginning.+--+-- Folds can only return the right values. Parsers can also return lefts.++-- | The return type of a 'Parser' step.+--+-- /Result types/: The parser driver feeds the input stream to the parser one+-- element at a time, representing a parse 'Step'. If the step result+-- 'SPartial' indicates that a parse result is available and the parser can+-- accept more input, we can extract the result using the parser's extract+-- function and feed more input to the parser. If the result is 'SContinue', we+-- must feed more input in order to get a result. If the parser returns 'SDone'+-- then a result is available and the parser can no longer take any more input.+--+-- /Stream position/: The @n@ in @SPartial n@, @Scontinue n@ and @SDone n@ is a+-- count by which we adjust the current stream position after this step. If the+-- count is positive we move forward in the stream, if it is 0 then we stay+-- where we are, if it is negative then we move backward in the stream.+-- Essentially, if the input stream position was @pos@ before processing the+-- current element then the new stream position after processing the element+-- would be @pos + n@.+--+-- We can also think of this count as the number of items consumed by the+-- parser. If the current input item is consumed then n is 1, if the current+-- input item should be presented to the next parser step then n is 0. If @n@+-- is less than 0 then the parser backtracks by n elements before the current+-- element before the next parsing step is invoked. @n@ is not allowed to be+-- greater than 1 in the regular stream parsers, but it can be more than 1 in+-- an array parser because it can consume more than one elements from the+-- array.+--+-- /Backtracking/: If the parser result is 'SContinue', the parser driver+-- retains the input in a backtracking buffer, in case of failure the parser+-- can backtrack maximum up to the length of the backtracking buffer. Whenever+-- the result is `SPartial` the current backtracking buffer is discarded; this+-- means that we cannot backtrack beyond the currrent position in the stream.+-- The parser must ensure that the backtrack position is always within the+-- bounds of the backtracking buffer, otherwise a runtime error will occur.+--+-- /Failure/: If the parser is not yet done, we can use the @extract@ operation+-- on the @state@ of the parser to extract a result. If the parser never+-- yielded a result in the past, @extract@ fails with a 'ParseError' exception.+-- If the parser yielded a 'Partial' result in the past then extract returns+-- the latest partial result. Therefore, if a parser yields a partial result+-- once then it cannot fail later on.+--+-- /Pre-release/+--+{-# ANN type Step Fuse #-}+data Step s b =+ SPartial !Int !s+ -- ^ @SPartial count state@. The following statements hold on an SPartial+ -- result:+ --+ -- 1. @extract@ on @state@ would succeed and give a result.+ -- 2. Input stream position is updated to @current position + count@.+ -- 3. All buffered input before the new position is dropped. The parser can+ -- never backtrack before this position.++ | SContinue !Int !s+ -- ^ @SContinue count state@. The following statements hold on an SContinue+ -- result:+ --+ -- 1. If 'SPartial' result was returned in the past, @extract@ on @state@+ -- would give that result otherwise it will return 'SError' or 'SContinue'.+ -- 2. Input stream position is updated to @current position + count@.+ -- 3. the previous input is retained in a backtrack buffer.++ | SDone !Int !b+ -- ^ Done with leftover input count and result.+ --+ -- @SDone count result@ means the parser has finished, it will not accept+ -- any more input, the final stream position must be set to @current+ -- position + count@ and the result of the parser is in @result@.++ | SError !String+ -- ^ Parser failed without generating any output.+ --+ -- The parsing operation may backtrack to the beginning and try another+ -- alternative.+ deriving (Show)++{-# ANN type Final Fuse #-}+data Final s b+ = FDone !Int !b -- ^ Return a result right away without an input.+ | FContinue !Int !s+ | FError !String -- ^ Return an error right away without an input.++--------------------------------------------------------------------------------+-- Custom Patterns+--------------------------------------------------------------------------------++negateDirection :: Step s b -> Step s b+negateDirection (SPartial i s) = SPartial (1 - i) s+negateDirection (SContinue i s) = SContinue (1 - i) s+negateDirection (SDone i b) = SDone (1 - i) b+negateDirection (SError s) = SError s++{-# DEPRECATED Error "Use @SError@ instead of @Error@" #-}+pattern Error :: String -> Step s b+pattern Error s = SError s++{-# DEPRECATED Partial "Use @SPartial (1 - n)@ instead of @Partial n@" #-}+pattern Partial :: Int -> s -> Step s b+pattern Partial i s <- (negateDirection -> SPartial i s)+ where Partial i s = SPartial (1 - i) s++{-# DEPRECATED Continue "Replace @Continue n@ with @SContinue (1 - n)@ in parser step and with @FContinue (-n)@ in parser extract" #-}+pattern Continue :: Int -> s -> Step s b+pattern Continue i s <- (negateDirection -> SContinue i s)+ where Continue i s = SContinue (1 - i) s++{-# DEPRECATED Done "Replace @Done n@ with @SDone (1 - n)@ in parser step and with @FDone (-n)@ in parser extract" #-}+pattern Done :: Int -> b -> Step s b+pattern Done i b <- (negateDirection -> SDone i b)+ where Done i b = SDone (1 - i) b++--------------------------------------------------------------------------------+-- Code+--------------------------------------------------------------------------------++-- | Map first function over the state and second over the result.+instance Bifunctor Step where+ {-# INLINE bimap #-}+ bimap f g step =+ case step of+ SPartial n s -> SPartial n (f s)+ SContinue n s -> SContinue n (f s)+ SDone n b -> SDone n (g b)+ SError err -> SError err++instance Bifunctor Final where+ {-# INLINE bimap #-}+ bimap f g step =+ case step of+ FContinue n s -> FContinue n (f s)+ FDone n b -> FDone n (g b)+ FError err -> FError err++-- | Bimap discarding the count, and using the supplied count instead.+bimapOverrideCount :: Int -> (s -> s1) -> (b -> b1) -> Step s b -> Step s1 b1+bimapOverrideCount n f g step =+ case step of+ SPartial _ s -> SPartial n (f s)+ SContinue _ s -> SContinue n (f s)+ SDone _ b -> SDone n (g b)+ SError err -> SError err++bimapMorphOverrideCount :: Int -> (s -> s1) -> (b -> b1) -> Final s b -> Step s1 b1+bimapMorphOverrideCount n f g step =+ case step of+ FDone _ b -> SDone n (g b)+ FContinue _ s -> SContinue n (f s)+ FError err -> SError err++bimapFinalOverrideCount :: Int -> (s -> s1) -> (b -> b1) -> Final s b -> Final s1 b1+bimapFinalOverrideCount n f g step =+ case step of+ FContinue _ s -> FContinue n (f s)+ FDone _ b -> FDone n (g b)+ FError err -> FError err++-- | fmap = second+instance Functor (Step s) where+ {-# INLINE fmap #-}+ fmap = second++instance Functor (Final s) where+ {-# INLINE fmap #-}+ fmap = second++-- | Map a function over the count.+--+{-# INLINE mapCount #-}+mapCount :: (Int -> Int) -> Step s b -> Step s b+mapCount f res =+ case res of+ SPartial n s -> SPartial (f n) s+ SDone n b -> SDone (f n) b+ SContinue n s -> SContinue (f n) s+ SError err -> SError err++-- | Map a monadic function over the result @b@ in @Step s b@.+--+-- /Internal/+{-# INLINE mapMStep #-}+mapMStep :: Applicative m => (a -> m b) -> Step s a -> m (Step s b)+mapMStep f res =+ case res of+ SPartial n s -> pure $ SPartial n s+ SDone n b -> SDone n <$> f b+ SContinue n s -> pure $ SContinue n s+ SError err -> pure $ SError err++{-# INLINE mapMFinal #-}+mapMFinal :: Applicative m => (a -> m b) -> Final s a -> m (Final s b)+mapMFinal f res =+ case res of+ FDone n b -> FDone n <$> f b+ FContinue n s -> pure $ FContinue n s+ FError err -> pure $ FError err++-- | A parser is a fold that can fail and is represented as @Parser step+-- initial extract@. Before we drive a parser we call the @initial@ action to+-- retrieve the initial state of the fold. The parser driver invokes @step@+-- with the state returned by the previous step and the next input element. It+-- results into a new state and a command to the driver represented by 'Step'+-- type. The driver keeps invoking the step function until it stops or fails.+-- At any point of time the driver can call @extract@ to inspect the result of+-- the fold. If the parser hits the end of input 'extract' is called.+-- It may result in an error or an output value.+--+-- /Pre-release/+--+data Parser a m b =+ forall s. Parser+ (s -> a -> m (Step s b))+ (m (Initial s b))+ (s -> m (Final s b))++-- | This exception is used when a parser ultimately fails, the user of the+-- parser is intimated via this exception.+--+newtype ParseError = ParseError String+ deriving (Eq, Show)++instance Exception ParseError where+ displayException (ParseError err) = err++-- | Like 'ParseError' but reports the stream position where the error ocurred.+-- The @Int@ is the position in the stream where the error ocurred. This+-- exception is used by position reporting parser drivers.+data ParseErrorPos = ParseErrorPos Int String+ deriving (Eq, Show)++instance Exception ParseErrorPos where+ displayException (ParseErrorPos pos err) =+ concat ["At ", show pos, ":", err]++-- | Map a function on the result i.e. on @b@ in @Parser a m b@.+instance Functor m => Functor (Parser a m) where+ {-# INLINE fmap #-}+ fmap f (Parser step1 initial1 extract) =+ Parser step initial (fmap3 f extract)++ where++ initial = fmap2 f initial1+ step s b = fmap2 f (step1 s b)+ fmap2 g = fmap (fmap g)+ fmap3 g = fmap (fmap (fmap g))++------------------------------------------------------------------------------+-- Mapping on the output+------------------------------------------------------------------------------++-- | @rmapM f parser@ maps the monadic function @f@ on the output of the parser.+--+-- >>> rmap = fmap+{-# INLINE rmapM #-}+rmapM :: Monad m => (b -> m c) -> Parser a m b -> Parser a m c+rmapM f (Parser step initial extract) =+ Parser step1 initial1 (extract >=> mapMFinal f)++ where++ initial1 = do+ res <- initial+ -- this is mapM f over result+ case res of+ IPartial x -> return $ IPartial x+ IDone a -> IDone <$> f a+ IError err -> return $ IError err+ step1 s a = step s a >>= mapMStep f++-- | A parser that always yields a pure value without consuming any input.+--+{-# INLINE_NORMAL fromPure #-}+fromPure :: Monad m => b -> Parser a m b+fromPure b = Parser undefined (pure $ IDone b) undefined++-- | A parser that always yields the result of an effectful action without+-- consuming any input.+--+{-# INLINE fromEffect #-}+fromEffect :: Monad m => m b -> Parser a m b+fromEffect b = Parser undefined (IDone <$> b) undefined++-------------------------------------------------------------------------------+-- Sequential applicative+-------------------------------------------------------------------------------++{-# ANN type SeqParseState Fuse #-}+data SeqParseState sl f sr = SeqParseL !sl | SeqParseR !f !sr++-- Note: this implementation of splitWith is fast because of stream fusion but+-- has quadratic time complexity, because each composition adds a new branch+-- that each subsequent parse's input element has to go through, therefore, it+-- cannot scale to a large number of compositions. After around 100+-- compositions the performance starts dipping rapidly beyond a CPS style+-- unfused implementation.+--+-- Note: This is a parsing dual of appending streams using+-- 'Streamly.Data.Stream.append', it splits the streams using two parsers and+-- zips the results.++-- | Sequential parser application. Apply two parsers sequentially to an input+-- stream. The first parser runs and processes the input, the remaining input+-- is then passed to the second parser. If both parsers succeed, their outputs+-- are combined using the supplied function. If either parser fails, the+-- operation fails.+--+-- This combinator delivers high performance by stream fusion but it comes with+-- some limitations. For those cases use the 'Applicative' instance of+-- 'Streamly.Data.ParserK.ParserK'.+--+-- CAVEAT 1: NO RECURSION. This function is strict in both arguments. As a+-- result, if a parser is defined recursively using this, it may cause an+-- infintie loop. The following example checks the strictness:+--+-- >>> p = Parser.splitWith const (Parser.satisfy (> 0)) undefined+-- >>> Stream.parse p $ Stream.fromList [1]+-- *** Exception: Prelude.undefined+-- ...+--+-- CAVEAT 2: QUADRATIC TIME COMPLEXITY. Static composition is fast due to+-- stream fusion, but it works well only for limited (e.g. up to 8)+-- compositions, use "Streamly.Data.ParserK" for larger compositions.+--+-- Below are some common idioms that can be expressed using 'splitWith':+--+-- >>> span p f1 f2 = Parser.splitWith (,) (Parser.takeWhile p f1) (Parser.fromFold f2)+-- >>> spanBy eq f1 f2 = Parser.splitWith (,) (Parser.groupBy eq f1) (Parser.fromFold f2)+--+-- /Pre-release/+--+{-# INLINE splitWith #-}+splitWith :: Monad m+ => (a -> b -> c) -> Parser x m a -> Parser x m b -> Parser x m c+splitWith func (Parser stepL initialL extractL)+ (Parser stepR initialR extractR) =+ Parser step initial extract++ where++ initial = do+ -- XXX We can use bimap here if we make this a Step type+ resL <- initialL+ case resL of+ IPartial sl -> return $ IPartial $ SeqParseL sl+ IDone bl -> do+ resR <- initialR+ -- XXX We can use bimap here if we make this a Step type+ return $ case resR of+ IPartial sr -> IPartial $ SeqParseR (func bl) sr+ IDone br -> IDone (func bl br)+ IError err -> IError err+ IError err -> return $ IError err++ -- Note: For the composed parse to terminate, the left parser has to be+ -- a terminating parser returning a Done at some point.+ step (SeqParseL st) a = do+ -- Important: Please do not use Applicative here. See+ -- https://github.com/composewell/streamly/issues/1033 and the problem+ -- defined in split_ for more info.+ -- XXX Use bimap+ resL <- stepL st a+ case resL of+ -- Note: We need to buffer the input for a possible Alternative+ -- e.g. in ((,) <$> p1 <*> p2) <|> p3, if p2 fails we have to+ -- backtrack and start running p3. So we need to keep the input+ -- buffered until we know that the applicative cannot fail.+ SPartial n s -> return $ SContinue n (SeqParseL s)+ SContinue n s -> return $ SContinue n (SeqParseL s)+ SDone n b -> do+ -- XXX Use bimap if we make this a Step type+ -- fmap (bimap (SeqParseR (func b)) (func b)) initialR+ initR <- initialR+ return $ case initR of+ IPartial sr -> SContinue n $ SeqParseR (func b) sr+ IDone br -> SDone n (func b br)+ IError err -> SError err+ SError err -> return $ SError err++ step (SeqParseR f st) a = fmap (bimap (SeqParseR f) f) (stepR st a)++ extract (SeqParseR f sR) = fmap (bimap (SeqParseR f) f) (extractR sR)+ extract (SeqParseL sL) = do+ -- XXX Use bimap here+ rL <- extractL sL+ case rL of+ FDone n bL -> do+ -- XXX Use bimap here if we use Step type in Initial+ iR <- initialR+ case iR of+ IPartial sR -> do+ fmap+ (bimap (SeqParseR (func bL)) (func bL))+ (extractR sR)+ IDone bR -> return $ FDone n $ func bL bR+ IError err -> return $ FError err+ FError err -> return $ FError err+ FContinue n s -> return $ FContinue n (SeqParseL s)++-------------------------------------------------------------------------------+-- Sequential applicative for backtracking folds+-------------------------------------------------------------------------------++-- XXX Create a newtype for nonfailing parsers and downgrade the parser to that+-- type before this operation and then upgrade.+--+-- We can do an inspection testing to reject unwanted constructors at compile+-- time.+--+-- We can use the compiler to automatically annotate accumulators, terminating+-- folds, non-failing parsers and failing parsers.++-- | Better performance 'splitWith' for non-failing parsers.+--+-- Does not work correctly for parsers that can fail.+--+-- ALL THE CAVEATS IN 'splitWith' APPLY HERE AS WELL.+--+{-# INLINE noErrorUnsafeSplitWith #-}+noErrorUnsafeSplitWith :: Monad m+ => (a -> b -> c) -> Parser x m a -> Parser x m b -> Parser x m c+noErrorUnsafeSplitWith func (Parser stepL initialL extractL)+ (Parser stepR initialR extractR) =+ Parser step initial extract++ where++ errMsg e = error $ "noErrorUnsafeSplitWith: unreachable: " ++ e++ initial = do+ resL <- initialL+ case resL of+ IPartial sl -> return $ IPartial $ SeqParseL sl+ IDone bl -> do+ resR <- initialR+ return $ bimap (SeqParseR (func bl)) (func bl) resR+ IError err -> errMsg err++ -- Note: For the composed parse to terminate, the left parser has to be+ -- a terminating parser returning a SDone at some point.+ step (SeqParseL st) a = do+ r <- stepL st a+ case r of+ -- Assume that the parser can never fail, therefore, we do not+ -- need to keep the input for backtracking.+ SPartial n s -> return $ SPartial n (SeqParseL s)+ SContinue n s -> return $ SContinue n (SeqParseL s)+ SDone n b -> do+ res <- initialR+ return+ $ case res of+ IPartial sr -> SPartial n $ SeqParseR (func b) sr+ IDone br -> SDone n (func b br)+ IError err -> errMsg err+ SError err -> errMsg err++ step (SeqParseR f st) a = fmap (bimap (SeqParseR f) f) (stepR st a)++ extract (SeqParseR f sR) = fmap (bimap (SeqParseR f) f) (extractR sR)++ extract (SeqParseL sL) = do+ rL <- extractL sL+ case rL of+ FDone n bL -> do+ iR <- initialR+ case iR of+ IPartial sR -> do+ rR <- extractR sR+ return+ $ bimapFinalOverrideCount+ n (SeqParseR (func bL)) (func bL) rR+ IDone bR -> return $ FDone n $ func bL bR+ IError err -> errMsg err+ FError err -> errMsg err+ FContinue n s -> return $ FContinue n (SeqParseL s)++{-# ANN type SeqAState Fuse #-}+data SeqAState sl sr = SeqAL !sl | SeqAR !sr++-- This turns out to be slightly faster than splitWith++-- | Sequential parser application ignoring the output of the first parser.+-- Apply two parsers sequentially to an input stream. The input is provided to+-- the first parser, when it is done the remaining input is provided to the+-- second parser. The output of the parser is the output of the second parser.+-- The operation fails if any of the parsers fail.+--+-- ALL THE CAVEATS IN 'splitWith' APPLY HERE AS WELL.+--+-- This implementation is strict in the second argument, therefore, the+-- following will fail:+--+-- >>> Stream.parse (Parser.split_ (Parser.satisfy (> 0)) undefined) $ Stream.fromList [1]+-- *** Exception: Prelude.undefined+-- ...+--+-- /Pre-release/+--+{-# INLINE split_ #-}+split_ :: Monad m => Parser x m a -> Parser x m b -> Parser x m b+split_ (Parser stepL initialL extractL) (Parser stepR initialR extractR) =+ Parser step initial extract++ where++ initial = do+ resL <- initialL+ case resL of+ IPartial sl -> return $ IPartial $ SeqAL sl+ IDone _ -> do+ resR <- initialR+ return $ first SeqAR resR+ IError err -> return $ IError err++ -- Note: For the composed parse to terminate, the left parser has to be+ -- a terminating parser returning a SDone at some point.+ step (SeqAL st) a = do+ -- Important: Do not use Applicative here. Applicative somehow caused+ -- the right action to run many times, not sure why though.+ resL <- stepL st a+ case resL of+ -- Note: this leads to buffering even if we are not in an+ -- Alternative composition.+ SPartial n s -> return $ SContinue n (SeqAL s)+ SContinue n s -> return $ SContinue n (SeqAL s)+ SDone n _ -> do+ initR <- initialR+ return $ case initR of+ IPartial s -> SContinue n (SeqAR s)+ IDone b -> SDone n b+ IError err -> SError err+ SError err -> return $ SError err++ step (SeqAR st) a = first SeqAR <$> stepR st a++ extract (SeqAR sR) = fmap (first SeqAR) (extractR sR)+ extract (SeqAL sL) = do+ rL <- extractL sL+ case rL of+ FDone n _ -> do+ iR <- initialR+ -- XXX For initial we can have a bimap with leftover.+ case iR of+ IPartial sR ->+ fmap (bimapFinalOverrideCount n SeqAR id) (extractR sR)+ IDone bR -> return $ FDone n bR+ IError err -> return $ FError err+ FError err -> return $ FError err+ FContinue n s -> return $ FContinue n (SeqAL s)++-- | Better performance 'split_' for non-failing parsers.+--+-- Does not work correctly for parsers that can fail.+--+-- ALL THE CAVEATS IN 'splitWith' APPLY HERE AS WELL.+--+{-# INLINE noErrorUnsafeSplit_ #-}+noErrorUnsafeSplit_ :: Monad m => Parser x m a -> Parser x m b -> Parser x m b+noErrorUnsafeSplit_+ (Parser stepL initialL extractL) (Parser stepR initialR extractR) =+ Parser step initial extract++ where++ errMsg e = error $ "noErrorUnsafeSplit_: unreachable: " ++ e++ initial = do+ resL <- initialL+ case resL of+ IPartial sl -> return $ IPartial $ SeqAL sl+ IDone _ -> do+ resR <- initialR+ return $ first SeqAR resR+ IError err -> errMsg err++ -- Note: For the composed parse to terminate, the left parser has to be+ -- a terminating parser returning a SDone at some point.+ step (SeqAL st) a = do+ -- Important: Please do not use Applicative here. Applicative somehow+ -- caused the next action to run many times in the "tar" parsing code,+ -- not sure why though.+ resL <- stepL st a+ case resL of+ SPartial n s -> return $ SPartial n (SeqAL s)+ SContinue n s -> return $ SContinue n (SeqAL s)+ SDone n _ -> do+ initR <- initialR+ return $ case initR of+ IPartial s -> SPartial n (SeqAR s)+ IDone b -> SDone n b+ IError err -> errMsg err+ SError err -> errMsg err++ step (SeqAR st) a = first SeqAR <$> stepR st a++ extract (SeqAR sR) = fmap (first SeqAR) (extractR sR)+ extract (SeqAL sL) = do+ rL <- extractL sL+ case rL of+ FDone n _ -> do+ iR <- initialR+ case iR of+ IPartial sR -> do+ fmap (bimapFinalOverrideCount n SeqAR id) (extractR sR)+ IDone bR -> return $ FDone n bR+ IError err -> errMsg err+ FError err -> errMsg err+ FContinue n s -> return $ FContinue n (SeqAL s)++-- | READ THE CAVEATS in 'splitWith' before using this instance.+--+-- >>> pure = Parser.fromPure+-- >>> (<*>) = Parser.splitWith id+-- >>> (*>) = Parser.split_+instance Monad m => Applicative (Parser a m) where+ {-# INLINE pure #-}+ pure = fromPure++ {-# INLINE (<*>) #-}+ (<*>) = splitWith id++ {-# INLINE (*>) #-}+ (*>) = split_++ {-# INLINE liftA2 #-}+ liftA2 f x = (<*>) (fmap f x)++-------------------------------------------------------------------------------+-- Sequential Alternative+-------------------------------------------------------------------------------++{-# ANN type AltParseState Fuse #-}+data AltParseState sl sr = AltParseL !Int !sl | AltParseR !sr++-- Note: this implementation of alt is fast because of stream fusion but has+-- quadratic time complexity, because each composition adds a new branch that+-- each subsequent alternative's input element has to go through, therefore, it+-- cannot scale to a large number of compositions++-- | Sequential alternative. The input is first passed to the first parser,+-- if it succeeds, the result is returned. However, if the first parser fails,+-- the parser driver backtracks and tries the same input on the second+-- (alternative) parser, returning the result if it succeeds.+--+-- This combinator delivers high performance by stream fusion but it comes with+-- some limitations. For those cases use the 'Alternative' instance of+-- 'Streamly.Data.ParserK.ParserK'.+--+-- CAVEAT 1: NO RECURSION. This function is strict in both arguments. As a+-- result, if a parser is defined recursively using this, it may cause an+-- infintie loop. The following example checks the strictness:+--+-- >> p = Parser.satisfy (> 0) `Parser.alt` undefined+-- >> Stream.parse p $ Stream.fromList [1..10]+-- *** Exception: Prelude.undefined+--+-- CAVEAT 2: QUADRATIC TIME COMPLEXITY. Static composition is fast due to+-- stream fusion, but it works well only for limited (e.g. up to 8)+-- compositions, use "Streamly.Data.ParserK" for larger compositions.+--+-- /Time Complexity:/ O(n^2) where n is the number of compositions.+--+-- /Pre-release/+--+{-# INLINE alt #-}+alt :: Monad m => Parser x m a -> Parser x m a -> Parser x m a+alt (Parser stepL initialL extractL) (Parser stepR initialR extractR) =+ Parser step initial extract++ where++ initial = do+ resL <- initialL+ case resL of+ IPartial sl -> return $ IPartial $ AltParseL 0 sl+ IDone bl -> return $ IDone bl+ IError _ -> do+ resR <- initialR+ return $ case resR of+ IPartial sr -> IPartial $ AltParseR sr+ IDone br -> IDone br+ IError err -> IError err++ -- Once a parser yields at least one value it cannot fail. This+ -- restriction helps us make backtracking more efficient, as we do not need+ -- to keep the consumed items buffered after a yield. Note that we do not+ -- enforce this and if a misbehaving parser does not honor this then we can+ -- get unexpected results. XXX Can we detect and flag this?+ step (AltParseL cnt st) a = do+ r <- stepL st a+ case r of+ SPartial n s -> return $ SPartial n (AltParseL 0 s)+ SContinue n s -> do+ assertM(cnt + n >= 0)+ return $ SContinue n (AltParseL (cnt + n) s)+ SDone n b -> return $ SDone n b+ SError _ -> do+ res <- initialR+ return+ $ case res of+ IPartial rR -> SContinue (negate cnt) (AltParseR rR)+ IDone b -> SDone (negate cnt) b+ IError err -> SError err++ step (AltParseR st) a = do+ r <- stepR st a+ return $ case r of+ SPartial n s -> SPartial n (AltParseR s)+ SContinue n s -> SContinue n (AltParseR s)+ SDone n b -> SDone n b+ SError err -> SError err++ extract (AltParseR sR) = fmap (first AltParseR) (extractR sR)++ extract (AltParseL cnt sL) = do+ rL <- extractL sL+ case rL of+ FDone n b -> return $ FDone n b+ FError _ -> do+ res <- initialR+ return+ $ case res of+ IPartial rR -> FContinue (- cnt) (AltParseR rR)+ IDone b -> FDone (- cnt) b+ IError err -> FError err+ FContinue n s -> do+ assertM(n == (- cnt))+ return $ FContinue n (AltParseL 0 s)++{-# ANN type Fused3 Fuse #-}+data Fused3 a b c = Fused3 !a !b !c++-- | See documentation of 'Streamly.Internal.Data.Parser.many'.+--+-- /Pre-release/+--+{-# INLINE splitMany #-}+splitMany :: Monad m => Parser a m b -> Fold m b c -> Parser a m c+splitMany (Parser step1 initial1 extract1) (Fold fstep finitial _ ffinal) =+ Parser step initial extract++ where++ -- Caution! There is mutual recursion here, inlining the right functions is+ -- important.++ handleCollect partial done fres =+ case fres of+ FL.Partial fs -> do+ pres <- initial1+ case pres of+ IPartial ps -> return $ partial $ Fused3 ps 0 fs+ IDone pb ->+ runCollectorWith (handleCollect partial done) fs pb+ IError _ -> done <$> ffinal fs+ FL.Done fb -> return $ done fb++ runCollectorWith cont fs pb = fstep fs pb >>= cont++ -- See notes in Fold.many for the reason why the parser must be initialized+ -- right away instead of on first input.+ initial = finitial >>= handleCollect IPartial IDone++ {-# INLINE step #-}+ step (Fused3 st cnt fs) a = do+ r <- step1 st a+ case r of+ SPartial n s -> do+ assertM(cnt + n >= 0)+ return $ SContinue n (Fused3 s (cnt + n) fs)+ SContinue n s -> do+ assertM(cnt + n >= 0)+ return $ SContinue n (Fused3 s (cnt + n) fs)+ SDone n b -> do+ assertM(cnt + n >= 0)+ fstep fs b >>= handleCollect (SPartial n) (SDone n)+ SError _ -> do+ xs <- ffinal fs+ -- XXX review, need a test for this+ return $ SDone (- cnt) xs++ extract (Fused3 _ 0 fs) = fmap (FDone 0) (ffinal fs)+ extract (Fused3 s cnt fs) = do+ r <- extract1 s+ case r of+ FError _ -> fmap (FDone (- cnt)) (ffinal fs)+ FDone n b -> do+ assertM((- n) <= cnt)+ fs1 <- fstep fs b+ case fs1 of+ FL.Partial s1 -> fmap (FDone n) (ffinal s1)+ FL.Done b1 -> return (FDone n b1)+ FContinue n s1 -> do+ assertM((- n) == cnt)+ return (FContinue n (Fused3 s1 0 fs))++-- | Like splitMany, but inner fold emits an output at the end even if no input+-- is received.+--+-- /Internal/+--+{-# INLINE splitManyPost #-}+splitManyPost :: Monad m => Parser a m b -> Fold m b c -> Parser a m c+splitManyPost (Parser step1 initial1 extract1) (Fold fstep finitial _ ffinal) =+ Parser step initial extract++ where++ -- Caution! There is mutual recursion here, inlining the right functions is+ -- important.++ handleCollect partial done fres =+ case fres of+ FL.Partial fs -> do+ pres <- initial1+ case pres of+ IPartial ps -> return $ partial $ Fused3 ps 0 fs+ IDone pb ->+ runCollectorWith (handleCollect partial done) fs pb+ IError _ -> done <$> ffinal fs+ FL.Done fb -> return $ done fb++ runCollectorWith cont fs pb = fstep fs pb >>= cont++ initial = finitial >>= handleCollect IPartial IDone++ {-# INLINE step #-}+ step (Fused3 st cnt fs) a = do+ r <- step1 st a+ case r of+ SPartial n s -> do+ assertM(cnt + n >= 0)+ return $ SContinue n (Fused3 s (cnt + n) fs)+ SContinue n s -> do+ assertM(cnt + n >= 0)+ return $ SContinue n (Fused3 s (cnt + n) fs)+ SDone n b -> do+ assertM(cnt + n >= 0)+ fstep fs b >>= handleCollect (SPartial n) (SDone n)+ SError _ -> do+ xs <- ffinal fs+ return $ SDone (- cnt) xs++ extract (Fused3 s cnt fs) = do+ r <- extract1 s+ case r of+ FError _ -> fmap (FDone (- cnt)) (ffinal fs)+ FDone n b -> do+ assertM((- n) <= cnt)+ fs1 <- fstep fs b+ case fs1 of+ FL.Partial s1 -> fmap (FDone n) (ffinal s1)+ FL.Done b1 -> return (FDone n b1)+ FContinue n s1 -> do+ assertM((- n) == cnt)+ return (FContinue n (Fused3 s1 0 fs))++-- | See documentation of 'Streamly.Internal.Data.Parser.some'.+--+-- /Pre-release/+--+{-# INLINE splitSome #-}+splitSome :: Monad m => Parser a m b -> Fold m b c -> Parser a m c+splitSome (Parser step1 initial1 extract1) (Fold fstep finitial _ ffinal) =+ Parser step initial extract++ where++ -- Caution! There is mutual recursion here, inlining the right functions is+ -- important.++ handleCollect partial done fres =+ case fres of+ FL.Partial fs -> do+ pres <- initial1+ case pres of+ IPartial ps -> return $ partial $ Fused3 ps 0 $ Right fs+ IDone pb ->+ runCollectorWith (handleCollect partial done) fs pb+ IError _ -> done <$> ffinal fs+ FL.Done fb -> return $ done fb++ runCollectorWith cont fs pb = fstep fs pb >>= cont++ initial = do+ fres <- finitial+ case fres of+ FL.Partial fs -> do+ pres <- initial1+ case pres of+ IPartial ps -> return $ IPartial $ Fused3 ps 0 $ Left fs+ IDone pb ->+ runCollectorWith (handleCollect IPartial IDone) fs pb+ IError err -> return $ IError err+ FL.Done _ ->+ return+ $ IError+ $ "splitSome: The collecting fold terminated without"+ ++ " consuming any elements."++ {-# INLINE step #-}+ step (Fused3 st cnt (Left fs)) a = do+ r <- step1 st a+ -- In the Left state, count is used only for the assert+ case r of+ SPartial n s -> do+ assertM(cnt + n >= 0)+ return $ SContinue n (Fused3 s (cnt + n) (Left fs))+ SContinue n s -> do+ assertM(cnt + n >= 0)+ return $ SContinue n (Fused3 s (cnt + n) (Left fs))+ SDone n b -> do+ assertM(cnt + n >= 0)+ fstep fs b >>= handleCollect (SPartial n) (SDone n)+ SError err -> return $ SError err+ step (Fused3 st cnt (Right fs)) a = do+ r <- step1 st a+ case r of+ SPartial n s -> do+ assertM(cnt + n >= 0)+ return $ SPartial n (Fused3 s (cnt + n) (Right fs))+ SContinue n s -> do+ assertM(cnt + n >= 0)+ return $ SContinue n (Fused3 s (cnt + n) (Right fs))+ SDone n b -> do+ assertM(cnt + n >= 0)+ fstep fs b >>= handleCollect (SPartial n) (SDone n)+ SError _ -> SDone (- cnt) <$> ffinal fs++ extract (Fused3 s cnt (Left fs)) = do+ r <- extract1 s+ case r of+ FError err -> return (FError err)+ FDone n b -> do+ assertM((- n) <= cnt)+ fs1 <- fstep fs b+ case fs1 of+ FL.Partial s1 -> fmap (FDone n) (ffinal s1)+ FL.Done b1 -> return (FDone n b1)+ FContinue n s1 -> do+ assertM((- n) == cnt)+ return (FContinue n (Fused3 s1 0 (Left fs)))+ extract (Fused3 s cnt (Right fs)) = do+ r <- extract1 s+ case r of+ FError _ -> fmap (FDone (- cnt)) (ffinal fs)+ FDone n b -> do+ assertM((- n) <= cnt)+ fs1 <- fstep fs b+ case fs1 of+ FL.Partial s1 -> fmap (FDone n) (ffinal s1)+ FL.Done b1 -> return (FDone n b1)+ FContinue n s1 -> do+ assertM((- n) == cnt)+ return (FContinue n (Fused3 s1 0 (Right fs)))++-- | A parser that always fails with an error message without consuming+-- any input.+--+{-# INLINE_NORMAL die #-}+die :: Monad m => String -> Parser a m b+die err = Parser undefined (pure (IError err)) undefined++-- | A parser that always fails with an effectful error message and without+-- consuming any input.+--+-- /Pre-release/+--+{-# INLINE dieM #-}+dieM :: Monad m => m String -> Parser a m b+dieM err = Parser undefined (IError <$> err) undefined++-- Note: The default implementations of "some" and "many" loop infinitely+-- because of the strict pattern match on both the arguments in applicative and+-- alternative. With the direct style parser type we cannot use the mutually+-- recursive definitions of "some" and "many".+--+-- Note: With the direct style parser type, the list in "some" and "many" is+-- accumulated strictly, it cannot be consumed lazily.++-- | READ THE CAVEATS in 'alt' before using this instance.+--+-- >>> empty = Parser.die "empty"+-- >>> (<|>) = Parser.alt+-- >>> many = flip Parser.many Fold.toList+-- >>> some = flip Parser.some Fold.toList+instance Monad m => Alternative (Parser a m) where+ {-# INLINE empty #-}+ empty = die "empty"++ {-# INLINE (<|>) #-}+ (<|>) = alt++ {-# INLINE many #-}+ many = flip splitMany toList++ {-# INLINE some #-}+ some = flip splitSome toList++{-# ANN type ConcatParseState Fuse #-}+data ConcatParseState sl m a b =+ ConcatParseL !sl+ | forall s. ConcatParseR (s -> a -> m (Step s b)) s (s -> m (Final s b))++-- XXX Does it fuse completely? Check and update, it cannot fuse the+-- dynamically generated parser.++-- | Map a 'Parser' returning function on the result of a 'Parser'.+--+-- ALL THE CAVEATS IN 'splitWith' APPLY HERE AS WELL.+--+-- /Pre-release/+--+{-# INLINE concatMap #-}+concatMap :: Monad m =>+ (b -> Parser a m c) -> Parser a m b -> Parser a m c+concatMap func (Parser stepL initialL extractL) = Parser step initial extract++ where++ {-# INLINE initializeR #-}+ initializeR (Parser stepR initialR extractR) = do+ resR <- initialR+ return $ case resR of+ IPartial sr -> IPartial $ ConcatParseR stepR sr extractR+ IDone br -> IDone br+ IError err -> IError err++ initial = do+ res <- initialL+ case res of+ IPartial s -> return $ IPartial $ ConcatParseL s+ IDone b -> initializeR (func b)+ IError err -> return $ IError err++ {-# INLINE initializeRL #-}+ initializeRL n (Parser stepR initialR extractR) = do+ resR <- initialR+ return $ case resR of+ IPartial sr -> SContinue n $ ConcatParseR stepR sr extractR+ IDone br -> SDone n br+ IError err -> SError err++ step (ConcatParseL st) a = do+ r <- stepL st a+ case r of+ SPartial n s -> return $ SContinue n (ConcatParseL s)+ SContinue n s -> return $ SContinue n (ConcatParseL s)+ SDone n b -> initializeRL n (func b)+ SError err -> return $ SError err++ step (ConcatParseR stepR st extractR) a = do+ r <- stepR st a+ return $ case r of+ SPartial n s -> SPartial n $ ConcatParseR stepR s extractR+ SContinue n s -> SContinue n $ ConcatParseR stepR s extractR+ SDone n b -> SDone n b+ SError err -> SError err++ {-# INLINE extractP #-}+ extractP n (Parser stepR initialR extractR) = do+ res <- initialR+ case res of+ IPartial s ->+ fmap+ (first (\s1 -> ConcatParseR stepR s1 extractR))+ (extractR s)+ IDone b -> return (FDone n b)+ IError err -> return $ FError err++ extract (ConcatParseR stepR s extractR) =+ fmap (first (\s1 -> ConcatParseR stepR s1 extractR)) (extractR s)+ extract (ConcatParseL sL) = do+ rL <- extractL sL+ case rL of+ FError err -> return $ FError err+ FDone n b -> extractP n $ func b+ FContinue n s -> return $ FContinue n (ConcatParseL s)++-- | Better performance 'concatMap' for non-failing parsers.+--+-- Does not work correctly for parsers that can fail.+--+-- ALL THE CAVEATS IN 'splitWith' APPLY HERE AS WELL.+--+{-# INLINE noErrorUnsafeConcatMap #-}+noErrorUnsafeConcatMap :: Monad m =>+ (b -> Parser a m c) -> Parser a m b -> Parser a m c+noErrorUnsafeConcatMap func (Parser stepL initialL extractL) =+ Parser step initial extract++ where++ {-# INLINE initializeR #-}+ initializeR (Parser stepR initialR extractR) = do+ resR <- initialR+ return $ case resR of+ IPartial sr -> IPartial $ ConcatParseR stepR sr extractR+ IDone br -> IDone br+ IError err -> IError err++ initial = do+ res <- initialL+ case res of+ IPartial s -> return $ IPartial $ ConcatParseL s+ IDone b -> initializeR (func b)+ IError err -> return $ IError err++ {-# INLINE initializeRL #-}+ initializeRL n (Parser stepR initialR extractR) = do+ resR <- initialR+ return $ case resR of+ IPartial sr -> SPartial n $ ConcatParseR stepR sr extractR+ IDone br -> SDone n br+ IError err -> SError err++ step (ConcatParseL st) a = do+ r <- stepL st a+ case r of+ SPartial n s -> return $ SPartial n (ConcatParseL s)+ SContinue n s -> return $ SContinue n (ConcatParseL s)+ SDone n b -> initializeRL n (func b)+ SError err -> return $ SError err++ step (ConcatParseR stepR st extractR) a = do+ r <- stepR st a+ return $ case r of+ SPartial n s -> SPartial n $ ConcatParseR stepR s extractR+ SContinue n s -> SContinue n $ ConcatParseR stepR s extractR+ SDone n b -> SDone n b+ SError err -> SError err++ {-# INLINE extractP #-}+ extractP n (Parser stepR initialR extractR) = do+ res <- initialR+ case res of+ IPartial s ->+ fmap+ (first (\s1 -> ConcatParseR stepR s1 extractR))+ (extractR s)+ IDone b -> return (FDone n b)+ IError err -> return $ FError err++ extract (ConcatParseR stepR s extractR) =+ fmap (first (\s1 -> ConcatParseR stepR s1 extractR)) (extractR s)+ extract (ConcatParseL sL) = do+ rL <- extractL sL+ case rL of+ FError err -> return $ FError err+ FDone n b -> extractP n $ func b+ FContinue n s -> return $ FContinue n (ConcatParseL s)++-- Note: The monad instance has quadratic performance complexity. It works fine+-- for small number of compositions but for a scalable implementation we need a+-- CPS version.++-- | READ THE CAVEATS in 'concatMap' before using this instance.+--+-- >>> (>>=) = flip Parser.concatMap+--+instance Monad m => Monad (Parser a m) where+ {-# INLINE return #-}+ return = pure++ {-# INLINE (>>=) #-}+ (>>=) = flip concatMap++ {-# INLINE (>>) #-}+ (>>) = (*>)++-- | >>> fail = Parser.die+instance Monad m => Fail.MonadFail (Parser a m) where+ {-# INLINE fail #-}+ fail = die++{-+-- | See documentation of 'Streamly.Internal.Data.Parser.ParserK.Type.Parser'.+--+instance Monad m => MonadPlus (Parser a m) where+ {-# INLINE mzero #-}+ mzero = die "mzero"++ {-# INLINE mplus #-}+ mplus = alt+-}++-- | >>> liftIO = Parser.fromEffect . liftIO+instance (MonadIO m) => MonadIO (Parser a m) where+ {-# INLINE liftIO #-}+ liftIO = fromEffect . liftIO++------------------------------------------------------------------------------+-- Mapping on input+------------------------------------------------------------------------------++-- | @lmap f parser@ maps the function @f@ on the input of the parser.+--+-- >>> Stream.parse (Parser.lmap (\x -> x * x) (Parser.fromFold Fold.sum)) (Stream.enumerateFromTo 1 100)+-- Right 338350+--+-- > lmap = Parser.lmapM return+--+{-# INLINE lmap #-}+lmap :: (a -> b) -> Parser b m r -> Parser a m r+lmap f (Parser step begin done) = Parser step1 begin done++ where++ step1 x a = step x (f a)++-- | @lmapM f parser@ maps the monadic function @f@ on the input of the parser.+--+{-# INLINE lmapM #-}+lmapM :: Monad m => (a -> m b) -> Parser b m r -> Parser a m r+lmapM f (Parser step begin done) = Parser step1 begin done++ where++ step1 x a = f a >>= step x++-- | Include only those elements that pass a predicate.+--+-- >>> Stream.parse (Parser.filter (> 5) (Parser.fromFold Fold.sum)) $ Stream.fromList [1..10]+-- Right 40+--+{-# INLINE filter #-}+filter :: Monad m => (a -> Bool) -> Parser a m b -> Parser a m b+filter f (Parser step initial extract) = Parser step1 initial extract++ where++ step1 x a = if f a then step x a else return $ SPartial 1 x++-- XXX move this to ParserD.Transformer++-- | Modify the environment of the underlying ReaderT monad.+{-# INLINE localReaderT #-}+localReaderT ::+ (r -> r) -> Parser a (ReaderT r m) b -> Parser a (ReaderT r m) b+localReaderT f (Parser step initial extract) =+ Parser+ ((local f .) . step)+ (local f initial)+ (local f . extract)
+ src/Streamly/Internal/Data/ParserDrivers.h view
@@ -0,0 +1,1100 @@+#ifndef PARSER_WITH_POS+#define PARSE_BREAK parseBreak+#define PARSE_BREAK_STREAMK parseBreakStreamK+#define PARSE_BREAK_CHUNKS parseBreakChunks+#define PARSE_BREAK_CHUNKS_GENERIC parseBreakChunksGeneric+#define PARSE_MANY parseMany+#define PARSE_ITERATE parseIterate+#define OPTIONAL(x)+#define PARSE_ERROR(x) ParseError+#define PARSE_ERROR_TYPE ParseError+#else+#undef PARSE_BREAK+#define PARSE_BREAK parseBreakPos+#undef PARSE_BREAK_STREAMK+#define PARSE_BREAK_STREAMK parseBreakStreamKPos+#undef PARSE_BREAK_CHUNKS+#define PARSE_BREAK_CHUNKS parseBreakChunksPos+#undef PARSE_BREAK_CHUNKS_GENERIC+#define PARSE_BREAK_CHUNKS_GENERIC parseBreakChunksGenericPos++#define ParseChunksState ParseChunksStatePos+#define ParseChunksInit ParseChunksInitPos+#define ParseChunksInitBuf ParseChunksInitBufPos+#define ParseChunksInitLeftOver ParseChunksInitLeftOverPos+#define ParseChunksStream ParseChunksStreamPos+#define ParseChunksStop ParseChunksStopPos+#define ParseChunksBuf ParseChunksBufPos+#define ParseChunksExtract ParseChunksExtractPos+#define ParseChunksYield ParseChunksYieldPos++#undef PARSE_MANY+#define PARSE_MANY parseManyPos++#define ConcatParseState ConcatParseStatePos+#define ConcatParseInit ConcatParseInitPos+#define ConcatParseInitBuf ConcatParseInitBufPos+#define ConcatParseInitLeftOver ConcatParseInitLeftOverPos+#define ConcatParseStop ConcatParseStopPos+#define ConcatParseStream ConcatParseStreamPos+#define ConcatParseBuf ConcatParseBufPos+#define ConcatParseExtract ConcatParseExtractPos+#define ConcatParseYield ConcatParseYieldPos++#undef PARSE_ITERATE+#define PARSE_ITERATE parseIteratePos+#undef OPTIONAL+#define OPTIONAL(x) (x)+#undef PARSE_ERROR+#define PARSE_ERROR(x) ParseErrorPos (x)+#undef PARSE_ERROR_TYPE+#define PARSE_ERROR_TYPE ParseErrorPos+#endif++{- HLINT ignore -}++{-# ANN type ParseChunksState Fuse #-}+data ParseChunksState x inpBuf st pst =+ ParseChunksInit OPTIONAL(Int) inpBuf st+ | ParseChunksInitBuf OPTIONAL(Int) inpBuf+ | ParseChunksInitLeftOver OPTIONAL(Int) inpBuf+ | ParseChunksStream OPTIONAL(Int) st inpBuf !pst+ | ParseChunksStop OPTIONAL(Int) inpBuf !pst+ | ParseChunksBuf OPTIONAL(Int) inpBuf st inpBuf !pst+ | ParseChunksExtract OPTIONAL(Int) inpBuf inpBuf !pst+ | ParseChunksYield x (ParseChunksState x inpBuf st pst)++-- XXX return the remaining stream as part of the error.+{-# INLINE_NORMAL PARSE_MANY #-}+PARSE_MANY+ :: Monad m+ => PRD.Parser a m b+ -> Stream m a+ -> Stream m (Either PARSE_ERROR_TYPE b)+PARSE_MANY (PRD.Parser pstep initial extract) (Stream step state) =+ Stream stepOuter (ParseChunksInit OPTIONAL(0) [] state)++ where++ {-# INLINE splitAt #-}+ splitAt = Stream.splitAt "Data.StreamK.parseMany"++ {-# INLINE_LATE stepOuter #-}+ -- Buffer is empty, get the first element from the stream, initialize the+ -- fold and then go to stream processing loop.+ stepOuter gst (ParseChunksInit OPTIONAL(i) [] st) = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> do+ res <- initial+ case res of+ PRD.IPartial ps ->+ return $ Skip $ ParseChunksBuf OPTIONAL(i) [x] s [] ps+ PRD.IDone pb ->+ let next = ParseChunksInit OPTIONAL(i) [x] s+ in return $ Skip $ ParseChunksYield (Right pb) next+ PRD.IError err ->+ return+ $ Skip+ $ ParseChunksYield+ (Left (PARSE_ERROR(i) err))+ (ParseChunksInitLeftOver OPTIONAL(i) [])+ Skip s -> return $ Skip $ ParseChunksInit OPTIONAL(i) [] s+ Stop -> return Stop++ -- Buffer is not empty, go to buffered processing loop+ stepOuter _ (ParseChunksInit OPTIONAL(i) src st) = do+ res <- initial+ case res of+ PRD.IPartial ps ->+ return $ Skip $ ParseChunksBuf OPTIONAL(i) src st [] ps+ PRD.IDone pb ->+ let next = ParseChunksInit OPTIONAL(i) src st+ in return $ Skip $ ParseChunksYield (Right pb) next+ PRD.IError err ->+ return+ $ Skip+ $ ParseChunksYield+ (Left (PARSE_ERROR(i) err))+ (ParseChunksInitLeftOver OPTIONAL(i) [])++ -- This is simplified ParseChunksInit+ stepOuter _ (ParseChunksInitBuf OPTIONAL(i) src) = do+ res <- initial+ case res of+ PRD.IPartial ps ->+ return $ Skip $ ParseChunksExtract OPTIONAL(i) src [] ps+ PRD.IDone pb ->+ let next = ParseChunksInitBuf OPTIONAL(i) src+ in return $ Skip $ ParseChunksYield (Right pb) next+ PRD.IError err ->+ return+ $ Skip+ $ ParseChunksYield+ (Left (PARSE_ERROR(i) err))+ (ParseChunksInitLeftOver OPTIONAL(i) [])++ -- XXX we just discard any leftover input at the end+ stepOuter _ (ParseChunksInitLeftOver OPTIONAL(_) _) = return Stop++ -- Buffer is empty, process elements from the stream+ stepOuter gst (ParseChunksStream OPTIONAL(i) st buf pst) = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> do+ pRes <- pstep pst x+ case pRes of+ PR.SPartial 1 pst1 ->+ return $ Skip $ ParseChunksStream OPTIONAL(i + 1) s [] pst1+ PR.SPartial m pst1 -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let src0 = Prelude.take n (x:buf)+ src = Prelude.reverse src0+ return $ Skip $ ParseChunksBuf OPTIONAL(i + m) src s [] pst1+ PR.SContinue 1 pst1 ->+ return $ Skip $ ParseChunksStream OPTIONAL(i + 1) s (x:buf) pst1+ PR.SContinue m pst1 -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let (src0, buf1) = splitAt n (x:buf)+ src = Prelude.reverse src0+ return $ Skip $ ParseChunksBuf OPTIONAL(i + m) src s buf1 pst1+ PR.SDone 1 b -> do+ return $ Skip $+ ParseChunksYield+ (Right b) (ParseChunksInit OPTIONAL(i + 1) [] s)+ PR.SDone m b -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let src = Prelude.reverse (Prelude.take n (x:buf))+ return $ Skip $+ ParseChunksYield+ (Right b) (ParseChunksInit OPTIONAL(i + m) src s)+ PR.SError err ->+ return+ $ Skip+ $ ParseChunksYield+ (Left (PARSE_ERROR(i + 1) err))+ (ParseChunksInitLeftOver OPTIONAL(i + 1) [])+ Skip s -> return $ Skip $ ParseChunksStream OPTIONAL(i) s buf pst+ Stop -> return $ Skip $ ParseChunksStop OPTIONAL(i) buf pst++ -- go back to stream processing mode+ stepOuter _ (ParseChunksBuf OPTIONAL(i) [] s buf pst) =+ return $ Skip $ ParseChunksStream OPTIONAL(i) s buf pst++ -- buffered processing loop+ stepOuter _ (ParseChunksBuf OPTIONAL(i) (x:xs) s buf pst) = do+ pRes <- pstep pst x+ case pRes of+ PR.SPartial 1 pst1 ->+ return $ Skip $ ParseChunksBuf OPTIONAL(i + 1) xs s [] pst1+ PR.SPartial m pst1 -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let src0 = Prelude.take n (x:buf)+ src = Prelude.reverse src0 ++ xs+ return $ Skip $ ParseChunksBuf OPTIONAL(i + m) src s [] pst1+ PR.SContinue 1 pst1 ->+ return $ Skip $ ParseChunksBuf OPTIONAL(i + 1) xs s (x:buf) pst1+ PR.SContinue m pst1 -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let (src0, buf1) = splitAt n (x:buf)+ src = Prelude.reverse src0 ++ xs+ return $ Skip $ ParseChunksBuf OPTIONAL(i + m) src s buf1 pst1+ PR.SDone 1 b ->+ return+ $ Skip+ $ ParseChunksYield (Right b) (ParseChunksInit OPTIONAL(i + 1) xs s)+ PR.SDone m b -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let src = Prelude.reverse (Prelude.take n (x:buf)) ++ xs+ return $ Skip+ $ ParseChunksYield+ (Right b) (ParseChunksInit OPTIONAL(i + m) src s)+ PR.SError err ->+ return+ $ Skip+ $ ParseChunksYield+ (Left (PARSE_ERROR(i + 1) err))+ (ParseChunksInitLeftOver OPTIONAL(i + 1) [])++ -- This is simplified ParseChunksBuf+ stepOuter _ (ParseChunksExtract OPTIONAL(i) [] buf pst) =+ return $ Skip $ ParseChunksStop OPTIONAL(i) buf pst++ stepOuter _ (ParseChunksExtract OPTIONAL(i) (x:xs) buf pst) = do+ pRes <- pstep pst x+ case pRes of+ PR.SPartial 1 pst1 ->+ return $ Skip $ ParseChunksExtract OPTIONAL(i + 1) xs [] pst1+ PR.SPartial m pst1 -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let src0 = Prelude.take n (x:buf)+ src = Prelude.reverse src0 ++ xs+ return $ Skip $ ParseChunksExtract OPTIONAL(i + m) src [] pst1+ PR.SContinue 1 pst1 ->+ return $ Skip $ ParseChunksExtract OPTIONAL(i + 1) xs (x:buf) pst1+ PR.SContinue m pst1 -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let (src0, buf1) = splitAt n (x:buf)+ src = Prelude.reverse src0 ++ xs+ return $ Skip $ ParseChunksExtract OPTIONAL(i + m) src buf1 pst1+ PR.SDone 1 b ->+ return+ $ Skip+ $ ParseChunksYield (Right b) (ParseChunksInitBuf OPTIONAL(i + 1) xs)+ PR.SDone m b -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let src = Prelude.reverse (Prelude.take n (x:buf)) ++ xs+ return+ $ Skip+ $ ParseChunksYield+ (Right b) (ParseChunksInitBuf OPTIONAL(i + m) src)+ PR.SError err ->+ return+ $ Skip+ $ ParseChunksYield+ (Left (PARSE_ERROR(i + 1) err))+ (ParseChunksInitLeftOver OPTIONAL(i + 1) [])++ -- This is simplified ParseChunksExtract+ stepOuter _ (ParseChunksStop OPTIONAL(i) buf pst) = do+ pRes <- extract pst+ case pRes of+ PR.FContinue 0 pst1 ->+ return $ Skip $ ParseChunksStop OPTIONAL(i) buf pst1+ PR.FContinue m pst1 -> do+ let n = (- m)+ assert (n <= length buf) (return ())+ let (src0, buf1) = splitAt n buf+ src = Prelude.reverse src0+ return $ Skip $ ParseChunksExtract OPTIONAL(i + m) src buf1 pst1+ PR.FDone 0 b -> do+ return $ Skip $+ ParseChunksYield (Right b) (ParseChunksInitLeftOver OPTIONAL(i) [])+ PR.FDone m b -> do+ let n = (- m)+ assert (n <= length buf) (return ())+ let src = Prelude.reverse (Prelude.take n buf)+ return $ Skip $+ ParseChunksYield (Right b) (ParseChunksInitBuf OPTIONAL(i + m) src)+ PR.FError err ->+ return+ $ Skip+ $ ParseChunksYield+ (Left (PARSE_ERROR(i) err))+ (ParseChunksInitLeftOver OPTIONAL(i) [])++ stepOuter _ (ParseChunksYield a next) = return $ Yield a next++{-# ANN type ConcatParseState Fuse #-}+data ConcatParseState c b inpBuf st p m a =+ ConcatParseInit OPTIONAL(Int) inpBuf st p+ | ConcatParseInitBuf OPTIONAL(Int) inpBuf p+ | ConcatParseInitLeftOver OPTIONAL(Int) inpBuf+ | forall s. ConcatParseStop OPTIONAL(Int)+ inpBuf (s -> a -> m (PRD.Step s b)) s (s -> m (PRD.Final s b))+ | forall s. ConcatParseStream OPTIONAL(Int)+ st inpBuf (s -> a -> m (PRD.Step s b)) s (s -> m (PRD.Final s b))+ | forall s. ConcatParseBuf OPTIONAL(Int)+ inpBuf st inpBuf (s -> a -> m (PRD.Step s b)) s (s -> m (PRD.Final s b))+ | forall s. ConcatParseExtract OPTIONAL(Int)+ inpBuf inpBuf (s -> a -> m (PRD.Step s b)) s (s -> m (PRD.Final s b))+ | ConcatParseYield c (ConcatParseState c b inpBuf st p m a)++{-# INLINE_NORMAL PARSE_ITERATE #-}+PARSE_ITERATE+ :: Monad m+ => (b -> PRD.Parser a m b)+ -> b+ -> Stream m a+ -> Stream m (Either PARSE_ERROR_TYPE b)+PARSE_ITERATE func seed (Stream step state) =+ Stream stepOuter (ConcatParseInit OPTIONAL(0) [] state (func seed))++ where++ {-# INLINE splitAt #-}+ splitAt = Stream.splitAt "Data.StreamK.parseIterate"++ {-# INLINE_LATE stepOuter #-}+ -- Buffer is empty, go to stream processing loop+ stepOuter _ (ConcatParseInit OPTIONAL(i) [] st (PRD.Parser pstep initial extract)) = do+ res <- initial+ case res of+ PRD.IPartial ps ->+ return $ Skip $ ConcatParseStream OPTIONAL(i) st [] pstep ps extract+ PRD.IDone pb ->+ let next = ConcatParseInit OPTIONAL(i) [] st (func pb)+ in return $ Skip $ ConcatParseYield (Right pb) next+ PRD.IError err ->+ return+ $ Skip+ $ ConcatParseYield+ (Left (PARSE_ERROR(i) err))+ (ConcatParseInitLeftOver OPTIONAL(i) [])++ -- Buffer is not empty, go to buffered processing loop+ stepOuter _ (ConcatParseInit OPTIONAL(i) src st+ (PRD.Parser pstep initial extract)) = do+ res <- initial+ case res of+ PRD.IPartial ps ->+ return $ Skip $ ConcatParseBuf OPTIONAL(i) src st [] pstep ps extract+ PRD.IDone pb ->+ let next = ConcatParseInit OPTIONAL(i) src st (func pb)+ in return $ Skip $ ConcatParseYield (Right pb) next+ PRD.IError err ->+ return+ $ Skip+ $ ConcatParseYield+ (Left (PARSE_ERROR(i) err))+ (ConcatParseInitLeftOver OPTIONAL(i) [])++ -- This is simplified ConcatParseInit+ stepOuter _ (ConcatParseInitBuf OPTIONAL(i) src+ (PRD.Parser pstep initial extract)) = do+ res <- initial+ case res of+ PRD.IPartial ps ->+ return $ Skip $ ConcatParseExtract OPTIONAL(i) src [] pstep ps extract+ PRD.IDone pb ->+ let next = ConcatParseInitBuf OPTIONAL(i) src (func pb)+ in return $ Skip $ ConcatParseYield (Right pb) next+ PRD.IError err ->+ return+ $ Skip+ $ ConcatParseYield+ (Left (PARSE_ERROR(i) err))+ (ConcatParseInitLeftOver OPTIONAL(i) [])++ -- XXX we just discard any leftover input at the end+ stepOuter _ (ConcatParseInitLeftOver OPTIONAL(_) _) = return Stop++ -- Buffer is empty process elements from the stream+ stepOuter gst (ConcatParseStream OPTIONAL(i) st buf pstep pst extract) = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> do+ pRes <- pstep pst x+ case pRes of+ PR.SPartial 1 pst1 ->+ return $ Skip+ $ ConcatParseStream OPTIONAL(i + 1) s [] pstep pst1 extract+ PR.SPartial m pst1 -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let src0 = Prelude.take n (x:buf)+ src = Prelude.reverse src0+ return $ Skip+ $ ConcatParseBuf+ OPTIONAL(i + m) src s [] pstep pst1 extract+ -- PR.SContinue 1 pst1 ->+ -- return $ Skip $ ConcatParseStream s (x:buf) pst1+ PR.SContinue m pst1 -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let (src0, buf1) = splitAt n (x:buf)+ src = Prelude.reverse src0+ return $ Skip+ $ ConcatParseBuf+ OPTIONAL(i + m) src s buf1 pstep pst1 extract+ -- XXX Specialize for Stop 0 common case?+ PR.SDone m b -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let src = Prelude.reverse (Prelude.take n (x:buf))+ return $ Skip+ $ ConcatParseYield+ (Right b)+ (ConcatParseInit OPTIONAL(i + m) src s (func b))+ PR.SError err ->+ return+ $ Skip+ $ ConcatParseYield+ (Left (PARSE_ERROR(i + 1) err))+ (ConcatParseInitLeftOver OPTIONAL(i + 1) [])+ Skip s ->+ return $ Skip $ ConcatParseStream OPTIONAL(i) s buf pstep pst extract+ Stop -> return $ Skip $ ConcatParseStop OPTIONAL(i) buf pstep pst extract++ -- go back to stream processing mode+ stepOuter _ (ConcatParseBuf OPTIONAL(i) [] s buf pstep ps extract) =+ return $ Skip $ ConcatParseStream OPTIONAL(i) s buf pstep ps extract++ -- buffered processing loop+ stepOuter _ (ConcatParseBuf OPTIONAL(i) (x:xs) s buf pstep pst extract) = do+ pRes <- pstep pst x+ case pRes of+ PR.SPartial 1 pst1 ->+ return $ Skip+ $ ConcatParseBuf OPTIONAL(i + 1) xs s [] pstep pst1 extract+ PR.SPartial m pst1 -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let src0 = Prelude.take n (x:buf)+ src = Prelude.reverse src0 ++ xs+ return $ Skip+ $ ConcatParseBuf OPTIONAL(i + m) src s [] pstep pst1 extract+ -- PR.SContinue 1 pst1 -> return $ Skip $ ConcatParseBuf xs s (x:buf) pst1+ PR.SContinue m pst1 -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let (src0, buf1) = splitAt n (x:buf)+ src = Prelude.reverse src0 ++ xs+ return $ Skip+ $ ConcatParseBuf OPTIONAL(i + m) src s buf1 pstep pst1 extract+ -- XXX Specialize for Stop 0 common case?+ PR.SDone m b -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let src = Prelude.reverse (Prelude.take n (x:buf)) ++ xs+ return $ Skip+ $ ConcatParseYield+ (Right b) (ConcatParseInit OPTIONAL(i + m) src s (func b))+ PR.SError err ->+ return+ $ Skip+ $ ConcatParseYield+ (Left (PARSE_ERROR(i + 1) err))+ (ConcatParseInitLeftOver OPTIONAL(i + 1) [])++ -- This is simplified ConcatParseBuf+ stepOuter _ (ConcatParseExtract OPTIONAL(i) [] buf pstep pst extract) =+ return $ Skip $ ConcatParseStop OPTIONAL(i) buf pstep pst extract++ stepOuter _ (ConcatParseExtract OPTIONAL(i) (x:xs) buf pstep pst extract) = do+ pRes <- pstep pst x+ case pRes of+ PR.SPartial 1 pst1 ->+ return $ Skip+ $ ConcatParseExtract OPTIONAL(i + 1) xs [] pstep pst1 extract+ PR.SPartial m pst1 -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let src0 = Prelude.take n (x:buf)+ src = Prelude.reverse src0 ++ xs+ return $ Skip+ $ ConcatParseExtract OPTIONAL(i + m) src [] pstep pst1 extract+ PR.SContinue 1 pst1 ->+ return $ Skip+ $ ConcatParseExtract OPTIONAL(i + 1) xs (x:buf) pstep pst1 extract+ PR.SContinue m pst1 -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let (src0, buf1) = splitAt n (x:buf)+ src = Prelude.reverse src0 ++ xs+ return $ Skip+ $ ConcatParseExtract OPTIONAL(i + m) src buf1 pstep pst1 extract+ PR.SDone 1 b ->+ return $ Skip+ $ ConcatParseYield+ (Right b) (ConcatParseInitBuf OPTIONAL(i + 1) xs (func b))+ PR.SDone m b -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let src = Prelude.reverse (Prelude.take n (x:buf)) ++ xs+ return $ Skip+ $ ConcatParseYield+ (Right b) (ConcatParseInitBuf OPTIONAL(i + m) src (func b))+ PR.SError err ->+ return+ $ Skip+ $ ConcatParseYield+ (Left (PARSE_ERROR(i + 1) err))+ (ConcatParseInitLeftOver OPTIONAL(i + 1) [])++ -- This is simplified ConcatParseExtract+ stepOuter _ (ConcatParseStop OPTIONAL(i) buf pstep pst extract) = do+ pRes <- extract pst+ case pRes of+ PR.FContinue 0 pst1 ->+ return $ Skip $ ConcatParseStop OPTIONAL(i) buf pstep pst1 extract+ PR.FContinue m pst1 -> do+ let n = (- m)+ assert (n <= length buf) (return ())+ let (src0, buf1) = splitAt n buf+ src = Prelude.reverse src0+ return $ Skip+ $ ConcatParseExtract OPTIONAL(i + m) src buf1 pstep pst1 extract+ PR.FDone 0 b -> do+ return $ Skip $+ ConcatParseYield (Right b) (ConcatParseInitLeftOver OPTIONAL(i) [])+ PR.FDone m b -> do+ let n = (- m)+ assert (n <= length buf) (return ())+ let src = Prelude.reverse (Prelude.take n buf)+ return $ Skip $+ ConcatParseYield+ (Right b) (ConcatParseInitBuf OPTIONAL(i + m) src (func b))+ PR.FError err ->+ return+ $ Skip+ $ ConcatParseYield+ (Left (PARSE_ERROR(i) err))+ (ConcatParseInitLeftOver OPTIONAL(i) [])++ stepOuter _ (ConcatParseYield a next) = return $ Yield a next++{-# INLINE PARSE_BREAK #-}+PARSE_BREAK :: Monad m =>+ PR.Parser a m b -> Stream m a -> m (Either PARSE_ERROR_TYPE b, Stream m a)+PARSE_BREAK (PRD.Parser pstep initial extract) stream@(Stream step state) = do+ res <- initial+ case res of+ PRD.IPartial s ->+ go SPEC state (List []) s OPTIONAL(0)+ -- Using go0 does improve alt and manyTill benchmarks dramatically+ -- but also degrades the split/monad benchmarks equally. Needs more+ -- investigation.+ -- go0 SPEC state s COUNT(0)+ PRD.IDone b -> return (Right b, stream)+ PRD.IError err -> return (Left (PARSE_ERROR(0) err), stream)++ where++ {-# INLINE splitAt #-}+ splitAt = Stream.splitAt "Data.Stream.parseBreak"++ -- "buf" contains last few items in the stream that we may have to+ -- backtrack to.+ --+ -- XXX currently we are using a dumb list based approach for backtracking+ -- buffer. This can be replaced by a sliding/ring buffer using Data.Array.+ -- That will allow us more efficient random back and forth movement.+ go !_ st buf !pst OPTIONAL(i) = do+ r <- step defState st+ case r of+ Yield x s -> do+ pRes <- pstep pst x+ case pRes of+ PR.SPartial 1 pst1 -> go SPEC s (List []) pst1 OPTIONAL(i+1)+ -- go0 SPEC s pst1 (i + 1)+ PR.SPartial 0 pst1 -> go1 SPEC s x pst1 OPTIONAL(i)+ PR.SPartial m pst1 -> do+ let n = 1 - m+ assert (n <= length (x:getList buf)) (return ())+ let src0 = Prelude.take n (x:getList buf)+ src = Prelude.reverse src0+ gobuf SPEC s (List []) (List src) pst1 OPTIONAL(i+m)+ PR.SContinue 1 pst1 ->+ go SPEC s (List (x:getList buf)) pst1 OPTIONAL(i+1)+ PR.SContinue 0 pst1 -> gobuf SPEC s buf (List [x]) pst1 OPTIONAL(i)+ PR.SContinue m pst1 -> do+ let n = 1 - m+ assert (n <= length (x:getList buf)) (return ())+ let (src0, buf1) = splitAt n (x:getList buf)+ src = Prelude.reverse src0+ gobuf SPEC s (List buf1) (List src) pst1 OPTIONAL(i+m)+ PR.SDone 1 b -> return (Right b, Stream step s)+ PR.SDone m b -> do+ let n = 1 - m+ assert (n <= length (x:getList buf)) (return ())+ let src0 = Prelude.take n (x:getList buf)+ src = Prelude.reverse src0+ -- XXX This would make it quadratic. We should probably+ -- use StreamK if we have to append many times.+ return+ ( Right b,+ Nesting.append (fromList src) (Stream step s))+ PR.SError err -> do+ let src = Prelude.reverse $ x:getList buf+ return+ ( Left (PARSE_ERROR(i+1) err)+ , Nesting.append (fromList src) (Stream step s)+ )++ Skip s -> go SPEC s buf pst OPTIONAL(i)+ Stop -> goStop SPEC buf pst OPTIONAL(i)++ {-+ go0 !_ st !pst i = do+ r <- step defState st+ case r of+ Yield x s -> do+ pRes <- pstep pst x+ case pRes of+ PR.SPartial 1 pst1 -> go0 SPEC s pst1 (i + 1)+ PR.SPartial 0 pst1 -> go1 SPEC s x pst1 i+ PR.SPartial _ _ -> error "Unreachable"+ PR.SContinue 1 pst1 -> go SPEC s (List [x]) pst1 (i + 1)+ PR.SContinue 0 pst1 -> go1 SPEC s x pst1 i+ PR.SContinue _ _ -> error "Unreachable"+ PR.SDone 1 b -> return (Right b, Stream step s)+ PR.SDone 0 b ->+ return ( Right b, StreamD.cons x (Stream step s))+ PR.SDone _ _ -> error "Unreachable"+ PR.SError err -> do+ return+ ( Left (PARSE_ERROR(i + 1) err)+ , StreamD.cons x (Stream step s)+ )++ Skip s -> go0 SPEC s pst i+ Stop -> goStop SPEC (List []) pst i+ -}++ go1 !_ s x !pst OPTIONAL(i) = do+ pRes <- pstep pst x+ case pRes of+ PR.SPartial 1 pst1 ->+ -- go0 SPEC s pst1 OPTIONAL(i + 1)+ go SPEC s (List []) pst1 OPTIONAL(i + 1)+ PR.SPartial 0 pst1 -> do+ go1 SPEC s x pst1 OPTIONAL(i)+ PR.SPartial m _ ->+ error $ "parseBreak: parser bug, go1: Partial m = " ++ show m+ PR.SContinue 1 pst1 ->+ go SPEC s (List [x]) pst1 OPTIONAL(i + 1)+ PR.SContinue 0 pst1 ->+ go1 SPEC s x pst1 OPTIONAL(i)+ PR.SContinue m _ -> do+ error $ "parseBreak: parser bug, go1: Continue m = " ++ show m+ PR.SDone 1 b -> do+ return (Right b, Stream step s)+ PR.SDone 0 b -> do+ return (Right b, StreamD.cons x (Stream step s))+ PR.SDone m _ -> do+ error $ "parseBreak: parser bug, go1: SDone m = " ++ show m+ PR.SError err ->+ return+ ( Left (PARSE_ERROR(i + 1) err)+ , Nesting.append (fromPure x) (Stream step s)+ )++ -- gobuf !_ s (List []) (List []) !pst i = go0 SPEC s pst i+ gobuf !_ s buf (List []) !pst OPTIONAL(i) = go SPEC s buf pst OPTIONAL(i)+ gobuf !_ s buf (List (x:xs)) !pst OPTIONAL(i) = do+ pRes <- pstep pst x+ case pRes of+ PR.SPartial 1 pst1 ->+ gobuf SPEC s (List []) (List xs) pst1 OPTIONAL(i + 1)+ PR.SPartial m pst1 -> do+ let n = 1 - m+ assert (n <= length (x:getList buf)) (return ())+ let src0 = Prelude.take n (x:getList buf)+ src = Prelude.reverse src0 ++ xs+ gobuf SPEC s (List []) (List src) pst1 OPTIONAL(i + m)+ PR.SContinue 1 pst1 ->+ gobuf SPEC s (List (x:getList buf)) (List xs) pst1 OPTIONAL(i + 1)+ PR.SContinue 0 pst1 ->+ gobuf SPEC s buf (List (x:xs)) pst1 OPTIONAL(i)+ PR.SContinue m pst1 -> do+ let n = 1 - m+ assert (n <= length (x:getList buf)) (return ())+ let (src0, buf1) = splitAt n (x:getList buf)+ src = Prelude.reverse src0 ++ xs+ gobuf SPEC s (List buf1) (List src) pst1 OPTIONAL(i + m)+ PR.SDone m b -> do+ let n = 1 - m+ assert (n <= length (x:getList buf)) (return ())+ let src0 = Prelude.take n (x:getList buf)+ src = Prelude.reverse src0 ++ xs+ return (Right b, Nesting.append (fromList src) (Stream step s))+ PR.SError err -> do+ let src = Prelude.reverse (getList buf) ++ x:xs+ return+ ( Left (PARSE_ERROR(i + 1) err)+ , Nesting.append (fromList src) (Stream step s)+ )++ -- This is simplified gobuf+ goExtract !_ buf (List []) !pst OPTIONAL(i) = goStop SPEC buf pst OPTIONAL(i)+ goExtract !_ buf (List (x:xs)) !pst OPTIONAL(i) = do+ pRes <- pstep pst x+ case pRes of+ PR.SPartial 1 pst1 ->+ goExtract SPEC (List []) (List xs) pst1 OPTIONAL(i + 1)+ PR.SPartial m pst1 -> do+ let n = 1 - m+ assert (n <= length (x:getList buf)) (return ())+ let src0 = Prelude.take n (x:getList buf)+ src = Prelude.reverse src0 ++ xs+ goExtract SPEC (List []) (List src) pst1 OPTIONAL(i + m)+ PR.SContinue 1 pst1 ->+ goExtract SPEC (List (x:getList buf)) (List xs) pst1 OPTIONAL(i + 1)+ PR.SContinue 0 pst1 ->+ goExtract SPEC buf (List (x:xs)) pst1 OPTIONAL(i)+ PR.SContinue m pst1 -> do+ let n = 1 - m+ assert (n <= length (x:getList buf)) (return ())+ let (src0, buf1) = splitAt n (x:getList buf)+ src = Prelude.reverse src0 ++ xs+ goExtract SPEC (List buf1) (List src) pst1 OPTIONAL(i + m)+ PR.SDone m b -> do+ let n = 1 - m+ assert (n <= length (x:getList buf)) (return ())+ let src0 = Prelude.take n (x:getList buf)+ src = Prelude.reverse src0 ++ xs+ return (Right b, fromList src)+ PR.SError err -> do+ let src = Prelude.reverse (getList buf) ++ x:xs+ return (Left (PARSE_ERROR(i + 1) err), fromList src)++ -- This is simplified goExtract+ {-# INLINE goStop #-}+ goStop _ buf pst OPTIONAL(i) = do+ pRes <- extract pst+ case pRes of+ PR.FContinue 0 pst1 -> goStop SPEC buf pst1 OPTIONAL(i)+ PR.FContinue m pst1 -> do+ let n = (- m)+ assert (n <= length (getList buf)) (return ())+ let (src0, buf1) = splitAt n (getList buf)+ src = Prelude.reverse src0+ goExtract SPEC (List buf1) (List src) pst1 OPTIONAL(i + m)+ PR.FDone 0 b -> return (Right b, StreamD.nil)+ PR.FDone m b -> do+ let n = (- m)+ assert (n <= length (getList buf)) (return ())+ let src0 = Prelude.take n (getList buf)+ src = Prelude.reverse src0+ return (Right b, fromList src)+ PR.FError err -> do+ let src = Prelude.reverse $ getList buf+ return (Left (PARSE_ERROR(i) err), fromList src)++{-# INLINE_NORMAL PARSE_BREAK_STREAMK #-}+PARSE_BREAK_STREAMK+ :: forall m a b. Monad m+ => ParserK.ParserK a m b+ -> StreamK m a+ -> m (Either PARSE_ERROR_TYPE b, StreamK m a)+PARSE_BREAK_STREAMK parser input = do+ let parserk = ParserK.runParser parser ParserK.parserDone 0 0+ in go OPTIONAL(0) [] parserk input++ where++ {-# INLINE backtrck #-}+ -- backtrck :: Int -> [a] -> StreamK m a -> (StreamK m a, [a])+ backtrck n xs stream =+ let (pre, post) = Stream.splitAt "Data.StreamK.parseBreak" n xs+ in (StreamK.append (StreamK.fromList (Prelude.reverse pre)) stream, post)++ {-# INLINE goStop #-}+ {-+ goStop+ :: OPTIONAL(Int ->)+ [a]+ -> (ParserK.Input a -> m (ParserK.Step a m b))+ -> m (Either PARSE_ERROR_TYPE b, StreamK m a)+ -}+ goStop OPTIONAL(pos) backBuf parserk = do+ pRes <- parserk ParserK.None+ case pRes of+ -- If we stop in an alternative, it will try calling the next+ -- parser, the next parser may call initial returning Partial and+ -- then immediately we have to call extract on it.+ ParserK.Partial 0 cont1 ->+ go OPTIONAL(pos) [] cont1 StreamK.nil+ ParserK.Partial n cont1 -> do+ let n1 = negate n+ assertM(n1 >= 0 && n1 <= length backBuf)+ let (s1, backBuf1) = backtrck n1 backBuf StreamK.nil+ in go OPTIONAL(pos + n) backBuf1 cont1 s1+ ParserK.Continue 0 cont1 ->+ go OPTIONAL(pos) backBuf cont1 StreamK.nil+ ParserK.Continue n cont1 -> do+ let n1 = negate n+ assertM(n1 >= 0 && n1 <= length backBuf)+ let (s1, backBuf1) = backtrck n1 backBuf StreamK.nil+ in go OPTIONAL(pos + n) backBuf1 cont1 s1+ ParserK.Done 0 b ->+ return (Right b, StreamK.nil)+ ParserK.Done n b -> do+ let n1 = negate n+ assertM(n1 >= 0 && n1 <= length backBuf)+ let (s1, _) = backtrck n1 backBuf StreamK.nil+ in return (Right b, s1)+ ParserK.Error _n err ->+ let strm = StreamK.fromList (Prelude.reverse backBuf)+ in return (Left (PARSE_ERROR(pos + _n) err), strm)++ {-+ yieldk+ :: OPTIONAL(Int ->)+ [a]+ -> (ParserK.Input a -> m (ParserK.Step a m b))+ -> a+ -> StreamK m a+ -> m (Either PPARSE_ERROR_TYPE b, StreamK m a)+ -}+ yieldk OPTIONAL(pos) backBuf parserk element stream = do+ pRes <- parserk (ParserK.Chunk element)+ -- NOTE: factoring out "StreamK.cons element stream" in a let statement here+ -- cause big alloc regression.+ case pRes of+ ParserK.Partial 1 cont1 -> go OPTIONAL(pos + 1) [] cont1 stream+ ParserK.Partial 0 cont1 -> go OPTIONAL(pos) [] cont1 (StreamK.cons element stream)+ ParserK.Partial n cont1 -> do -- n < 0 case+ let n1 = negate n+ bufLen = length backBuf+ s = StreamK.cons element stream+ assertM(n1 >= 0 && n1 <= bufLen)+ let (s1, _) = backtrck n1 backBuf s+ go OPTIONAL(pos + n) [] cont1 s1+ ParserK.Continue 1 cont1 -> go OPTIONAL(pos + 1) (element:backBuf) cont1 stream+ ParserK.Continue 0 cont1 ->+ go OPTIONAL(pos) backBuf cont1 (StreamK.cons element stream)+ ParserK.Continue n cont1 -> do+ let n1 = negate n+ bufLen = length backBuf+ s = StreamK.cons element stream+ assertM(n1 >= 0 && n1 <= bufLen)+ let (s1, backBuf1) = backtrck n1 backBuf s+ go OPTIONAL(pos + n) backBuf1 cont1 s1+ ParserK.Done 1 b -> pure (Right b, stream)+ ParserK.Done 0 b -> pure (Right b, StreamK.cons element stream)+ ParserK.Done n b -> do+ let n1 = negate n+ bufLen = length backBuf+ s = StreamK.cons element stream+ assertM(n1 >= 0 && n1 <= bufLen)+ let (s1, _) = backtrck n1 backBuf s+ pure (Right b, s1)+ ParserK.Error _n err ->+ let strm =+ StreamK.append+ (StreamK.fromList (Prelude.reverse backBuf))+ (StreamK.cons element stream)+ -- XXX Need to test if the +1 is correct.+ in return (Left (PARSE_ERROR(pos + _n + 1) err), strm)++ {-+ go+ :: OPTIONAL(Int ->)+ [a]+ -> (ParserK.Input a -> m (ParserK.Step a m b))+ -> StreamK m a+ -> m (Either PARSE_ERROR_TYPE b, StreamK m a)+ -}+ go OPTIONAL(pos) backBuf parserk stream = do+ let stop = goStop OPTIONAL(pos) backBuf parserk+ single a = yieldk OPTIONAL(pos) backBuf parserk a StreamK.nil+ in StreamK.foldStream+ defState (yieldk OPTIONAL(pos) backBuf parserk) single stop stream++{-# INLINE_NORMAL PARSE_BREAK_CHUNKS #-}+PARSE_BREAK_CHUNKS+ :: (Monad m, Unbox a)+ => ParserK (Array a) m b+ -> StreamK m (Array a)+ -> m (Either PARSE_ERROR_TYPE b, StreamK m (Array a))+PARSE_BREAK_CHUNKS parser input = do+ let parserk = ParserK.runParser parser ParserK.parserDone 0 0+ in go OPTIONAL(0) [] parserk input++ where++ {-# INLINE goStop #-}+ goStop OPTIONAL(pos) backBuf parserk = do+ pRes <- parserk ParserK.None+ case pRes of+ -- If we stop in an alternative, it will try calling the next+ -- parser, the next parser may call initial returning Partial and+ -- then immediately we have to call extract on it.+ ParserK.Partial 0 cont1 ->+ go OPTIONAL(pos) [] cont1 StreamK.nil+ ParserK.Partial n cont1 -> do+ let n1 = negate n+ assertM(n1 >= 0 && n1 <= sum (Prelude.map Array.length backBuf))+ let (s1, backBuf1) = backtrack n1 backBuf StreamK.nil+ in go OPTIONAL(pos + n) backBuf1 cont1 s1+ ParserK.Continue 0 cont1 ->+ go OPTIONAL(pos) backBuf cont1 StreamK.nil+ ParserK.Continue n cont1 -> do+ let n1 = negate n+ assertM(n1 >= 0 && n1 <= sum (Prelude.map Array.length backBuf))+ let (s1, backBuf1) = backtrack n1 backBuf StreamK.nil+ in go OPTIONAL(pos + n) backBuf1 cont1 s1+ ParserK.Done 0 b ->+ return (Right b, StreamK.nil)+ ParserK.Done n b -> do+ let n1 = negate n+ assertM(n1 >= 0 && n1 <= sum (Prelude.map Array.length backBuf))+ let (s1, _) = backtrack n1 backBuf StreamK.nil+ in return (Right b, s1)+ ParserK.Error _n err -> do+ let s1 = Prelude.foldl (flip StreamK.cons) StreamK.nil backBuf+ return (Left (PARSE_ERROR(pos + _n) err), s1)++ seekErr n len =+ error $ "parseBreak: Partial: forward seek not implemented n = "+ ++ show n ++ " len = " ++ show len++ yieldk OPTIONAL(pos) backBuf parserk arr stream = do+ pRes <- parserk (ParserK.Chunk arr)+ let len = Array.length arr+ case pRes of+ ParserK.Partial n cont1 ->+ case compare n len of+ EQ -> go OPTIONAL(pos + n) [] cont1 stream+ LT -> do+ if n >= 0+ then yieldk OPTIONAL(pos + n) [] cont1 arr stream+ else do+ let n1 = negate n+ bufLen = sum (Prelude.map Array.length backBuf)+ s = StreamK.cons arr stream+ assertM(n1 >= 0 && n1 <= bufLen)+ let (s1, _) = backtrack n1 backBuf s+ go OPTIONAL(pos + n) [] cont1 s1+ GT -> seekErr n len+ ParserK.Continue n cont1 ->+ case compare n len of+ EQ -> go OPTIONAL(pos + n) (arr:backBuf) cont1 stream+ LT -> do+ if n >= 0+ then yieldk OPTIONAL(pos + n) backBuf cont1 arr stream+ else do+ let n1 = negate n+ bufLen = sum (Prelude.map Array.length backBuf)+ s = StreamK.cons arr stream+ assertM(n1 >= 0 && n1 <= bufLen)+ let (s1, backBuf1) = backtrack n1 backBuf s+ go OPTIONAL(pos + n) backBuf1 cont1 s1+ GT -> seekErr n len+ ParserK.Done n b -> do+ let n1 = len - n+ assertM(n1 <= sum (Prelude.map Array.length (arr:backBuf)))+ let (s1, _) = backtrack n1 (arr:backBuf) stream+ in return (Right b, s1)+ ParserK.Error _n err -> do+ let s1 = Prelude.foldl (flip StreamK.cons) stream (arr:backBuf)+ return (Left (PARSE_ERROR(pos + _n + 1) err), s1)++ go OPTIONAL(pos) backBuf parserk stream = do+ let stop = goStop OPTIONAL(pos) backBuf parserk+ single a = yieldk OPTIONAL(pos) backBuf parserk a StreamK.nil+ in StreamK.foldStream+ defState (yieldk OPTIONAL(pos) backBuf parserk) single stop stream++{-# INLINE_NORMAL PARSE_BREAK_CHUNKS_GENERIC #-}+PARSE_BREAK_CHUNKS_GENERIC+ :: forall m a b. Monad m+ => ParserK.ParserK (GArray.Array a) m b+ -> StreamK m (GArray.Array a)+ -> m (Either PARSE_ERROR_TYPE b, StreamK m (GArray.Array a))+PARSE_BREAK_CHUNKS_GENERIC parser input = do+ let parserk = ParserK.runParser parser ParserK.parserDone 0 0+ in go OPTIONAL(0) [] parserk input++ where++ {-# INLINE goStop #-}+ {-+ goStop+ :: OPTIONAL(Int ->)+ [GArray.Array a]+ -> (ParserK.Input (GArray.Array a)+ -> m (ParserK.Step (GArray.Array a) m b))+ -> m (Either PARSE_ERROR_TYPE b, StreamK m (GArray.Array a))+ -}+ goStop OPTIONAL(pos) backBuf parserk = do+ pRes <- parserk ParserK.None+ case pRes of+ -- If we stop in an alternative, it will try calling the next+ -- parser, the next parser may call initial returning Partial and+ -- then immediately we have to call extract on it.+ ParserK.Partial 0 cont1 ->+ go OPTIONAL(pos) [] cont1 StreamK.nil+ ParserK.Partial n cont1 -> do+ let n1 = negate n+ assertM(n1 >= 0 && n1 <= sum (Prelude.map GArray.length backBuf))+ let (s1, backBuf1) = backtrackGeneric n1 backBuf StreamK.nil+ in go OPTIONAL(pos + n) backBuf1 cont1 s1+ ParserK.Continue 0 cont1 ->+ go OPTIONAL(pos) backBuf cont1 StreamK.nil+ ParserK.Continue n cont1 -> do+ let n1 = negate n+ assertM(n1 >= 0 && n1 <= sum (Prelude.map GArray.length backBuf))+ let (s1, backBuf1) = backtrackGeneric n1 backBuf StreamK.nil+ in go OPTIONAL(pos + n) backBuf1 cont1 s1+ ParserK.Done 0 b ->+ return (Right b, StreamK.nil)+ ParserK.Done n b -> do+ let n1 = negate n+ assertM(n1 >= 0 && n1 <= sum (Prelude.map GArray.length backBuf))+ let (s1, _) = backtrackGeneric n1 backBuf StreamK.nil+ in return (Right b, s1)+ ParserK.Error _n err ->+ let strm = Prelude.foldl (flip StreamK.cons) StreamK.nil backBuf+ in return (Left (PARSE_ERROR(pos + _n) err), strm)++ seekErr n len =+ error $ "parseBreak: Partial: forward seek not implemented n = "+ ++ show n ++ " len = " ++ show len++ {-+ yieldk+ :: OPTIONAL(Int ->)+ [GArray.Array a]+ -> (ParserK.Input (GArray.Array a)+ -> m (ParserK.Step (GArray.Array a) m b))+ -> Array a+ -> StreamK m (GArray.Array a)+ -> m (Either PARSE_ERROR_TYPE b, StreamK m (GArray.Array a))+ -}+ yieldk OPTIONAL(pos) backBuf parserk arr stream = do+ pRes <- parserk (ParserK.Chunk arr)+ let len = GArray.length arr+ case pRes of+ ParserK.Partial n cont1 ->+ case compare n len of+ EQ -> go OPTIONAL(pos + n) [] cont1 stream+ LT -> do+ if n >= 0+ then yieldk OPTIONAL(pos + n) [] cont1 arr stream+ else do+ let n1 = negate n+ bufLen = sum (Prelude.map GArray.length backBuf)+ s = StreamK.cons arr stream+ assertM(n1 >= 0 && n1 <= bufLen)+ let (s1, _) = backtrackGeneric n1 backBuf s+ go OPTIONAL(pos + n) [] cont1 s1+ GT -> seekErr n len+ ParserK.Continue n cont1 ->+ case compare n len of+ EQ -> go OPTIONAL(pos + n) (arr:backBuf) cont1 stream+ LT -> do+ if n >= 0+ then yieldk OPTIONAL(pos + n) backBuf cont1 arr stream+ else do+ let n1 = negate n+ bufLen = sum (Prelude.map GArray.length backBuf)+ s = StreamK.cons arr stream+ assertM(n1 >= 0 && n1 <= bufLen)+ let (s1, backBuf1) = backtrackGeneric n1 backBuf s+ go OPTIONAL(pos + n) backBuf1 cont1 s1+ GT -> seekErr n len+ ParserK.Done n b -> do+ let n1 = len - n+ assertM(n1 <= sum (Prelude.map GArray.length (arr:backBuf)))+ let (s1, _) = backtrackGeneric n1 (arr:backBuf) stream+ in return (Right b, s1)+ ParserK.Error _n err ->+ let strm = Prelude.foldl (flip StreamK.cons) stream (arr:backBuf)+ in return (Left (PARSE_ERROR(pos + _n + 1) err), strm)++ {-+ go+ :: OPTIONAL(Int ->)+ [GArray.Array a]+ -> (ParserK.Input (GArray.Array a)+ -> m (ParserK.Step (GArray.Array a) m b))+ -> StreamK m (GArray.Array a)+ -> m (Either PARSE_ERROR_TYPE b, StreamK m (GArray.Array a))+ -}+ go OPTIONAL(pos) backBuf parserk stream = do+ let stop = goStop OPTIONAL(pos) backBuf parserk+ single a = yieldk OPTIONAL(pos) backBuf parserk a StreamK.nil+ in StreamK.foldStream+ defState (yieldk OPTIONAL(pos) backBuf parserk) single stop stream
+ src/Streamly/Internal/Data/ParserDrivers.hs view
@@ -0,0 +1,141 @@+{-# LANGUAGE CPP #-}+-- |+-- Module : Streamly.Internal.Data.ParserDrivers+-- Copyright : (c) 2018 Composewell Technologies+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC++module Streamly.Internal.Data.ParserDrivers+ (+ -- * Running a Parser+ parseBreak+ , parseBreakPos+ , parseBreakStreamK+ , parseBreakStreamKPos+ , parseBreakChunks+ , parseBreakChunksPos+ , parseBreakChunksGeneric+ , parseBreakChunksGenericPos+ , parseMany+ , parseManyPos+ , parseIterate+ , parseIteratePos+ )+ where++#include "assert.hs"+#include "inline.hs"+#include "ArrayMacros.h"++import Data.Proxy (Proxy(..))+import Fusion.Plugin.Types (Fuse(..))+import GHC.Exts (SpecConstrAnnotation(..))+import GHC.Types (SPEC(..))+import Streamly.Internal.Data.Array.Type (Array(..))+import Streamly.Internal.Data.Parser (ParseError(..), ParseErrorPos(..))+import Streamly.Internal.Data.ParserK.Type (ParserK)+import Streamly.Internal.Data.StreamK.Type (StreamK)+import Streamly.Internal.Data.SVar.Type (adaptState, defState)+import Streamly.Internal.Data.Unbox (Unbox(..))++import qualified Streamly.Internal.Data.Array.Type as Array+import qualified Streamly.Internal.Data.Array.Generic.Type as GArray+import qualified Streamly.Internal.Data.Parser as PR+import qualified Streamly.Internal.Data.Parser as PRD+import qualified Streamly.Internal.Data.ParserK.Type as ParserK+import qualified Streamly.Internal.Data.Stream.Type as Nesting+import qualified Streamly.Internal.Data.Stream.Type as Stream+import qualified Streamly.Internal.Data.Stream.Generate as StreamD+import qualified Streamly.Internal.Data.StreamK.Type as StreamK++import Streamly.Internal.Data.Stream.Type hiding (splitAt)+import Prelude hiding (splitAt)++-- GHC parser does not accept {-# ANN type [] NoSpecConstr #-}, so we need+-- to make a newtype.+{-# ANN type List NoSpecConstr #-}+newtype List a = List {getList :: [a]}++-- The backracking buffer consists of arrays in the most-recent-first order. We+-- want to take a total of n array elements from this buffer. Note: when we+-- have to take an array partially, we must take the last part of the array.+{-# INLINE backtrack #-}+backtrack :: forall m a. Unbox a =>+ Int+ -> [Array a]+ -> StreamK m (Array a)+ -> (StreamK m (Array a), [Array a])+backtrack count buf inp+ | count < 0 = seekOver count+ -- XXX this is handled at the call site, so we can assert that here.+ | count == 0 = (inp, buf)+ | otherwise = go count buf inp++ where++ go n [] _ = seekUnder count n+ go n (x:xs) stream =+ let len = Array.length x+ in if n > len+ then go (n - len) xs (StreamK.cons x stream)+ else if n == len+ then (StreamK.cons x stream, xs)+ else let !(Array contents start end) = x+ !start1 = end - (n * SIZE_OF(a))+ arr1 = Array contents start1 end+ arr2 = Array contents start start1+ in (StreamK.cons arr1 stream, arr2:xs)++ seekOver x =+ error $ "Array.parseBreak: bug in parser, seeking ["+ ++ show (negate x)+ ++ "] elements in future"++ seekUnder x y =+ error $ "Array.parseBreak: bug in parser, backtracking ["+ ++ show x+ ++ "] elements. Goes ["+ ++ show y+ ++ "] elements beyond backtrack buffer"++{-# INLINE backtrackGeneric #-}+backtrackGeneric ::+ Int+ -> [GArray.Array a]+ -> StreamK m (GArray.Array a)+ -> (StreamK m (GArray.Array a), [GArray.Array a])+backtrackGeneric count buf inp+ | count < 0 = seekOver count+ | count == 0 = (inp, buf)+ | otherwise = go count buf inp++ where++ go n [] _ = seekUnder count n+ go n (x:xs) stream =+ let len = GArray.length x+ in if n > len+ then go (n - len) xs (StreamK.cons x stream)+ else if n == len+ then (StreamK.cons x stream, xs)+ else let arr1 = GArray.unsafeSliceOffLen (len - n) n x+ arr2 = GArray.unsafeSliceOffLen 0 (len - n) x+ in (StreamK.cons arr1 stream, arr2:xs)++ seekOver x =+ error $ "Array.Generic.parseBreak: bug in parser, seeking ["+ ++ show (negate x)+ ++ "] elements in future"++ seekUnder x y =+ error $ "Array.Generic.parseBreak: bug in parser, backtracking ["+ ++ show x+ ++ "] elements. Goes ["+ ++ show y+ ++ "] elements beyond backtrack buffer"++#include "ParserDrivers.h"+#define PARSER_WITH_POS+#include "ParserDrivers.h"
+ src/Streamly/Internal/Data/ParserK.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE CPP #-}+-- |+-- Module : Streamly.Internal.Data.ParserK+-- Copyright : (c) 2020 Composewell Technologies+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC++module Streamly.Internal.Data.ParserK+ (+ module Streamly.Internal.Data.ParserK.Type++ -- * Deprecated+ , adaptC+ , adaptCG+ )+where++import Streamly.Internal.Data.Parser (Parser)+import Streamly.Internal.Data.Array (Array)+import Streamly.Internal.Data.Unbox (Unbox)+import Streamly.Internal.Data.ParserK.Type++import qualified Streamly.Internal.Data.Array as Array+import qualified Streamly.Internal.Data.Array.Generic as GenArray++#include "inline.hs"++{-# DEPRECATED adaptC "Use Streamly.Data.Array.toParserK" #-}+{-# INLINE_LATE adaptC #-}+adaptC :: (Monad m, Unbox a) => Parser a m b -> ParserK (Array a) m b+adaptC = Array.toParserK++{-# DEPRECATED adaptCG "Use Streamly.Data.Array.Generic.toParserK" #-}+{-# INLINE_LATE adaptCG #-}+adaptCG ::+ Monad m => Parser a m b -> ParserK (GenArray.Array a) m b+adaptCG = GenArray.toParserK
+ src/Streamly/Internal/Data/ParserK/Type.hs view
@@ -0,0 +1,629 @@+{-# LANGUAGE CPP #-}+-- |+-- Module : Streamly.Internal.Data.Parser.ParserK.Type+-- Copyright : (c) 2020 Composewell Technologies+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- CPS style implementation of parsers.+--+-- The CPS representation allows linear performance for Applicative, sequence,+-- Monad, Alternative, and choice operations compared to the quadratic+-- complexity of the corresponding direct style operations. However, direct+-- style operations allow fusion with ~10x better performance than CPS.+--+-- The direct style representation does not allow for recursive definitions of+-- "some" and "many" whereas CPS allows that.+--+module Streamly.Internal.Data.ParserK.Type+ (+ -- * Setup+ -- | To execute the code examples provided in this module in ghci, please+ -- run the following commands first.+ --+ -- $setup++ -- * Types+ Step (..)+ , Input (..)+ , ParseResult (..)+ , ParserK (..)++ -- * Adapting from Parser+ , parserDone+ , toParserK -- XXX move to StreamK module+ , toParser -- XXX unParserK, unK, unPK++ -- * Basic Parsers+ , fromPure+ , fromEffect+ , die++ -- * Expression Parsers+ , chainl+ , chainl1+ , chainr+ , chainr1++ -- * Deprecated+ , adapt+ )+where++#include "ArrayMacros.h"+#include "assert.hs"+#include "deprecation.h"+#include "inline.hs"++#if !MIN_VERSION_base(4,18,0)+import Control.Applicative (liftA2)+#endif+import Control.Applicative (Alternative(..))+import Control.Monad (MonadPlus(..), ap)+import Control.Monad.IO.Class (MonadIO, liftIO)+-- import Control.Monad.Trans.Class (MonadTrans(lift))+import GHC.Types (SPEC(..))++import qualified Control.Monad.Fail as Fail+import qualified Streamly.Internal.Data.Parser.Type as ParserD++#include "DocTestDataParserK.hs"++-------------------------------------------------------------------------------+-- Developer Notes+-------------------------------------------------------------------------------++-- MonadReader cannot be implemented using continuations for ParserK+--+-- "local" (and hence "MonadReader") cannot be implemented for ParserK because+-- there is no way to override all continuations.+--+-- We can implement `MonadReader` for ParserK via ParserD:+--+-- @+-- instance (Show r, MonadReader r m) => MonadReader r (Parser a m) where+-- {-# INLINE ask #-}+-- ask = Parser.fromEffect ask+-- {-# INLINE local #-}+-- local f (Parser step initial extract) =+-- Parser+-- ((local f .) . step)+-- (local f initial)+-- (local f . extract)+--+-- instance (Show r, MonadReader r m) => MonadReader r (ParserK a m) where+-- {-# INLINE ask #-}+-- ask = ParserK.fromEffect ask+-- {-# INLINE local #-}+-- local f parser = ParserK.adapt $ local f $ ParserK.toParser parser+-- @++-------------------------------------------------------------------------------+-- Types+-------------------------------------------------------------------------------++-- Note: We cannot use an Array directly as input because we need to identify+-- the end of input case using None. We cannot do that using nil Array as nil+-- Arrays can be encountered in normal input as well.+--+-- We could specialize the ParserK type to use an Array directly, that provides+-- some performance improvement. The best advantage of that is when we consume+-- one element at a time from the array. If we really want that perf+-- improvement we can use a special ParserK type with the following Input.+--+-- data Input a = None | Chunk {-# UNPACK #-} !(Array a)+--+-- XXX Rename Chunk to Some.+data Input a = None | Chunk a++-- Note: Step should ideally be called StepResult and StepParser should be just+-- Step, but then it will not be consistent with Parser/Stream.++-- Using "Input" in runParser is not necessary but it avoids making+-- one more function call to get the input. This could be helpful+-- for cases where we process just one element per call.++-- | A parsing function that parses a single input object.+type StepParser a m r = Input a -> m (Step a m r)++-- | The intermediate result of running a parser step. The parser driver may+-- (1) stop with a final result ('Done') with no more inputs to be accepted,+-- (2) generate an intermediate result ('Partial') and accept more inputs, (3)+-- generate no result but wait for more input ('Continue'), (4) or fail with an+-- error ('Error').+--+-- The Int is a count by which the current stream position should be adjusted+-- before calling the next parsing step.+--+-- See the documentation of 'Streamly.Data.Parser.Step' for more details, this+-- has the same semantics.+--+-- /Pre-release/+--+data Step a m r =+ Done !Int r+ | Partial !Int (StepParser a m r)+ | Continue !Int (StepParser a m r)+ -- The Error constructor in ParserK Step carries a count, but the 'Parser'+ -- Step does not carry a count - this is because in ParserK we can have+ -- chunked drivers which can consume multiple inputs before returning a+ -- result or error. In such cases, if an error occurs the parser has to+ -- tell us the offset where the error occurred. In case of 'Parser' type we+ -- do not have chunked drivers, we always drive it one element at a time,+ -- therefore, the offset is not required on Error, the driver already knows+ -- where we are. However, if we ever build a chunked driver for 'Parser' we+ -- will need this argument in Parser Step as well.+ | Error !Int String++instance Functor m => Functor (Step a m) where+ fmap f (Done n r) = Done n (f r)+ fmap f (Partial n k) = Partial n (fmap (fmap f) . k)+ fmap f (Continue n k) = Continue n (fmap (fmap f) . k)+ fmap _ (Error n e) = Error n e++-- Note: Passing position index separately instead of passing it with the+-- result causes huge regression in expression parsing becnhmarks.++-- | The parser's result.+--+-- Int is the position index in the stream relative to the position on entry+-- i.e. when the parser started running. When the parser enters the position+-- index is zero. If the parser consumed n elements then the new position index+-- would be n. If the parser is backtracking then the position index would be+-- negative.+--+-- /Pre-release/+--+data ParseResult b =+ Success !Int !b -- Position index, result+ | Failure !Int !String -- Position index, error++-- | Map a function over 'Success'.+instance Functor ParseResult where+ fmap f (Success n b) = Success n (f b)+ fmap _ (Failure n e) = Failure n e++-- XXX Change the type to the shape (a -> m r -> m r) -> (m r -> m r) -> m r+--+-- The parse continuation would be: Array a -> m (Step a m r) -> m (Step a m r)+-- The extract continuation would be: m (Step a m r) -> m (Step a m r)+--+-- Use Step itself in place of ParseResult.++-- | A continuation passing style parser representation.++-- A parser is a continuation of 'Step's, each step passes a state and a parse+-- result to the next 'Step'. The resulting 'Step' may carry a continuation+-- that consumes input 'a' and results in another 'Step'. Essentially, the+-- continuation may either consume input without a result or return a result+-- with no further input to be consumed.+--+-- The first argument of runParser is a continuation to be invoked after the+-- parser is done, it is of the following shape:+--+-- >>> type Cont = ParseResult b -> Int -> StepParser a m r+--+-- First argument of the continuation is the 'ParseResult'. The current stream+-- position is carried as part of the 'Success' or 'Failure' constructors of+-- 'ParseResult'. The second argument of the continuation is a count of the+-- elements used in the current alterantive in an alternative composition, if+-- the alternative fails we need to backtrack by this amount before invoking+-- the next alternative.+--+-- The second argument of runParser is the incoming stream position adjustment.+-- The parser driver needs to adjust the current position of the stream by this+-- amount before consuming further input. A positive value means move forward+-- by that much in the stream and a negative value means backward. See the+-- 'Step' and 'Streamly.Data.Parser.Step' documentation for more details.+--+-- The third argument is the incoming cumulative used element count for the+-- current alternative, same as described for the continuation above.+--+newtype ParserK a m b = MkParser+ { runParser :: forall r.+ -- Do not eta reduce the applications of this continuation.+ -- Continuation to be invoked after the parser is done+ (ParseResult b -> Int -> StepParser a m r)+ -- stream position adjustment before the parser starts.+ -> Int+ -- initial used count for the current alternative.+ -> Int+ -- final parse result, when the last continuation is done.+ -> StepParser a m r+ }++-------------------------------------------------------------------------------+-- Functor+-------------------------------------------------------------------------------++-- XXX rewrite this using ParserD, expose rmapM from ParserD.++-- | Map a function on the result i.e. on @b@ in @Parser a m b@.+instance Functor m => Functor (ParserK a m) where+ {-# INLINE fmap #-}+ fmap f parser = MkParser $ \k pos used inp ->+ let k1 res = k (fmap f res)+ in runParser parser k1 pos used inp++-------------------------------------------------------------------------------+-- Sequential applicative+-------------------------------------------------------------------------------++-- This is the dual of stream "fromPure".++-- | A parser that always yields a pure value without consuming any input.+--+-- /Pre-release/+--+{-# INLINE fromPure #-}+fromPure :: b -> ParserK a m b+fromPure b = MkParser $ \k pos used inp -> k (Success pos b) used inp++-- | See 'Streamly.Internal.Data.Parser.fromEffect'.+--+-- /Pre-release/+--+{-# INLINE fromEffect #-}+fromEffect :: Monad m => m b -> ParserK a m b+fromEffect eff =+ MkParser $ \k pos used inp -> eff >>= \b -> k (Success pos b) used inp++-- | @f \<$> p1 \<*> p2@ applies parsers p1 and p2 sequentially to an input+-- stream. The first parser runs and processes the input, the remaining input+-- is then passed to the second parser. If both parsers succeed, their outputs+-- are applied to the function @f@. If either parser fails, the operation+-- fails.+--+instance Monad m => Applicative (ParserK a m) where+ {-# INLINE pure #-}+ pure = fromPure++ {-# INLINE (<*>) #-}+ (<*>) = ap++ {-# INLINE (*>) #-}+ p1 *> p2 = MkParser $ \k pos used input ->+ let k1 (Success pos1 _) u inp = runParser p2 k pos1 u inp+ k1 (Failure pos1 e) u inp = k (Failure pos1 e) u inp+ in runParser p1 k1 pos used input++ {-# INLINE (<*) #-}+ p1 <* p2 = MkParser $ \k pos used input ->+ let k1 (Success pos1 b) u1 inp =+ let k2 (Success pos2 _) u2 inp2 = k (Success pos2 b) u2 inp2+ k2 (Failure pos2 e) u2 inp2 = k (Failure pos2 e) u2 inp2+ in runParser p2 k2 pos1 u1 inp+ k1 (Failure pos1 e) u1 inp = k (Failure pos1 e) u1 inp+ in runParser p1 k1 pos used input++ {-# INLINE liftA2 #-}+ liftA2 f p = (<*>) (fmap f p)++-------------------------------------------------------------------------------+-- Monad+-------------------------------------------------------------------------------++-- This is the dual of "nil".+--+-- | A parser that always fails with an error message without consuming+-- any input.+--+-- /Pre-release/+--+{-# INLINE die #-}+die :: String -> ParserK a m b+die err = MkParser (\k pos used inp -> k (Failure pos err) used inp)++-- | Monad composition can be used for lookbehind parsers, we can dynamically+-- compose new parsers based on the results of the previously parsed values.+instance Monad m => Monad (ParserK a m) where+ {-# INLINE return #-}+ return = pure++ {-# INLINE (>>=) #-}+ p >>= f = MkParser $ \k pos used input ->+ let k1 (Success pos1 b) u1 inp = runParser (f b) k pos1 u1 inp+ k1 (Failure pos1 e) u1 inp = k (Failure pos1 e) u1 inp+ in runParser p k1 pos used input++ {-# INLINE (>>) #-}+ (>>) = (*>)++#if !(MIN_VERSION_base(4,13,0))+ -- This is redefined instead of just being Fail.fail to be+ -- compatible with base 4.8.+ {-# INLINE fail #-}+ fail = die+#endif+instance Monad m => Fail.MonadFail (ParserK a m) where+ {-# INLINE fail #-}+ fail = die++instance MonadIO m => MonadIO (ParserK a m) where+ {-# INLINE liftIO #-}+ liftIO = fromEffect . liftIO++-------------------------------------------------------------------------------+-- Alternative+-------------------------------------------------------------------------------++-- | @p1 \<|> p2@ passes the input to parser p1, if it succeeds, the result is+-- returned. However, if p1 fails, the parser driver backtracks and tries the+-- same input on the alternative parser p2, returning the result if it+-- succeeds.+--+instance Monad m => Alternative (ParserK a m) where+ {-# INLINE empty #-}+ empty = die "empty"++ {-# INLINE (<|>) #-}+ p1 <|> p2 = MkParser $ \k pos _ input ->+ let+ k1 (Failure pos1 _) used inp = runParser p2 k (pos1 - used) 0 inp+ k1 success _ inp = k success 0 inp+ in runParser p1 k1 pos 0 input++ -- some and many are implemented here instead of using default definitions+ -- so that we can use INLINE on them. It gives 50% performance improvement.++ {-# INLINE many #-}+ many v = many_v++ where++ many_v = some_v <|> pure []+ some_v = (:) <$> v <*> many_v++ {-# INLINE some #-}+ some v = some_v++ where++ many_v = some_v <|> pure []+ some_v = (:) <$> v <*> many_v++-- | 'mzero' is same as 'empty', it aborts the parser. 'mplus' is same as+-- '<|>', it selects the first succeeding parser.+--+instance Monad m => MonadPlus (ParserK a m) where+ {-# INLINE mzero #-}+ mzero = die "mzero"++ {-# INLINE mplus #-}+ mplus = (<|>)++{-+instance MonadTrans (ParserK a) where+ {-# INLINE lift #-}+ lift = fromEffect+-}++--------------------------------------------------------------------------------+-- Make a ParserK from Parser+--------------------------------------------------------------------------------++{-# INLINE adaptWith #-}+adaptWith+ :: forall m a s b r. (Monad m)+ => (s -> a -> m (ParserD.Step s b))+ -> m (ParserD.Initial s b)+ -> (s -> m (ParserD.Final s b))+ -> (ParseResult b -> Int -> Input a -> m (Step a m r))+ -> Int+ -> Int+ -> Input a+ -> m (Step a m r)+adaptWith pstep initial extract cont !relPos !usedCount !input = do+ res <- initial+ case res of+ ParserD.IPartial pst -> do+ if relPos == 0+ then+ case input of+ -- In element parser case chunk is just one element+ Chunk element -> parseContChunk usedCount pst element+ None -> parseContNothing usedCount pst+ -- XXX Previous code was using Continue in this case+ else+ -- We consumed previous input, need to fetch the next+ -- input from the driver.+ pure $ Partial relPos (parseCont usedCount pst)+ ParserD.IDone b -> cont (Success relPos b) usedCount input+ ParserD.IError err -> cont (Failure relPos err) usedCount input++ where++ {-# NOINLINE parseContChunk #-}+ parseContChunk !count !state x = do+ go SPEC state++ where++ go !_ !pst = do+ r <- pstep pst x+ case r of+ -- Done, call the next continuation+ ParserD.SDone 1 b ->+ cont (Success 1 b) (count + 1) (Chunk x)+ ParserD.SDone 0 b ->+ cont (Success 0 b) count (Chunk x)+ ParserD.SDone m b -> -- n > 1+ let n = 1 - m+ in cont (Success (1 - n) b) (count + 1 - n) (Chunk x)++ -- Not done yet, return the parseCont continuation+ ParserD.SPartial 1 pst1 ->+ pure $ Partial 1 (parseCont (count + 1) pst1)+ ParserD.SPartial 0 pst1 ->+ -- XXX if we recurse we are not dropping backtrack buffer+ -- on partial.+ -- XXX recurse or call the driver?+ go SPEC pst1+ ParserD.SPartial m pst1 -> -- n > 0+ let n = 1 - m+ in pure $ Partial (1 - n) (parseCont (count + 1 - n) pst1)+ ParserD.SContinue 1 pst1 ->+ pure $ Continue 1 (parseCont (count + 1) pst1)+ ParserD.SContinue 0 pst1 ->+ -- XXX recurse or call the driver?+ go SPEC pst1+ ParserD.SContinue m pst1 -> -- n > 0+ let n = 1 - m+ in pure $ Continue (1 - n) (parseCont (count + 1 - n) pst1)++ -- SError case+ ParserD.SError err ->+ cont (Failure 0 err) count (Chunk x)++ {-# NOINLINE parseContNothing #-}+ parseContNothing !count !pst = do+ r <- extract pst+ case r of+ ParserD.FDone n b ->+ assert (n <= 0)+ (cont (Success n b) (count + n) None)+ ParserD.FContinue n pst1 ->+ assert (n <= 0)+ (return $ Continue n (parseCont (count + n) pst1))+ ParserD.FError err ->+ -- XXX It is called only when there is no input chunk. So using+ -- 0 as the position is correct?+ cont (Failure 0 err) count None++ -- XXX Maybe we can use two separate continuations instead of using+ -- Just/Nothing cases here. That may help in avoiding the parseContJust+ -- function call.+ {-# INLINE parseCont #-}+ parseCont !cnt !pst (Chunk element) = parseContChunk cnt pst element+ parseCont !cnt !pst None = parseContNothing cnt pst++-- | Convert a 'Parser' to 'ParserK'.+--+-- /Pre-release/+--+{-# INLINE_LATE toParserK #-}+toParserK, adapt :: Monad m => ParserD.Parser a m b -> ParserK a m b+toParserK (ParserD.Parser step initial extract) =+ MkParser $ adaptWith step initial extract++RENAME(adapt,toParserK)++-------------------------------------------------------------------------------+-- Convert CPS style 'Parser' to direct style 'D.Parser'+-------------------------------------------------------------------------------++-- | A continuation to extract the result when a CPS parser is done.+{-# INLINE parserDone #-}+parserDone :: Applicative m =>+ ParseResult b -> Int -> Input a -> m (Step a m b)+parserDone (Success n b) _ _ =+ -- trace ("parserDone Success n: " ++ show n) $+ assert(n <= 1) `seq` pure (Done n b)+parserDone (Failure n e) _ _ =+ -- trace ("parserDone Failure n: " ++ show n) $+ assert(n <= 1) `seq` pure (Error n e)++-- XXX Note that this works only for single element parsers and not for Array+-- input parsers. The asserts will fail for array parsers.+-- XXX We should move this to StreamK module along with toParserK++-- | Convert a CPS style 'ParserK' to a direct style 'Parser'.+--+-- /Pre-release/+--+{-# INLINE_LATE toParser #-}+toParser :: Monad m => ParserK a m b -> ParserD.Parser a m b+toParser parser = ParserD.Parser step initial extract++ where++ initial = pure (ParserD.IPartial (runParser parser parserDone 0 0))++ step cont a = do+ r <- cont (Chunk a)+ return $ case r of+ Done n b -> assert (n <= 1) (ParserD.SDone n b)+ Error _ e -> ParserD.SError e+ Partial n cont1 -> assert (n <= 1) (ParserD.SPartial n cont1)+ Continue n cont1 -> assert (n <= 1) (ParserD.SContinue n cont1)++ extract cont = do+ r <- cont None+ case r of+ Done n b -> assert (n <= 0) (return $ ParserD.FDone n b)+ Error _ e -> return $ ParserD.FError e+ Partial _ cont1 -> extract cont1+ Continue n cont1 ->+ assert (n <= 0) (return $ ParserD.FContinue n cont1)++{-# RULES "toParserK/toParser fusion" [2]+ forall s. toParser (toParserK s) = s #-}+{-# RULES "toParser/toParserK fusion" [2]+ forall s. toParserK (toParser s) = s #-}++-- | @chainl1 p op x@ parses /one/ or more occurrences of @p@, separated by+-- @op@. Returns a value obtained by a /left/ associative application of all+-- functions returned by @op@ to the values returned by @p@.+--+-- >>> num = Parser.decimal+-- >>> plus = Parser.char '+' *> pure (+)+-- >>> expr = ParserK.chainl1 (StreamK.toParserK num) (StreamK.toParserK plus)+-- >>> StreamK.parse expr $ StreamK.fromStream $ Stream.fromList "1+2+3"+-- Right 6+--+-- If you're building full expression parsers with operator precedence and+-- associativity, consider using @makeExprParser@ from the @parser-combinators@+-- package.+--+-- See also 'Streamly.Internal.Data.Parser.deintercalate'.+--+{-# INLINE chainl1 #-}+chainl1 :: ParserK b IO a -> ParserK b IO (a -> a -> a) -> ParserK b IO a+chainl1 p op = p >>= go++ where++ go l = step l <|> pure l++ step l = do+ f <- op+ r <- p+ go (f l r)++-- | @chainl p op x@ is like 'chainl1' but allows /zero/ or more occurrences of+-- @p@, separated by @op@. If there are zero occurrences of @p@, the value @x@+-- is returned.+{-# INLINE chainl #-}+chainl :: ParserK b IO a -> ParserK b IO (a -> a -> a) -> a -> ParserK b IO a+chainl p op x = chainl1 p op <|> pure x++-- | Like chainl1 but parses right associative application of the operator+-- instead of left associative.+--+-- >>> num = Parser.decimal+-- >>> pow = Parser.char '^' *> pure (^)+-- >>> expr = ParserK.chainr1 (StreamK.toParserK num) (StreamK.toParserK pow)+-- >>> StreamK.parse expr $ StreamK.fromStream $ Stream.fromList "2^3^2"+-- Right 512+--+{-# INLINE chainr1 #-}+chainr1 :: ParserK b IO a -> ParserK b IO (a -> a -> a) -> ParserK b IO a+chainr1 p op = p >>= go++ where++ go l = step l <|> pure l++ step l = do+ f <- op+ r <- chainr1 p op+ return (f l r)++-- | @chainr p op x@ is like 'chainr1' but allows /zero/ or more occurrences of+-- @p@, separated by @op@. If there are zero occurrences of @p@, the value @x@+-- is returned.+{-# INLINE chainr #-}+chainr :: ParserK b IO a -> ParserK b IO (a -> a -> a) -> a -> ParserK b IO a+chainr p op x = chainr1 p op <|> pure x
+ src/Streamly/Internal/Data/Path.hs view
@@ -0,0 +1,58 @@+-- |+-- Module : Streamly.Internal.Data.Path+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--+module Streamly.Internal.Data.Path+ (+ -- * Exceptions+ PathException (..)++ -- * Conversions+ , IsPath (..)+ )+where++import Control.Exception (Exception)+import Control.Monad.Catch (MonadThrow(..))++------------------------------------------------------------------------------+-- Exceptions+------------------------------------------------------------------------------++-- | Exceptions thrown by path operations.+newtype PathException =+ InvalidPath String+ deriving (Show, Eq)++instance Exception PathException++------------------------------------------------------------------------------+-- Conversions+------------------------------------------------------------------------------++-- XXX Swap the order of IsPath arguments?+-- XXX rename to fromBase, fromBasePath, fromOsPath?++-- | If the type @a b@ is a member of 'IsPath' it means we know how to convert+-- the type @b@ to and from the base type @a@.+--+class IsPath a b where+ -- | Like 'fromPath' but does not check the properties of 'Path'. The user+ -- is responsible to maintain the invariants enforced by the type @b@+ -- otherwise surprising behavior may result.+ --+ -- This operation provides performance and simplicity when we know that the+ -- properties of the path are already verified, for example, when we get+ -- the path from the file system or from the OS APIs.+ unsafeFromPath :: a -> b++ -- | Convert a base path type to other forms of well-typed paths. It may+ -- fail if the path does not satisfy the properties of the target type.+ --+ fromPath :: MonadThrow m => a -> m b++ -- | Convert a well-typed path to the base path type. Never fails.+ toPath :: b -> a
src/Streamly/Internal/Data/Pipe.hs view
@@ -6,271 +6,34 @@ -- Stability : experimental -- Portability : GHC ----- There are three fundamental types in streamly. They are streams--- ("Streamly.Data.Stream"), pipes ("Streamly.Internal.Data.Pipe") and folds ("Streamly.Data.Fold").+-- There are three fundamental types that make up a stream pipeline:+--+-- * Stream: sources+-- * Scan: transformations+-- * Fold: sinks+-- -- Streams are sources or producers of values, multiple sources can be merged -- into a single source but a source cannot be split into multiple stream -- sources. Folds are sinks or consumers, a stream can be split and -- distributed to multiple folds but the results cannot be merged back into a--- stream source again. Pipes are transformations, a stream source can be split--- and distributed to multiple pipes each pipe can apply its own transform on--- the stream and the results can be merged back into a single pipe. Pipes can--- be attached to a source to produce a source or they can be attached to a--- fold to produce a fold, or multiple pipes can be merged or zipped into a--- single pipe.+-- stream source again. Scans are simple one-to-one transformations with+-- filtering. One element cannot be transformed to multiple elements. --+-- The Pipe type is a super type of all the above, it is the most complex type.+-- All of these can be represented by a pipe. A pipe can act as a source or a+-- sink or a transformation, dynamically. A stream source can be split and+-- distributed to multiple pipes each pipe can apply its own transform on the+-- stream and the results can be merged back into a single pipe. Pipes can be+-- attached to a source to produce a source or they can be attached to a fold+-- to produce a fold, or multiple pipes can be merged or zipped into a single+-- pipe.+-- -- > import qualified Streamly.Internal.Data.Pipe as Pipe module Streamly.Internal.Data.Pipe (- -- * Pipe Type- Pipe-- -- * Pipes- -- ** Mapping- , map- , mapM-- {-- -- ** Filtering- , lfilter- , lfilterM- -- , ldeleteBy- -- , luniq-- {-- -- ** Mapping Filters- , lmapMaybe- , lmapMaybeM-- -- ** Scanning Filters- , lfindIndices- , lelemIndices-- -- ** Insertion- -- | Insertion adds more elements to the stream.-- , linsertBy- , lintersperseM-- -- ** Reordering- , lreverse- -}-- -- * Parsing- -- ** Trimming- , ltake- -- , lrunFor -- time- , ltakeWhile- {-- , ltakeWhileM- , ldrop- , ldropWhile- , ldropWhileM- -}-- -- ** Splitting- -- | Streams can be split into segments in space or in time. We use the- -- term @chunk@ to refer to a spatial length of the stream (spatial window)- -- and the term @session@ to refer to a length in time (time window).-- -- In imperative terms, grouped folding can be considered as a nested loop- -- where we loop over the stream to group elements and then loop over- -- individual groups to fold them to a single value that is yielded in the- -- output stream.-- -- *** By Chunks- , chunksOf- , sessionsOf-- -- *** By Elements- , splitBy- , splitSuffixBy- , splitSuffixBy'- -- , splitPrefixBy- , wordsBy-- -- *** By Sequences- , splitOn- , splitSuffixOn- -- , splitPrefixOn- -- , wordsOn-- -- Keeping the delimiters- , splitOn'- , splitSuffixOn'- -- , splitPrefixOn'-- -- Splitting by multiple sequences- -- , splitOnAny- -- , splitSuffixOnAny- -- , splitPrefixOnAny-- -- ** Grouping- , groups- , groupsBy- , groupsRollingBy- -}-- -- * Composing Pipes- , tee- , zipWith- , compose-- {-- -- * Distributing- -- |- -- The 'Applicative' instance of a distributing 'Fold' distributes one copy- -- of the stream to each fold and combines the results using a function.- --- -- @- --- -- |-------Fold m a b--------|- -- ---stream m a---| |---m (b,c,...)- -- |-------Fold m a c--------|- -- | |- -- ...- -- @- --- -- To compute the average of numbers in a stream without going through the- -- stream twice:- --- -- >>> let avg = (/) <$> FL.sum <*> fmap fromIntegral FL.length- -- >>> FL.foldl' avg (S.enumerateFromTo 1.0 100.0)- -- 50.5- --- -- The 'Semigroup' and 'Monoid' instances of a distributing fold distribute- -- the input to both the folds and combines the outputs using Monoid or- -- Semigroup instances of the output types:- --- -- >>> import Data.Monoid (Sum)- -- >>> FL.foldl' (FL.head <> FL.last) (fmap Sum $ S.enumerateFromTo 1.0 100.0)- -- Just (Sum {getSum = 101.0})- --- -- The 'Num', 'Floating', and 'Fractional' instances work in the same way.-- , tee- , distribute-- -- * Partitioning- -- |- -- Direct items in the input stream to different folds using a function to- -- select the fold. This is useful to demultiplex the input stream.- -- , partitionByM- -- , partitionBy- , partition-- -- * Demultiplexing- , demux- -- , demuxWith- , demux_- -- , demuxWith_-- -- * Classifying- , classify- -- , classifyWith-- -- * Unzipping- , unzip- -- These can be expressed using lmap/lmapM and unzip- -- , unzipWith- -- , unzipWithM-- -- * Nested Folds- -- , concatMap- -- , chunksOf- , duplicate -- experimental-- -- * Windowed Classification- -- | Split the stream into windows or chunks in space or time. 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 is split- -- into windows of specified size, the window can be terminated early if- -- the closing flag is specified in the input stream.- --- -- The term "chunk" is used for a space window and the term "session" is- -- used for a time window.-- -- ** Tumbling Windows- -- | A new window starts after the previous window is finished.- -- , classifyChunksOf- , classifySessionsOf-- -- ** Keep Alive Windows- -- | The window size is extended if an event arrives within the specified- -- window size. This can represent sessions with idle or inactive timeout.- -- , classifyKeepAliveChunks- , classifyKeepAliveSessions-- {-- -- ** Sliding Windows- -- | A new window starts after the specified slide from the previous- -- window. Therefore windows can overlap.- , classifySlidingChunks- , classifySlidingSessions- -}- -- ** Sliding Window Buffers- -- , slidingChunkBuffer- -- , slidingSessionBuffer--}+ module Streamly.Internal.Data.Pipe.Type ) where --- import Control.Concurrent (threadDelay, forkIO, killThread)--- import Control.Concurrent.MVar (MVar, newMVar, takeMVar, putMVar)--- import Control.Exception (SomeException(..), catch, mask)--- import Control.Monad (void)--- import Control.Monad.Catch (throwM)--- import Control.Monad.IO.Class (MonadIO(..))--- import Control.Monad.Trans (lift)--- import Control.Monad.Trans.Control (control)--- import Data.Functor.Identity (Identity)--- import Data.Heap (Entry(..))--- import Data.Map.Strict (Map)--- import Data.Maybe (fromJust, isJust, isNothing)---- import Foreign.Storable (Storable(..))-import Prelude- hiding (id, filter, drop, dropWhile, take, takeWhile, zipWith, foldr,- foldl, map, 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, mconcat, foldMap, unzip,- span, splitAt, break, mapM)---- import qualified Data.Heap as H--- import qualified Data.Map.Strict as Map--- import qualified Prelude---- import Streamly.Data.Fold.Types (Fold(..)) import Streamly.Internal.Data.Pipe.Type- (Pipe(..), PipeState(..), Step(..), zipWith, tee, map, compose)--- import Streamly.Internal.Data.Array.Type (Array)--- import Streamly.Internal.Data.Ring.Unboxed (Ring)--- import Streamly.Internal.Data.Stream (Stream)--- import Streamly.Internal.Data.Time.Units--- (AbsTime, MilliSecond64(..), addToAbsTime, diffAbsTime, toRelTime,--- toAbsTime)---- import Streamly.Internal.Data.Strict---- import qualified Streamly.Internal.Data.Array.Type as A--- import qualified Streamly.Data.Stream as S--- import qualified Streamly.Internal.Data.Stream.StreamD as D--- import qualified Streamly.Internal.Data.Stream.StreamK as K--- import qualified Streamly.Internal.Data.Stream.Common as P----------------------------------------------------------------------------------- Pipes----------------------------------------------------------------------------------- | Lift a monadic function to a 'Pipe'.------ @since 0.7.0-{-# INLINE mapM #-}-mapM :: Monad m => (a -> m b) -> Pipe m a b-mapM f = Pipe consume undefined ()- where- consume _ a = do- r <- f a- return $ Yield r (Consume ())
src/Streamly/Internal/Data/Pipe/Type.hs view
@@ -1,5 +1,3 @@-#include "inline.hs"- -- | -- Module : Streamly.Internal.Data.Pipe.Type -- Copyright : (c) 2019 Composewell Technologies@@ -9,99 +7,413 @@ -- Portability : GHC module Streamly.Internal.Data.Pipe.Type- ( Step (..)+ (+ -- * Type+ Step (..) , Pipe (..)- , PipeState (..)- , zipWith- , tee- , map++ -- * From folds+ , fromStream+ , fromScanr+ , fromFold+ , scanFold++ -- * Primitive Pipes+ , identity+ , map -- function?+ , mapM -- functionM?+ , filter+ , filterM++ -- * Combinators , compose+ , teeMerge+ -- , zipWith -- teeZip ) where -import Control.Arrow (Arrow(..))+#include "inline.hs"+-- import Control.Arrow (Arrow(..)) import Control.Category (Category(..))-import Data.Maybe (isJust)-import Prelude hiding (zipWith, map, id, unzip, null)-import Streamly.Internal.Data.Tuple.Strict (Tuple'(..), Tuple3'(..))+import Data.Functor ((<&>))+#if __GLASGOW_HASKELL__ >= 810+import Data.Kind (Type)+#endif+import Fusion.Plugin.Types (Fuse(..))+import Streamly.Internal.Data.Fold.Type (Fold(..))+import Streamly.Internal.Data.Scanr (Scanr(..))+import Streamly.Internal.Data.Stream.Type (Stream(..))+-- import Streamly.Internal.Data.Tuple.Strict (Tuple'(..), Tuple3'(..))+import Streamly.Internal.Data.SVar.Type (defState) import qualified Prelude+import qualified Streamly.Internal.Data.Fold.Type as Fold+import qualified Streamly.Internal.Data.Stream.Type as Stream +import Prelude hiding (filter, zipWith, map, mapM, id, unzip, null)++-- $setup+-- >>> :m+-- >>> :set -XFlexibleContexts+-- >>> import Control.Category+--+-- >>> import qualified Streamly.Internal.Data.Fold as Fold+-- >>> import qualified Streamly.Internal.Data.Pipe as Pipe+-- >>> import qualified Streamly.Internal.Data.Stream as Stream+ ------------------------------------------------------------------------------ -- Pipes ------------------------------------------------------------------------------ --- A scan is a much simpler version of pipes. A scan always produces an output--- on an input whereas a pipe does not necessarily produce an output on an--- input, it might consume multiple inputs before producing an output. That way--- it can implement filtering. Similarly, it can produce more than one output--- on an single input.+-- XXX If we do not want to change Streams, we should use "Yield b s" instead+-- of "Yield s b". Though "Yield s b" is sometimes better when using curried+-- "Yield s". "Yield b" sounds better because the verb applies to "b". ----- Therefore when two pipes are composed in parallel formation, one may run--- slower or faster than the other. If all of them are being fed from the same--- source, we may have to buffer the input to match the speeds. In case of--- scans we do not have that problem.+-- Note: We could reduce the number of constructors by using Consume | Produce+-- wrapper around the state. But when fusion does not occur, it may be better+-- to use a flat structure rather than nested to avoid more allocations. In a+-- flat structure the pointer tag from the Step constructor itself can identiy+-- any of the 5 constructors. ----- We may also need a "Stop" constructor to indicate that we are not generating--- any more values and we can have a "Done" constructor to indicate that we are--- not consuming any more values. Similarly we can have a stop with error or--- exception and a done with error or leftover values.+{-# ANN type Step Fuse #-}+data Step cs ps b =+ YieldC cs b -- ^ Yield and consume+ | SkipC cs -- ^ Skip and consume+ | Stop -- ^ when consuming, Stop means input remains unused+ -- Therefore, Stop should not be used when we are processing an input,+ -- instead use YieldP and then Stop.+ | YieldP ps b -- ^ Yield and produce+ | SkipP ps -- ^ Skip and produce++instance Functor (Step cs ps) where+ {-# INLINE fmap #-}+ fmap f (YieldC s b) = YieldC s (f b)+ fmap f (YieldP s b) = YieldP s (f b)+ fmap _ (SkipC s) = SkipC s+ fmap _ (SkipP s) = SkipP s+ fmap _ Stop = Stop++-- A pipe uses a consume function and a produce function. It can dynamically+-- switch from consume/fold mode to a produce/source mode. ----- In generator mode, Continue means no output/continue. In fold mode Continue means--- need more input to produce result. we can perhaps call it Continue instead.+-- We can upgrade a stream, fold or scan into a pipe. However, the simpler+-- types should be preferred because they can be more efficient and fuse+-- better. ---data Step s a =- Yield a s- | Continue s+-- The design of the Pipe type is such that the pipe decides whether it wants+-- to consume or produce, not the driver. The driver has to do what the pipe+-- dictates, if it can. The starting state of the pipe could either be+-- consuming or producing. Current implementation starts with a consuming+-- state. If the default state of the pipe is consumption state and there is no+-- input, the driver can call finalC :: cs -> m (Step cs ps b) to switch the+-- pipe to production state. The pipe can use SkipP to switch to production+-- state. If the default state of the pipe is producing state, the pipe can use+-- SkipC to switch to the consumer state. The driver can use finalP to switch+-- to consuming state. -- | Represents a stateful transformation over an input stream of values of -- type @a@ to outputs of type @b@ in 'Monad' @m@.+--+-- The constructor is @Pipe consume produce initial@.+data Pipe m a b =+ forall cs ps. Pipe+ (cs -> a -> m (Step cs ps b))+ (ps -> m (Step cs ps b))+ -- (cs -> m (Step cs ps b)) -- finalC+ -- (ps -> m (Step cs ps b)) -- finalP+ cs -- Either cs ps --- A pipe uses a consume function and a produce function. It can switch from--- consume/fold mode to a produce/source mode. The first step function is a--- fold function while the seocnd one is a stream generator function.+------------------------------------------------------------------------------+-- Functor: Mapping on the output+------------------------------------------------------------------------------++-- | 'fmap' maps a pure function on a scan output. ----- We can upgrade a stream or a fold into a pipe. However, streams are more--- efficient in generation and folds are more efficient in consumption.+-- >>> Stream.toList $ Stream.pipe (fmap (+1) Pipe.identity) $ Stream.fromList [1..5::Int]+-- [2,3,4,5,6] ----- For pure transformation we can have a 'Scan' type. A Scan would be more--- efficient in zipping whereas pipes are useful for merging and zipping where--- we know buffering can occur. A Scan type can be upgraded to a pipe.+instance Functor m => Functor (Pipe m a) where+ {-# INLINE_NORMAL fmap #-}+ fmap f (Pipe consume produce cinitial) =+ Pipe consume1 produce1 cinitial++ where++ {-# INLINE_LATE consume1 #-}+ consume1 s b = fmap (fmap f) (consume s b)+ {-# INLINE_LATE produce1 #-}+ produce1 s = fmap (fmap f) (produce s)++-------------------------------------------------------------------------------+-- Category+-------------------------------------------------------------------------------++{-# ANN type ComposeConsume Fuse #-}+#if __GLASGOW_HASKELL__ >= 810+type ComposeConsume :: Type -> Type -> Type -> Type+#endif+data ComposeConsume csL psL csR =+ ComposeConsume csL csR++{-# ANN type ComposeProduce Fuse #-}+data ComposeProduce csL psL csR psR =+ ComposeProduceR csL psR+ | ComposeProduceL psL csR+ | ComposeProduceLR psL psR++-- | Series composition. Compose two pipes such that the output of the second+-- pipe is attached to the input of the first pipe. ----- XXX In general the starting state could either be for generation or for--- consumption. Currently we are only starting with a consumption state.+-- >>> Stream.toList $ Stream.pipe (Pipe.map (+1) >>> Pipe.map (+1)) $ Stream.fromList [1..5::Int]+-- [3,4,5,6,7] ----- An explicit either type for better readability of the code-data PipeState s1 s2 = Consume s1 | Produce s2+{-# INLINE_NORMAL compose #-}+compose :: Monad m => Pipe m b c -> Pipe m a b -> Pipe m a c+compose+ (Pipe consumeR produceR initialR)+ (Pipe consumeL produceL initialL) =+ Pipe consume produce (ComposeConsume initialL initialR) -isProduce :: PipeState s1 s2 -> Bool-isProduce s =- case s of- Produce _ -> True- Consume _ -> False+ where -data Pipe m a b =- forall s1 s2. Pipe (s1 -> a -> m (Step (PipeState s1 s2) b))- (s2 -> m (Step (PipeState s1 s2) b)) s1+ {-# INLINE consumeLFeedR #-}+ consumeLFeedR csL csR bL = do+ rR <- consumeR csR bL+ return+ $ case rR of+ YieldC csR1 br -> YieldC (ComposeConsume csL csR1) br+ SkipC csR1 -> SkipC (ComposeConsume csL csR1)+ Stop -> Stop+ YieldP psR br -> YieldP (ComposeProduceR csL psR) br+ SkipP psR -> SkipP (ComposeProduceR csL psR) -instance Monad m => Functor (Pipe m a) where- {-# INLINE_NORMAL fmap #-}- fmap f (Pipe consume produce initial) = Pipe consume' produce' initial- where- {-# INLINE_LATE consume' #-}- consume' st a = do- r <- consume st a- return $ case r of- Yield x s -> Yield (f x) s- Continue s -> Continue s+ {-# INLINE produceLFeedR #-}+ produceLFeedR psL csR bL = do+ rR <- consumeR csR bL+ return+ $ case rR of+ YieldC csR1 br -> YieldP (ComposeProduceL psL csR1) br+ SkipC csR1 -> SkipP (ComposeProduceL psL csR1)+ Stop -> Stop+ YieldP psR br -> YieldP (ComposeProduceLR psL psR) br+ SkipP psR -> SkipP (ComposeProduceLR psL psR) - {-# INLINE_LATE produce' #-}- produce' st = do- r <- produce st- return $ case r of- Yield x s -> Yield (f x) s- Continue s -> Continue s+ consume (ComposeConsume csL csR) x = do+ rL <- consumeL csL x+ case rL of+ YieldC csL1 bL ->+ -- XXX Use SkipC instead? Flat may be better for fusion.+ consumeLFeedR csL1 csR bL+ SkipC csL1 -> return $ SkipC (ComposeConsume csL1 csR)+ Stop -> return Stop+ YieldP psL bL ->+ -- XXX Use SkipC instead?+ produceLFeedR psL csR bL+ SkipP psL -> return $ SkipP (ComposeProduceL psL csR) + produce (ComposeProduceL psL csR) = do+ rL <- produceL psL+ case rL of+ YieldC csL bL ->+ -- XXX Use SkipC instead?+ consumeLFeedR csL csR bL+ SkipC csL -> return $ SkipC (ComposeConsume csL csR)+ Stop -> return Stop+ YieldP psL1 bL ->+ -- XXX Use SkipC instead?+ produceLFeedR psL1 csR bL+ SkipP psL1 -> return $ SkipP (ComposeProduceL psL1 csR)++ produce (ComposeProduceR csL psR) = do+ rR <- produceR psR+ return+ $ case rR of+ YieldC csR1 br -> YieldC (ComposeConsume csL csR1) br+ SkipC csR1 -> SkipC (ComposeConsume csL csR1)+ Stop -> Stop+ YieldP psR1 br -> YieldP (ComposeProduceR csL psR1) br+ SkipP psR1 -> SkipP (ComposeProduceR csL psR1)++ produce (ComposeProduceLR psL psR) = do+ rR <- produceR psR+ return+ $ case rR of+ YieldC csR1 br -> YieldP (ComposeProduceL psL csR1) br+ SkipC csR1 -> SkipP (ComposeProduceL psL csR1)+ Stop -> Stop+ YieldP psR1 br -> YieldP (ComposeProduceLR psL psR1) br+ SkipP psR1 -> SkipP (ComposeProduceLR psL psR1)++-- | A pipe representing mapping of a monadic action.+--+-- >>> Stream.toList $ Stream.pipe (Pipe.mapM print) $ Stream.fromList [1..5::Int]+-- 1+-- 2+-- 3+-- 4+-- 5+-- [(),(),(),(),()]+--+{-# INLINE mapM #-}+mapM :: Monad m => (a -> m b) -> Pipe m a b+mapM f = Pipe (\() a -> f a <&> YieldC ()) undefined ()++-- | A pipe representing mapping of a pure function.+--+-- >>> Stream.toList $ Stream.pipe (Pipe.map (+1)) $ Stream.fromList [1..5::Int]+-- [2,3,4,5,6]+--+{-# INLINE map #-}+map :: Monad m => (a -> b) -> Pipe m a b+map f = mapM (return Prelude.. f)++{- HLINT ignore "Redundant map" -}++-- | An identity pipe producing the same output as input.+--+-- >>> identity = Pipe.map Prelude.id+--+-- >>> Stream.toList $ Stream.pipe (Pipe.identity) $ Stream.fromList [1..5::Int]+-- [1,2,3,4,5]+--+{-# INLINE identity #-}+identity :: Monad m => Pipe m a a+identity = map Prelude.id++-- | "." composes the pipes in series.+instance Monad m => Category (Pipe m) where+ {-# INLINE id #-}+ id = identity++ {-# INLINE (.) #-}+ (.) = compose++{-# ANN type TeeMergeConsume Fuse #-}+data TeeMergeConsume csL csR+ = TeeMergeConsume !csL !csR+ | TeeMergeConsumeOnlyL !csL+ | TeeMergeConsumeOnlyR !csR++{-# ANN type TeeMergeProduce Fuse #-}+data TeeMergeProduce csL csR psL psR x+ = TeeMergeProduce !csL !csR !x+ | TeeMergeProduceL !psL !csR !x+ | TeeMergeProduceR !csL !psR+ | TeeMergeProduceOnlyL !psL+ | TeeMergeProduceOnlyR !psR++-- | Parallel composition. Distribute the input across two pipes and merge+-- their outputs.+--+-- >>> Stream.toList $ Stream.pipe (Pipe.teeMerge Pipe.identity (Pipe.map (\x -> x * x))) $ Stream.fromList [1..5::Int]+-- [1,1,2,4,3,9,4,16,5,25]+--+{-# INLINE_NORMAL teeMerge #-}+teeMerge :: Monad m => Pipe m a b -> Pipe m a b -> Pipe m a b+teeMerge (Pipe consumeL produceL initialL) (Pipe consumeR produceR initialR) =+ Pipe consume produce (TeeMergeConsume initialL initialR)++ where++ {-# INLINE feedRightOnly #-}+ feedRightOnly csR a = do+ resR <- consumeR csR a+ return+ $ case resR of+ YieldC cs b -> YieldC (TeeMergeConsumeOnlyR cs) b+ SkipC cs -> SkipC (TeeMergeConsumeOnlyR cs)+ Stop -> Stop+ YieldP ps b -> YieldP (TeeMergeProduceOnlyR ps) b+ SkipP ps -> SkipP (TeeMergeProduceOnlyR ps)++ {-# INLINE_LATE consume #-}+ consume (TeeMergeConsume csL csR) a = do+ resL <- consumeL csL a+ case resL of+ YieldC cs b -> return $ YieldP (TeeMergeProduce cs csR a) b+ SkipC cs -> return $ SkipP (TeeMergeProduce cs csR a)+ Stop ->+ -- XXX Skip to a state instead?+ feedRightOnly csR a+ YieldP ps b -> return $ YieldP (TeeMergeProduceL ps csR a) b+ SkipP ps -> return $ SkipP (TeeMergeProduceL ps csR a)++ -- XXX Adding additional consume states causes 4x regression in+ -- All.Data.Stream/o-1-space.pipesX4.tee benchmark (mapM 4 times).+ -- Commenting these two states makes it 4x faster. Need to investigate why.+ consume (TeeMergeConsumeOnlyL csL) a = do+ resL <- consumeL csL a+ return+ $ case resL of+ YieldC cs b -> YieldC (TeeMergeConsumeOnlyL cs) b+ SkipC cs -> SkipC (TeeMergeConsumeOnlyL cs)+ Stop -> Stop+ YieldP ps b -> YieldP (TeeMergeProduceOnlyL ps) b+ SkipP ps -> SkipP (TeeMergeProduceOnlyL ps)+ consume (TeeMergeConsumeOnlyR csR) a = feedRightOnly csR a++ {-# INLINE_LATE produce #-}+ produce (TeeMergeProduce csL csR a) = do+ res <- consumeR csR a+ return+ $ case res of+ YieldC cs b -> YieldC (TeeMergeConsume csL cs) b+ SkipC cs -> SkipC (TeeMergeConsume csL cs)+ Stop -> SkipC (TeeMergeConsumeOnlyL csL)+ YieldP ps b -> YieldP (TeeMergeProduceR csL ps) b+ SkipP ps -> SkipP (TeeMergeProduceR csL ps)++ produce (TeeMergeProduceL psL csR a) = do+ res <- produceL psL+ case res of+ YieldC cs b -> return $ YieldP (TeeMergeProduce cs csR a) b+ SkipC cs -> return $ SkipP (TeeMergeProduce cs csR a)+ Stop -> feedRightOnly csR a+ YieldP ps b -> return $ YieldP (TeeMergeProduceL ps csR a) b+ SkipP ps -> return $ SkipP (TeeMergeProduceL ps csR a)++ produce (TeeMergeProduceR csL psR) = do+ res <- produceR psR+ return $ case res of+ YieldC cs b -> YieldC (TeeMergeConsume csL cs) b+ SkipC cs -> SkipC (TeeMergeConsume csL cs)+ Stop -> SkipC (TeeMergeConsumeOnlyL csL)+ YieldP ps b -> YieldP (TeeMergeProduceR csL ps) b+ SkipP ps -> SkipP (TeeMergeProduceR csL ps)++ produce (TeeMergeProduceOnlyL psL) = do+ resL <- produceL psL+ return+ $ case resL of+ YieldC cs b -> YieldC (TeeMergeConsumeOnlyL cs) b+ SkipC cs -> SkipC (TeeMergeConsumeOnlyL cs)+ Stop -> Stop+ YieldP ps b -> YieldP (TeeMergeProduceOnlyL ps) b+ SkipP ps -> SkipP (TeeMergeProduceOnlyL ps)++ produce (TeeMergeProduceOnlyR psR) = do+ resL <- produceR psR+ return+ $ case resL of+ YieldC cs b -> YieldC (TeeMergeConsumeOnlyR cs) b+ SkipC cs -> SkipC (TeeMergeConsumeOnlyR cs)+ Stop -> Stop+ YieldP ps b -> YieldP (TeeMergeProduceOnlyR ps) b+ SkipP ps -> SkipP (TeeMergeProduceOnlyR ps)++-- | '<>' composes the pipes in parallel.+instance Monad m => Semigroup (Pipe m a b) where+ {-# INLINE (<>) #-}+ (<>) = teeMerge++-------------------------------------------------------------------------------+-- Arrow+-------------------------------------------------------------------------------++{-+unzip :: Pipe m a x -> Pipe m b y -> Pipe m (a, b) (x, y)+unzip = undefined+ -- XXX move this to a separate module data Deque a = Deque [a] [a] @@ -124,6 +436,8 @@ h : t -> Just (h, Deque [] t) _ -> Nothing +-- XXX This is old code retained for reference until rewritten.+ -- | The composed pipe distributes the input to both the constituent pipes and -- zips the output of the two using a supplied zipping function. --@@ -251,193 +565,164 @@ (<*>) = zipWith id --- | The composed pipe distributes the input to both the constituent pipes and--- merges the outputs of the two.------ @since 0.7.0-{-# INLINE_NORMAL tee #-}-tee :: Monad m => Pipe m a b -> Pipe m a b -> Pipe m a b-tee (Pipe consumeL produceL stateL) (Pipe consumeR produceR stateR) =- Pipe consume produce state+instance Monad m => Arrow (Pipe m) where+ {-# INLINE arr #-}+ arr = map++ {-# INLINE (***) #-}+ (***) = unzip++ {-# INLINE (&&&) #-}+ -- (&&&) = zipWith (,)+ (&&&) = undefined+-}++-------------------------------------------------------------------------------+-- Primitive pipes+-------------------------------------------------------------------------------++-- | A filtering pipe using a monadic predicate.+{-# INLINE filterM #-}+filterM :: Monad m => (a -> m Bool) -> Pipe m a a+filterM f = Pipe (\() a -> f a >>= g a) undefined ()+ where - state = Tuple' (Consume stateL) (Consume stateR)+ {-# INLINE g #-}+ g a b =+ return+ $ if b+ then YieldC () a+ else SkipC () - consume (Tuple' sL sR) a =- case sL of- Consume st -> do- r <- consumeL st a- return $ case r of- Yield x s -> Yield x (Produce (Tuple3' (Just a) s sR))- Continue s -> Continue (Produce (Tuple3' (Just a) s sR))- -- XXX we should never come here unless the initial state of the- -- first pipe is set to "Right".- Produce _st -> undefined -- do- {-- r <- produceL st- return $ case r of- Yield x s -> Yield x (Right (Tuple3' (Just a) s sR))- Continue s -> Continue (Right (Tuple3' (Just a) s sR))- -}+-- | A filtering pipe using a pure predicate.+--+-- >>> Stream.toList $ Stream.pipe (Pipe.filter odd) $ Stream.fromList [1..5::Int]+-- [1,3,5]+--+{-# INLINE filter #-}+filter :: Monad m => (a -> Bool) -> Pipe m a a+filter f = filterM (return Prelude.. f) - produce (Tuple3' (Just a) sL sR) =- case sL of- Consume _ ->- case sR of- Consume st -> do- r <- consumeR st a- let nextL s = Consume (Tuple' sL s)- let nextR s = Produce (Tuple3' Nothing sL s)- return $ case r of- Yield x s@(Consume _) -> Yield x (nextL s)- Yield x s@(Produce _) -> Yield x (nextR s)- Continue s@(Consume _) -> Continue (nextL s)- Continue s@(Produce _) -> Continue (nextR s)- -- We will never come here unless the initial state of- -- second pipe is set to "Right".- Produce _ -> undefined- Produce st -> do- r <- produceL st- let next s = Produce (Tuple3' (Just a) s sR)- return $ case r of- Yield x s -> Yield x (next s)- Continue s -> Continue (next s)+-------------------------------------------------------------------------------+-- Convert folds to pipes+------------------------------------------------------------------------------- - produce (Tuple3' Nothing sL sR) =- case sR of- Consume _ -> undefined -- should never occur- Produce st -> do- r <- produceR st- return $ case r of- Yield x s@(Consume _) ->- Yield x (Consume (Tuple' sL s))- Yield x s@(Produce _) ->- Yield x (Produce (Tuple3' Nothing sL s))- Continue s@(Consume _) ->- Continue (Consume (Tuple' sL s))- Continue s@(Produce _) ->- Continue (Produce (Tuple3' Nothing sL s))+-- Note when we have a separate Scan type then we can remove extract from+-- Folds. Then folds can only be used for foldMany or many and not for+-- scanning. This combinator has to be removed then. -instance Monad m => Semigroup (Pipe m a b) where- {-# INLINE (<>) #-}- (<>) = tee+-- XXX The way filter is implemented in Folds is that it discards the input and+-- on "extract" it will return the previous accumulator value only. Thus the+-- accumulator may repeat in the output stream when filter is used. Ideally the+-- output stream should not have a value corresponding to the filtered value.+-- With "Continue s" and "Partial s b" instead of using "extract" we can do+-- that. --- | Lift a pure function to a 'Pipe'.+{-# ANN type FromFoldConsume Fuse #-}+#if __GLASGOW_HASKELL__ >= 810+type FromFoldConsume :: Type -> Type -> Type+#endif+data FromFoldConsume s x = FoldConsumeInit | FoldConsumeGo s++{-# ANN type FromFoldProduce Fuse #-}+data FromFoldProduce s x = FoldProduceInit s x | FoldProduceStop++-- XXX This should be removed once we remove "extract" from folds.++-- | Pipes do not support finalization yet. This does not finalize the fold+-- when the stream stops before the fold terminates. So cannot be used on folds+-- that require finalization. ----- @since 0.7.0-{-# INLINE map #-}-map :: Monad m => (a -> b) -> Pipe m a b-map f = Pipe consume undefined ()+-- >>> Stream.toList $ Stream.pipe (Pipe.scanFold Fold.sum) $ Stream.fromList [1..5::Int]+-- [1,3,6,10,15]+--+{-# INLINE scanFold #-}+scanFold :: Monad m => Fold m a b -> Pipe m a b+scanFold (Fold fstep finitial fextract _) =+ Pipe consume produce FoldConsumeInit+ where- consume _ a = return $ Yield (f a) (Consume ()) -{---- | A hollow or identity 'Pipe' passes through everything that comes in.------ @since 0.7.0-{-# INLINE id #-}-id :: Monad m => Pipe m a a-id = map Prelude.id--}+ -- XXX make the initial state Either type and start in produce mode+ consume FoldConsumeInit x = do+ r <- finitial+ return $ case r of+ Fold.Partial s -> SkipP (FoldProduceInit s x)+ Fold.Done b -> YieldP FoldProduceStop b --- | Compose two pipes such that the output of the second pipe is attached to--- the input of the first pipe.+ consume (FoldConsumeGo st) a = do+ r <- fstep st a+ case r of+ Fold.Partial s -> do+ b <- fextract s+ return $ YieldC (FoldConsumeGo s) b+ Fold.Done b -> return $ YieldP FoldProduceStop b++ produce (FoldProduceInit st x) = consume (FoldConsumeGo st) x+ produce FoldProduceStop = return Stop++-- XXX The doctest for Pipe.fromFold fails with "[]" as the result.++-- | Create a singleton pipe from a fold. ----- @since 0.7.0-{-# INLINE_NORMAL compose #-}-compose :: Monad m => Pipe m b c -> Pipe m a b -> Pipe m a c-compose (Pipe consumeL produceL stateL) (Pipe consumeR produceR stateR) =- Pipe consume produce state+-- Pipes do not support finalization yet. This does not finalize the fold+-- when the stream stops before the fold terminates. So cannot be used on folds+-- that require such finalization.+--+-- >> Stream.toList $ Stream.pipe (Pipe.fromFold Fold.sum) $ Stream.fromList [1..5::Int]+-- [15]+--+{-# INLINE fromFold #-}+fromFold :: Monad m => Fold m a b -> Pipe m a b+fromFold (Fold fstep finitial _ _) =+ Pipe consume produce FoldConsumeInit where - state = Tuple' (Consume stateL) (Consume stateR)+ -- XXX make the initial state Either type and start in produce mode+ consume FoldConsumeInit x = do+ r <- finitial+ return $ case r of+ Fold.Partial s -> SkipP (FoldProduceInit s x)+ Fold.Done b -> YieldP FoldProduceStop b - consume (Tuple' sL sR) a =- case sL of- Consume stt ->- case sR of- Consume st -> do- rres <- consumeR st a- case rres of- Yield x sR' -> do- let next s =- if isProduce sR'- then Produce s- else Consume s- lres <- consumeL stt x- return $ case lres of- Yield y s1@(Consume _) ->- Yield y (next $ Tuple' s1 sR')- Yield y s1@(Produce _) ->- Yield y (Produce $ Tuple' s1 sR')- Continue s1@(Consume _) ->- Continue (next $ Tuple' s1 sR')- Continue s1@(Produce _) ->- Continue (Produce $ Tuple' s1 sR')- Continue s1@(Consume _) ->- return $ Continue (Consume $ Tuple' sL s1)- Continue s1@(Produce _) ->- return $ Continue (Produce $ Tuple' sL s1)- Produce _ -> undefined- -- XXX we should never come here unless the initial state of the- -- first pipe is set to "Right".- Produce _ -> undefined+ consume (FoldConsumeGo st) a = do+ r <- fstep st a+ return $ case r of+ Fold.Partial s -> SkipC (FoldConsumeGo s)+ Fold.Done b -> YieldP FoldProduceStop b - -- XXX we need to write the code in mor optimized fashion. Use Continue- -- more and less yield points.- produce (Tuple' sL sR) =- case sL of- Produce st -> do- r <- produceL st- let next s = if isProduce sR then Produce s else Consume s- return $ case r of- Yield x s@(Consume _) -> Yield x (next $ Tuple' s sR)- Yield x s@(Produce _) -> Yield x (Produce $ Tuple' s sR)- Continue s@(Consume _) -> Continue (next $ Tuple' s sR)- Continue s@(Produce _) -> Continue (Produce $ Tuple' s sR)- Consume stt ->- case sR of- Produce st -> do- rR <- produceR st- case rR of- Yield x sR' -> do- let next s =- if isProduce sR'- then Produce s- else Consume s- rL <- consumeL stt x- return $ case rL of- Yield y s1@(Consume _) ->- Yield y (next $ Tuple' s1 sR')- Yield y s1@(Produce _) ->- Yield y (Produce $ Tuple' s1 sR')- Continue s1@(Consume _) ->- Continue (next $ Tuple' s1 sR')- Continue s1@(Produce _) ->- Continue (Produce $ Tuple' s1 sR')- Continue s1@(Consume _) ->- return $ Continue (Consume $ Tuple' sL s1)- Continue s1@(Produce _) ->- return $ Continue (Produce $ Tuple' sL s1)- Consume _ -> return $ Continue (Consume $ Tuple' sL sR)+ produce (FoldProduceInit st x) = consume (FoldConsumeGo st) x+ produce FoldProduceStop = return Stop -instance Monad m => Category (Pipe m) where- {-# INLINE id #-}- id = map Prelude.id+-- | Produces the stream on consuming ().+--+{-# INLINE fromStream #-}+fromStream :: Monad m => Stream m a -> Pipe m () a+fromStream (Stream step state) = Pipe consume produce () - {-# INLINE (.) #-}- (.) = compose+ where -unzip :: Pipe m a x -> Pipe m b y -> Pipe m (a, b) (x, y)-unzip = undefined+ -- XXX make the initial state Either type and start in produce mode+ consume () () = return $ SkipP state -instance Monad m => Arrow (Pipe m) where- {-# INLINE arr #-}- arr = map+ produce st = do+ r <- step defState st+ return $ case r of+ Stream.Yield b s -> YieldP s b+ Stream.Skip s -> SkipP s+ Stream.Stop -> Stop - {-# INLINE (***) #-}- (***) = unzip+{-# INLINE fromScanr #-}+fromScanr :: Monad m => Scanr m a b -> Pipe m a b+fromScanr (Scanr step initial) = Pipe consume undefined initial - {-# INLINE (&&&) #-}- (&&&) = zipWith (,)+ where++ consume st a = do+ r <- step st a+ return $ case r of+ Stream.Yield b s -> YieldC s b+ Stream.Skip s -> SkipC s+ Stream.Stop -> Stop
src/Streamly/Internal/Data/Producer.hs view
@@ -24,31 +24,24 @@ -- unecessary function calls can be avoided. module Streamly.Internal.Data.Producer- ( Producer (..)+ (+ module Streamly.Internal.Data.Producer.Source+ , module Streamly.Internal.Data.Producer.Type -- * Converting , simplify-- -- * Producers- , nil- , nilM- , unfoldrM , fromStreamD- , fromList-- -- * Combinators- , NestedLoop (..)- , concat ) where #include "inline.hs" -import Streamly.Internal.Data.Stream.StreamD.Step (Step(..))-import Streamly.Internal.Data.Stream.StreamD.Type (Stream(..))+import Streamly.Internal.Data.Stream.Step (Step(..))+import Streamly.Internal.Data.Stream.Type (Stream(..)) import Streamly.Internal.Data.SVar.Type (defState) import Streamly.Internal.Data.Unfold.Type (Unfold(..)) +import Streamly.Internal.Data.Producer.Source import Streamly.Internal.Data.Producer.Type import Prelude hiding (concat)
src/Streamly/Internal/Data/Producer/Source.hs view
@@ -36,11 +36,12 @@ import Control.Exception (assert) import GHC.Exts (SpecConstrAnnotation(..)) import GHC.Types (SPEC(..))-import Streamly.Internal.Data.Parser.ParserD (ParseError(..), Step(..))+import Streamly.Internal.Data.Parser+ (ParseError(..), ParseErrorPos(..), Step(..), Final(..)) import Streamly.Internal.Data.Producer.Type (Producer(..))-import Streamly.Internal.Data.Stream.StreamD.Step (Step(..))+import Streamly.Internal.Data.Stream.Step (Step(..)) -import qualified Streamly.Internal.Data.Parser.ParserD as ParserD+import qualified Streamly.Internal.Data.Parser as ParserD -- import qualified Streamly.Internal.Data.Parser.ParserK.Type as ParserK import Prelude hiding (read)@@ -115,7 +116,7 @@ ParserD.Parser a m b -> Producer m (Source s a) a -> Source s a- -> m (Either ParseError b, Source s a)+ -> m (Either ParseErrorPos b, Source s a) parse (ParserD.Parser pstep initial extract) (Producer ustep uinject uextract)@@ -125,121 +126,144 @@ case res of ParserD.IPartial s -> do state <- uinject seed- go SPEC state (List []) s+ go SPEC state (List []) s 0 ParserD.IDone b -> return (Right b, seed)- ParserD.IError err -> return (Left (ParseError err), seed)+ ParserD.IError err -> return (Left (ParseErrorPos 0 err), seed) where -- XXX currently we are using a dumb list based approach for backtracking -- buffer. This can be replaced by a sliding/ring buffer using Data.Array. -- That will allow us more efficient random back and forth movement.- go !_ st buf !pst = do+ go !_ st buf !pst i = do r <- ustep st case r of Yield x s -> do pRes <- pstep pst x case pRes of- Partial 0 pst1 -> go SPEC s (List []) pst1- Partial n pst1 -> do+ SPartial 1 pst1 -> go SPEC s (List []) pst1 i+ SPartial m pst1 -> do+ let n = 1 - m assert (n <= length (x:getList buf)) (return ()) let src0 = Prelude.take n (x:getList buf) src = Prelude.reverse src0- gobuf SPEC s (List []) (List src) pst1- Continue 0 pst1 -> go SPEC s (List (x:getList buf)) pst1- Continue n pst1 -> do+ gobuf SPEC s (List []) (List src) pst1 (i + 1 - n)+ SContinue 1 pst1 -> go SPEC s (List (x:getList buf)) pst1 (i + 1)+ SContinue m pst1 -> do+ let n = 1 - m assert (n <= length (x:getList buf)) (return ()) let (src0, buf1) = splitAt n (x:getList buf) src = Prelude.reverse src0- gobuf SPEC s (List buf1) (List src) pst1- Done n b -> do+ gobuf SPEC s (List buf1) (List src) pst1 (i + 1 - n)+ SDone m b -> do+ let n = 1 - m assert (n <= length (x:getList buf)) (return ()) let src0 = Prelude.take n (x:getList buf) src = Prelude.reverse src0 s1 <- uextract s return (Right b, unread src s1)- Error err -> do+ SError err -> do s1 <- uextract s- return (Left (ParseError err), unread [x] s1)- Skip s -> go SPEC s buf pst- Stop -> goStop buf pst+ let src = Prelude.reverse (getList buf)+ return+ ( Left (ParseErrorPos (i + 1) err)+ , unread (src ++ [x]) s1+ )+ Skip s -> go SPEC s buf pst i+ Stop -> goStop buf pst i - gobuf !_ s buf (List []) !pst = go SPEC s buf pst- gobuf !_ s buf (List (x:xs)) !pst = do+ gobuf !_ s buf (List []) !pst i = go SPEC s buf pst i+ gobuf !_ s buf (List (x:xs)) !pst i = do pRes <- pstep pst x case pRes of- Partial 0 pst1 ->- gobuf SPEC s (List []) (List xs) pst1- Partial n pst1 -> do+ SPartial 1 pst1 ->+ gobuf SPEC s (List []) (List xs) pst1 (i + 1)+ SPartial m pst1 -> do+ let n = 1 - m assert (n <= length (x:getList buf)) (return ()) let src0 = Prelude.take n (x:getList buf) src = Prelude.reverse src0 ++ xs- gobuf SPEC s (List []) (List src) pst1- Continue 0 pst1 ->- gobuf SPEC s (List (x:getList buf)) (List xs) pst1- Continue n pst1 -> do+ gobuf SPEC s (List []) (List src) pst1 (i + 1 - n)+ SContinue 1 pst1 ->+ gobuf SPEC s (List (x:getList buf)) (List xs) pst1 (i + 1)+ SContinue m pst1 -> do+ let n = 1 - m assert (n <= length (x:getList buf)) (return ()) let (src0, buf1) = splitAt n (x:getList buf) src = Prelude.reverse src0 ++ xs- gobuf SPEC s (List buf1) (List src) pst1- Done n b -> do+ gobuf SPEC s (List buf1) (List src) pst1 (i + 1 - n)+ SDone m b -> do+ let n = 1 - m assert (n <= length (x:getList buf)) (return ()) let src0 = Prelude.take n (x:getList buf) src = Prelude.reverse src0 s1 <- uextract s return (Right b, unread src s1)- Error err -> do+ SError err -> do s1 <- uextract s- return (Left (ParseError err), unread (x:xs) s1)+ let src = Prelude.reverse (getList buf)+ return+ ( Left (ParseErrorPos (i + 1) err)+ , unread (src ++ (x:xs)) s1+ ) -- This is a simplified gobuf- goExtract !_ buf (List []) !pst = goStop buf pst- goExtract !_ buf (List (x:xs)) !pst = do+ goExtract !_ buf (List []) !pst i = goStop buf pst i+ goExtract !_ buf (List (x:xs)) !pst i = do pRes <- pstep pst x case pRes of- Partial 0 pst1 ->- goExtract SPEC (List []) (List xs) pst1- Partial n pst1 -> do+ SPartial 1 pst1 ->+ goExtract SPEC (List []) (List xs) pst1 (i + 1)+ SPartial m pst1 -> do+ let n = 1 - m assert (n <= length (x:getList buf)) (return ()) let src0 = Prelude.take n (x:getList buf) src = Prelude.reverse src0 ++ xs- goExtract SPEC (List []) (List src) pst1- Continue 0 pst1 ->- goExtract SPEC (List (x:getList buf)) (List xs) pst1- Continue n pst1 -> do+ goExtract SPEC (List []) (List src) pst1 (i + 1 - n)+ SContinue 1 pst1 ->+ goExtract SPEC (List (x:getList buf)) (List xs) pst1 (i + 1)+ SContinue m pst1 -> do+ let n = 1 - m assert (n <= length (x:getList buf)) (return ()) let (src0, buf1) = splitAt n (x:getList buf) src = Prelude.reverse src0 ++ xs- goExtract SPEC (List buf1) (List src) pst1- Done n b -> do+ goExtract SPEC (List buf1) (List src) pst1 (i + 1 - n)+ SDone m b -> do+ let n = 1 - m assert (n <= length (x:getList buf)) (return ()) let src0 = Prelude.take n (x:getList buf) src = Prelude.reverse src0 return (Right b, unread src (source Nothing))- Error err ->- return (Left (ParseError err), unread (x:xs) (source Nothing))+ SError err -> do+ let src = Prelude.reverse (getList buf)+ return+ ( Left (ParseErrorPos (i + 1) err)+ , unread (src ++ (x:xs)) (source Nothing)+ ) -- This is a simplified goExtract {-# INLINE goStop #-}- goStop buf pst = do+ goStop buf pst i = do pRes <- extract pst case pRes of- Partial _ _ -> error "Bug: parseD: Partial in extract"- Continue 0 pst1 ->- goStop buf pst1- Continue n pst1 -> do+ FContinue 0 pst1 ->+ goStop buf pst1 i+ FContinue m pst1 -> do+ let n = (- m) assert (n <= length (getList buf)) (return ()) let (src0, buf1) = splitAt n (getList buf) src = Prelude.reverse src0- goExtract SPEC (List buf1) (List src) pst1- Done 0 b -> return (Right b, source Nothing)- Done n b -> do+ goExtract SPEC (List buf1) (List src) pst1 (i - n)+ FDone 0 b -> return (Right b, source Nothing)+ FDone m b -> do+ let n = (- m) assert (n <= length (getList buf)) (return ()) let src0 = Prelude.take n (getList buf) src = Prelude.reverse src0 return (Right b, unread src (source Nothing))- Error err ->- return (Left (ParseError err), source Nothing)+ FError err -> do+ let src = Prelude.reverse (getList buf)+ return (Left (ParseErrorPos i err), unread src (source Nothing)) {- -- | Parse a buffered source using a parser, returning the parsed value and the
src/Streamly/Internal/Data/Producer/Type.hs view
@@ -33,12 +33,16 @@ #include "inline.hs" import Fusion.Plugin.Types (Fuse(..))-import Streamly.Internal.Data.Stream.StreamD.Step (Step(..))+import Streamly.Internal.Data.Stream.Step (Step(..)) import Prelude hiding (concat, map) ------------------------------------------------------------------------------ -- Type ------------------------------------------------------------------------------++-- Note that this type cannot be made a Functor on the seed/result type because+-- that requires bi-directional mapping between the two types, see translate+-- and lmap below. -- | A @Producer m a b@ is a generator of a stream of values of type @b@ from a -- seed of type 'a' in 'Monad' @m@.
src/Streamly/Internal/Data/Refold/Type.hs view
@@ -47,12 +47,12 @@ import Fusion.Plugin.Types (Fuse(..)) import Streamly.Internal.Data.Fold.Step (Step(..), mapMStep) -import Prelude hiding (take, iterate)+import Prelude hiding (Foldable(..), take, iterate) -- $setup -- >>> :m -- >>> import qualified Streamly.Internal.Data.Refold.Type as Refold--- >>> import qualified Streamly.Internal.Data.Fold.Type as Fold+-- >>> import qualified Streamly.Internal.Data.Fold as Fold -- >>> import qualified Streamly.Internal.Data.Stream as Stream -- All folds in the Fold module should be implemented using Refolds.
− src/Streamly/Internal/Data/Ring.hs
@@ -1,164 +0,0 @@--- |--- Module : Streamly.Internal.Data.Ring--- Copyright : (c) 2021 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC-----module Streamly.Internal.Data.Ring- ( Ring(..)-- -- * Generation- , createRing- , writeLastN-- -- * Modification- , seek- , unsafeInsertRingWith-- -- * Conversion- , toMutArray- , toStreamWith- ) where--#include "assert.hs"--import Control.Monad.IO.Class (liftIO, MonadIO)-import Streamly.Internal.Data.Stream.StreamD.Type (Stream)-import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))-import Streamly.Internal.Data.Fold.Type (Fold(..))-import Streamly.Internal.Data.Array.Generic.Mut.Type- ( MutArray(..)- , new- , uninit- , putIndexUnsafe- , putSliceUnsafe- )--- import qualified Streamly.Internal.Data.Stream.StreamD.Type as Stream-import qualified Streamly.Internal.Data.Fold.Type as Fold---- XXX Use MutableArray rather than keeping a MutArray here.-data Ring a = Ring- { ringArr :: MutArray a- -- XXX We can keep the current fill amount, Or we can keep a count of total- -- elements inserted and compute ring head as well using mod on that,- -- assuming it won't overflow. But mod could be expensive.- , ringHead :: !Int -- current index to be over-written- , ringMax :: !Int -- first index beyond allocated memory- }------------------------------------------------------------------------------------ Generation------------------------------------------------------------------------------------ XXX If we align the ringMax to nearest power of two then computation of the--- index to write could be cheaper.-{-# INLINE createRing #-}-createRing :: MonadIO m => Int -> m (Ring a)-createRing count = liftIO $ do- arr <- new count- arr1 <- uninit arr count- return (Ring- { ringArr = arr1- , ringHead = 0- , ringMax = count- })---{-# INLINE writeLastN #-}-writeLastN :: MonadIO m => Int -> Fold m a (Ring a)-writeLastN n = Fold step initial extract-- where-- initial = do- if n <= 0- then Fold.Done <$> createRing 0- else do- rb <- createRing n- return $ Fold.Partial $ Tuple' rb (0 :: Int)-- step (Tuple' rb cnt) x = do- rh1 <- liftIO $ unsafeInsertRingWith rb x- return $ Fold.Partial $ Tuple' (rb {ringHead = rh1}) (cnt + 1)-- extract (Tuple' rb@Ring{..} cnt) =- return $- if cnt < ringMax- then Ring ringArr 0 ringHead- else rb------------------------------------------------------------------------------------ Modification------------------------------------------------------------------------------------ XXX This is safe--- Take the ring head and return the new ring head.-{-# INLINE unsafeInsertRingWith #-}-unsafeInsertRingWith :: Ring a -> a -> IO Int-unsafeInsertRingWith Ring{..} x = do- assertM(ringMax >= 1)- assertM(ringHead < ringMax)- putIndexUnsafe ringHead ringArr x- let rh1 = ringHead + 1- next = if rh1 == ringMax then 0 else rh1- return next---- | Move the ring head clockwise (+ve adj) or counter clockwise (-ve adj) by--- the given amount.-{-# INLINE seek #-}-seek :: MonadIO m => Int -> Ring a -> m (Ring a)-seek adj rng@Ring{..}- | ringMax > 0 = liftIO $ do- -- XXX try avoiding mod when in bounds- let idx1 = ringHead + adj- next = mod idx1 ringMax- return $ Ring ringArr next ringMax- | otherwise = pure rng------------------------------------------------------------------------------------ Conversion------------------------------------------------------------------------------------ | @toMutArray rignHeadAdjustment lengthToRead ring@.--- Convert the ring into a boxed mutable array. Note that the returned MutArray--- may share the same underlying memory as the Ring.-{-# INLINE toMutArray #-}-toMutArray :: MonadIO m => Int -> Int -> Ring a -> m (MutArray a)-toMutArray adj n Ring{..} = do- let len = min ringMax n- let idx = mod (ringHead + adj) ringMax- end = idx + len- if end <= ringMax- then- -- putSliceUnsafe ringArr idx arr1 0 len- return $ ringArr { arrStart = idx, arrLen = len }- else do- -- XXX Just swap the elements in the existing ring and return the- -- same array without reallocation.- arr <- liftIO $ new len- arr1 <- uninit arr len- putSliceUnsafe ringArr idx arr1 0 (ringMax - idx)- putSliceUnsafe ringArr 0 arr1 (ringMax - idx) (end - ringMax)- return arr1---- This would be theoretically slower than toMutArray because of a branch--- introduced for each element in the second half of the ring.---- | Seek by n and then read the entire ring. Use 'take' on the stream to--- restrict the reads.-toStreamWith :: Int -> Ring a -> Stream m a-toStreamWith = undefined-{--toStreamWith n Ring{..}- | ringMax > 0 = concatEffect $ liftIO $ do- idx <- readIORef ringHead- let idx1 = idx + adj- next = mod idx1 ringMax- s1 = undefined -- stream initial slice- s2 = undefined -- stream next slice- return (s1 `Stream.append` s2)- | otherwise = Stream.nil--}
− src/Streamly/Internal/Data/Ring/Unboxed.hs
@@ -1,615 +0,0 @@--- |--- Module : Streamly.Internal.Data.Ring.Unboxed--- Copyright : (c) 2019 Composewell Technologies--- License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ A ring array is a circular mutable array.---- XXX Write benchmarks--- XXX Make the implementation similar to mutable array--- XXX Rename this module to Data.RingArray.Storable--module Streamly.Internal.Data.Ring.Unboxed- ( Ring(..)-- -- * Construction- , new- , newRing- , writeN-- , advance- , moveBy- , startOf-- -- * Random writes- , unsafeInsert- , slide- , putIndex- , modifyIndex-- -- * Unfolds- , read- , readRev-- -- * Random reads- , getIndex- , getIndexUnsafe- , getIndexRev-- -- * Size- , length- , byteLength- -- , capacity- , byteCapacity- , bytesFree-- -- * Casting- , cast- , castUnsafe- , asBytes- , fromArray-- -- * Folds- , unsafeFoldRing- , unsafeFoldRingM- , unsafeFoldRingFullM- , unsafeFoldRingNM-- -- * Stream of Arrays- , ringsOf-- -- * Fast Byte Comparisons- , unsafeEqArray- , unsafeEqArrayN-- , slidingWindow- , slidingWindowWith- ) where--#include "ArrayMacros.h"-#include "inline.hs"--import Control.Exception (assert)-import Control.Monad.IO.Class (MonadIO(..))-import Data.Word (Word8)-import Foreign.Storable-import Foreign.ForeignPtr (ForeignPtr, withForeignPtr, touchForeignPtr)-import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)-import Foreign.Ptr (plusPtr, minusPtr, castPtr)-import Streamly.Internal.Data.Unboxed as Unboxed (Unbox, peekWith)-import GHC.ForeignPtr (mallocPlainForeignPtrAlignedBytes)-import GHC.Ptr (Ptr(..))-import Streamly.Internal.Data.Array.Mut.Type (MutArray)-import Streamly.Internal.Data.Fold.Type (Fold(..), Step(..), lmap)-import Streamly.Internal.Data.Stream.StreamD.Type (Stream)-import Streamly.Internal.Data.Stream.StreamD.Step (Step(..))-import Streamly.Internal.Data.Unfold.Type (Unfold(..))-import Streamly.Internal.System.IO (unsafeInlineIO)--import qualified Streamly.Internal.Data.Array.Mut.Type as MA-import qualified Streamly.Internal.Data.Array.Type as A--import Prelude hiding (length, concat, read)---- $setup--- >>> :m--- >>> import qualified Streamly.Internal.Data.Ring.Unboxed as Ring---- | A ring buffer is a mutable array of fixed size. Initially the array is--- empty, with ringStart pointing at the start of allocated memory. We call the--- next location to be written in the ring as ringHead. Initially ringHead ==--- ringStart. When the first item is added, ringHead points to ringStart +--- sizeof item. When the buffer becomes full ringHead would wrap around to--- ringStart. When the buffer is full, ringHead always points at the oldest--- item in the ring and the newest item added always overwrites the oldest--- item.------ When using it we should keep in mind that a ringBuffer is a mutable data--- structure. We should not leak out references to it for immutable use.----data Ring a = Ring- { ringStart :: {-# UNPACK #-} !(ForeignPtr a) -- first address- , ringBound :: {-# UNPACK #-} !(Ptr a) -- first address beyond allocated memory- }------------------------------------------------------------------------------------ Construction------------------------------------------------------------------------------------ | Get the first address of the ring as a pointer.-startOf :: Ring a -> Ptr a-startOf = unsafeForeignPtrToPtr . ringStart---- | Create a new ringbuffer and return the ring buffer and the ringHead.--- Returns the ring and the ringHead, the ringHead is same as ringStart.-{-# INLINE new #-}-new :: forall a. Storable a => Int -> IO (Ring a, Ptr a)-new count = do- let size = count * max 1 (sizeOf (undefined :: a))- fptr <- mallocPlainForeignPtrAlignedBytes size (alignment (undefined :: a))- let p = unsafeForeignPtrToPtr fptr- return (Ring- { ringStart = fptr- , ringBound = p `plusPtr` size- }, p)---- XXX Rename this to "new".------ | @newRing count@ allocates an empty array that can hold 'count' items. The--- memory of the array is uninitialized and the allocation is aligned as per--- the 'Storable' instance of the type.------ /Unimplemented/-{-# INLINE newRing #-}-newRing :: Int -> m (Ring a)-newRing = undefined---- | Advance the ringHead by 1 item, wrap around if we hit the end of the--- array.-{-# INLINE advance #-}-advance :: forall a. Storable a => Ring a -> Ptr a -> Ptr a-advance Ring{..} ringHead =- let ptr = PTR_NEXT(ringHead,a)- in if ptr < ringBound- then ptr- else unsafeForeignPtrToPtr ringStart---- | Move the ringHead by n items. The direction depends on the sign on whether--- n is positive or negative. Wrap around if we hit the beginning or end of the--- array.-{-# INLINE moveBy #-}-moveBy :: forall a. Storable a => Int -> Ring a -> Ptr a -> Ptr a-moveBy by Ring {..} ringHead = ringStartPtr `plusPtr` advanceFromHead-- where-- elemSize = STORABLE_SIZE_OF(a)- ringStartPtr = unsafeForeignPtrToPtr ringStart- lenInBytes = ringBound `minusPtr` ringStartPtr- offInBytes = ringHead `minusPtr` ringStartPtr- len = assert (lenInBytes `mod` elemSize == 0) $ lenInBytes `div` elemSize- off = assert (offInBytes `mod` elemSize == 0) $ offInBytes `div` elemSize- advanceFromHead = (off + by `mod` len) * elemSize---- XXX Move the writeLastN from array module here.------ | @writeN n@ is a rolling fold that keeps the last n elements of the stream--- in a ring array.------ /Unimplemented/-{-# INLINE writeN #-}-writeN :: -- (Storable a, MonadIO m) =>- Int -> Fold m a (Ring a)-writeN = undefined------------------------------------------------------------------------------------ Conversions------------------------------------------------------------------------------------ | Cast a mutable array to a ring array.-fromArray :: MutArray a -> Ring a-fromArray = undefined------------------------------------------------------------------------------------ Conversion to/from array------------------------------------------------------------------------------------ | Modify a given index of a ring array using a modifier function.------ /Unimplemented/-modifyIndex :: -- forall m a b. (MonadIO m, Storable a) =>- Ring a -> Int -> (a -> (a, b)) -> m b-modifyIndex = undefined---- | /O(1)/ Write the given element at the given index in the ring array.--- Performs in-place mutation of the array.------ >>> putIndex arr ix val = Ring.modifyIndex arr ix (const (val, ()))------ /Unimplemented/-{-# INLINE putIndex #-}-putIndex :: -- (MonadIO m, Storable a) =>- Ring a -> Int -> a -> m ()-putIndex = undefined---- | Insert an item at the head of the ring, when the ring is full this--- replaces the oldest item in the ring with the new item. This is unsafe--- beause ringHead supplied is not verified to be within the Ring. Also,--- the ringStart foreignPtr must be guaranteed to be alive by the caller.-{-# INLINE unsafeInsert #-}-unsafeInsert :: Storable a => Ring a -> Ptr a -> a -> IO (Ptr a)-unsafeInsert rb ringHead newVal = do- poke ringHead newVal- -- touchForeignPtr (ringStart rb)- return $ advance rb ringHead---- | Insert an item at the head of the ring, when the ring is full this--- replaces the oldest item in the ring with the new item.------ /Unimplemented/-slide :: -- forall m a. (MonadIO m, Storable a) =>- Ring a -> a -> m (Ring a)-slide = undefined------------------------------------------------------------------------------------ Random reads------------------------------------------------------------------------------------ | Return the element at the specified index without checking the bounds.------ Unsafe because it does not check the bounds of the ring array.-{-# INLINE_NORMAL getIndexUnsafe #-}-getIndexUnsafe :: -- forall m a. (MonadIO m, Storable a) =>- Ring a -> Int -> m a-getIndexUnsafe = undefined---- | /O(1)/ Lookup the element at the given index. Index starts from 0.----{-# INLINE getIndex #-}-getIndex :: -- (MonadIO m, Storable a) =>- Ring a -> Int -> m a-getIndex = undefined---- | /O(1)/ Lookup the element at the given index from the end of the array.--- Index starts from 0.------ Slightly faster than computing the forward index and using getIndex.----{-# INLINE getIndexRev #-}-getIndexRev :: -- (MonadIO m, Storable a) =>- Ring a -> Int -> m a-getIndexRev = undefined------------------------------------------------------------------------------------ Size------------------------------------------------------------------------------------ | /O(1)/ Get the byte length of the array.------ /Unimplemented/-{-# INLINE byteLength #-}-byteLength :: Ring a -> Int-byteLength = undefined---- | /O(1)/ Get the length of the array i.e. the number of elements in the--- array.------ Note that 'byteLength' is less expensive than this operation, as 'length'--- involves a costly division operation.------ /Unimplemented/-{-# INLINE length #-}-length :: -- forall a. Storable a =>- Ring a -> Int-length = undefined---- | Get the total capacity of an array. An array may have space reserved--- beyond the current used length of the array.------ /Pre-release/-{-# INLINE byteCapacity #-}-byteCapacity :: Ring a -> Int-byteCapacity = undefined---- | The remaining capacity in the array for appending more elements without--- reallocation.------ /Pre-release/-{-# INLINE bytesFree #-}-bytesFree :: Ring a -> Int-bytesFree = undefined------------------------------------------------------------------------------------ Unfolds------------------------------------------------------------------------------------ XXX We can read the ring in a loop and use "take" to restrict the number of--- elements to be taken.------ | Read n elements from the ring starting at the supplied ring head. If n is--- more than the ring size it keeps reading the ring in a circular fashion.------ If the ring is not full the user must ensure than n is less than or equal to--- the number of valid elements in the ring.------ /Internal/-{-# INLINE_NORMAL read #-}-read :: forall m a. (MonadIO m, Storable a) => Unfold m (Ring a, Ptr a, Int) a-read = Unfold step return-- where-- step (rb, rh, n) = do- if n <= 0- then do- liftIO $ touchForeignPtr (ringStart rb)- return Stop- else do- x <- liftIO $ peek rh- let rh1 = advance rb rh- return $ Yield x (rb, rh1, n - 1)---- | Unfold a ring array into a stream in reverse order.------ /Unimplemented/-{-# INLINE_NORMAL readRev #-}-readRev :: -- forall m a. (MonadIO m, Storable a) =>- Unfold m (MutArray a) a-readRev = undefined------------------------------------------------------------------------------------ Stream of arrays------------------------------------------------------------------------------------ XXX Move this module to a lower level Ring/Type module and move ringsOf to a--- higher level ring module where we can import "scan".---- | @ringsOf n stream@ groups the input stream into a stream of--- ring arrays of size n. Each ring is a sliding window of size n.------ /Unimplemented/-{-# INLINE_NORMAL ringsOf #-}-ringsOf :: -- forall m a. (MonadIO m, Storable a) =>- Int -> Stream m a -> Stream m (MutArray a)-ringsOf = undefined -- Stream.scan (writeN n)------------------------------------------------------------------------------------ Casting------------------------------------------------------------------------------------ | Cast an array having elements of type @a@ into an array having elements of--- type @b@. The array size must be a multiple of the size of type @b@.------ /Unimplemented/----castUnsafe :: Ring a -> Ring b-castUnsafe = undefined---- | Cast an @Array a@ into an @Array Word8@.------ /Unimplemented/----asBytes :: Ring a -> Ring Word8-asBytes = castUnsafe---- | Cast an array having elements of type @a@ into an array having elements of--- type @b@. The length of the array should be a multiple of the size of the--- target element otherwise 'Nothing' is returned.------ /Pre-release/----cast :: forall a b. Storable b => Ring a -> Maybe (Ring b)-cast arr =- let len = byteLength arr- r = len `mod` STORABLE_SIZE_OF(b)- in if r /= 0- then Nothing- else Just $ castUnsafe arr------------------------------------------------------------------------------------ Equality------------------------------------------------------------------------------------ XXX remove all usage of unsafeInlineIO------ | Like 'unsafeEqArray' but compares only N bytes instead of entire length of--- the ring buffer. This is unsafe because the ringHead Ptr is not checked to--- be in range.-{-# INLINE unsafeEqArrayN #-}-unsafeEqArrayN :: Ring a -> Ptr a -> A.Array a -> Int -> Bool-unsafeEqArrayN Ring{..} rh A.Array{..} nBytes- | nBytes < 0 = error "unsafeEqArrayN: n should be >= 0"- | nBytes == 0 = True- | otherwise = unsafeInlineIO $ check (castPtr rh) 0-- where-- w8Contents = arrContents-- check p i = do- (relem :: Word8) <- peek p- aelem <- peekWith w8Contents i- if relem == aelem- then go (p `plusPtr` 1) (i + 1)- else return False-- go p i- | i == nBytes = return True- | castPtr p == ringBound =- go (castPtr (unsafeForeignPtrToPtr ringStart)) i- | castPtr p == rh = touchForeignPtr ringStart >> return True- | otherwise = check p i---- XXX This is not modular. We should probably just convert the array and the--- ring buffer to streams and compare the two streams. Need to check perf--- though.---- | Byte compare the entire length of ringBuffer with the given array,--- starting at the supplied ringHead pointer. Returns true if the Array and--- the ringBuffer have identical contents.------ This is unsafe because the ringHead Ptr is not checked to be in range. The--- supplied array must be equal to or bigger than the ringBuffer, ARRAY BOUNDS--- ARE NOT CHECKED.-{-# INLINE unsafeEqArray #-}-unsafeEqArray :: Ring a -> Ptr a -> A.Array a -> Bool-unsafeEqArray Ring{..} rh A.Array{..} =- unsafeInlineIO $ check (castPtr rh) 0-- where-- w8Contents = arrContents-- check p i = do- (relem :: Word8) <- peek p- aelem <- peekWith w8Contents i- if relem == aelem- then go (p `plusPtr` 1) (i + 1)- else return False-- go p i- | castPtr p ==- ringBound = go (castPtr (unsafeForeignPtrToPtr ringStart)) i- | castPtr p == rh = touchForeignPtr ringStart >> return True- | otherwise = check p i------------------------------------------------------------------------------------ Folding------------------------------------------------------------------------------------ XXX We can unfold it into a stream and fold the stream instead.--- XXX use MonadIO------ | Fold the buffer starting from ringStart up to the given 'Ptr' using a pure--- step function. This is useful to fold the items in the ring when the ring is--- not full. The supplied pointer is usually the end of the ring.------ Unsafe because the supplied Ptr is not checked to be in range.-{-# INLINE unsafeFoldRing #-}-unsafeFoldRing :: forall a b. Storable a- => Ptr a -> (b -> a -> b) -> b -> Ring a -> b-unsafeFoldRing ptr f z Ring{..} =- let !res = unsafeInlineIO $ withForeignPtr ringStart $ \p ->- go z p ptr- in res- where- go !acc !p !q- | p == q = return acc- | otherwise = do- x <- peek p- go (f acc x) (PTR_NEXT(p,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. (MonadIO m, Storable a)- => Ptr a -> (b -> a -> m b) -> b -> Ring a -> m b-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 = unsafeInlineIO $ peek start- acc1 <- f acc x- go acc1 (PTR_NEXT(start,a)) end---- | Fold the entire length of a ring buffer starting at the supplied ringHead--- pointer. Assuming the supplied ringHead pointer points to the oldest item,--- this would fold the ring starting from the oldest item to the newest item in--- the ring.------ Note, this will crash on ring of 0 size.----{-# INLINE unsafeFoldRingFullM #-}-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 {..} =- withForeignPtrM ringStart $ \_ -> go z rh- where- go !acc !start = do- let !x = unsafeInlineIO $ peek start- acc' <- f acc x- let ptr = advance rb start- if ptr == rh- then return acc'- else go acc' ptr---- | Fold @Int@ items in the ring starting at @Ptr a@. Won't fold more--- than the length of the ring.------ Note, this will crash on ring of 0 size.----{-# INLINE unsafeFoldRingNM #-}-unsafeFoldRingNM :: forall m a b. (MonadIO m, Storable a)- => Int -> Ptr a -> (b -> a -> m b) -> b -> Ring a -> m b-unsafeFoldRingNM count rh f z rb@Ring {..} =- withForeignPtrM ringStart $ \_ -> go count z rh-- where-- go 0 acc _ = return acc- go !n !acc !start = do- let !x = unsafeInlineIO $ peek start- acc' <- f acc x- let ptr = advance rb start- if ptr == rh || n == 0- then return acc'- else go (n - 1) acc' ptr--data Tuple4' a b c d = Tuple4' !a !b !c !d deriving Show---- | Like slidingWindow but also provides the entire ring contents as an Array.--- The array reflects the state of the ring after inserting the incoming--- element.------ IMPORTANT NOTE: The ring is mutable, therefore, the result of @(m (Array--- a))@ action depends on when it is executed. It does not capture the sanpshot--- of the ring at a particular time.-{-# INLINE slidingWindowWith #-}-slidingWindowWith :: forall m a b. (MonadIO m, Storable a, Unbox a)- => Int -> Fold m ((a, Maybe a), m (MutArray a)) b -> Fold m a b-slidingWindowWith n (Fold step1 initial1 extract1) = Fold step initial extract-- where-- initial = do- if n <= 0- then error "Window size must be > 0"- else do- r <- initial1- (rb, rh) <- liftIO $ new n- return $- case r of- Partial s -> Partial $ Tuple4' rb rh (0 :: Int) s- Done b -> Done b-- toArray foldRing rb rh = do- arr <- liftIO $ MA.newPinned n- let snoc' b a = liftIO $ MA.snocUnsafe b a- foldRing rh snoc' arr rb-- step (Tuple4' rb rh i st) a- | i < n = do- rh1 <- liftIO $ unsafeInsert rb rh a- liftIO $ touchForeignPtr (ringStart rb)- let action = toArray unsafeFoldRingM rb (PTR_NEXT(rh, a))- r <- step1 st ((a, Nothing), action)- return $- case r of- Partial s -> Partial $ Tuple4' rb rh1 (i + 1) s- Done b -> Done b- | otherwise = do- old <- liftIO $ peek rh- rh1 <- liftIO $ unsafeInsert rb rh a- liftIO $ touchForeignPtr (ringStart rb)- r <- step1 st ((a, Just old), toArray unsafeFoldRingFullM rb rh1)- return $- case r of- Partial s -> Partial $ Tuple4' rb rh1 (i + 1) s- Done b -> Done b-- extract (Tuple4' _ _ _ st) = extract1 st---- | @slidingWindow collector@ is an incremental sliding window--- fold that does not require all the intermediate elements in a computation.--- This maintains @n@ elements in the window, when a new element comes it slides--- out the oldest element and the new element along with the old element are--- supplied to the collector fold.------ The 'Maybe' type is for the case when initially the window is filling and--- there is no old element.----{-# INLINE slidingWindow #-}-slidingWindow :: forall m a b. (MonadIO m, Storable a, Unbox a)- => Int -> Fold m (a, Maybe a) b -> Fold m a b-slidingWindow n f = slidingWindowWith n (lmap fst f)
+ src/Streamly/Internal/Data/RingArray.hs view
@@ -0,0 +1,963 @@+-- |+-- Module : Streamly.Internal.Data.RingArray+-- Copyright : (c) 2019 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- Unboxed, mutable ring arrays of fixed size. In case you need to expand the+-- size of a ring, copy it to a MutArray, expand the array and cast it back to+-- ring.++-- XXX Write benchmarks++module Streamly.Internal.Data.RingArray+ ( RingArray (..)+ , Ring++ -- * Debugging+ , showRing++ -- * Construction+ , createOfLast+ , castMutArray+ , castMutArrayWith+ , unsafeCastMutArray+ , unsafeCastMutArrayWith++ -- * Moving the Head+ , moveForward+ , moveReverse+ , moveBy++ -- * In-place Mutation+ , insert+ , replace+ , replace_+ , putIndex+ , modifyIndex++ -- * Random Access+ , getIndex+ , unsafeGetIndex+ , unsafeGetHead++ -- * Conversion+ , toList+ , toMutArray++ -- * Streams+ , read+ , readRev++ -- * Unfolds+ , reader+ , readerRev++ -- * Size+ , length+ , byteLength++ -- * Casting+ , cast+ , unsafeCast+ , asBytes+ , asMutArray+ , asMutArray_++ -- * Folds+ , foldlM'+ , fold++ -- * Stream of Rings+ , ringsOf+ , scanRingsOf+ , scanCustomFoldRingsBy+ , scanFoldRingsBy++ -- * Fast Byte Comparisons+ , eqArray+ , eqArrayN++ -- * Deprecated+ , unsafeFoldRing+ , unsafeFoldRingM+ , unsafeFoldRingNM+ , unsafeFoldRingFullM+ , slidingWindow+ , slidingWindowWith+ ) where++#include "ArrayMacros.h"+#include "inline.hs"++import Control.Monad (when)+import Control.Monad.IO.Class (MonadIO(..))+import Data.Proxy (Proxy(..))+import Data.Word (Word8)+import Fusion.Plugin.Types (Fuse(..))+import GHC.Types (SPEC(..))+import Streamly.Internal.Data.Array.Type (Array)+import Streamly.Internal.Data.MutArray.Type (MutArray(..))+import Streamly.Internal.Data.MutByteArray.Type (MutByteArray)+import Streamly.Internal.Data.Fold.Type (Fold(..), Step(..), lmap)+import Streamly.Internal.Data.Scanl.Type (Scanl(..))+import Streamly.Internal.Data.Stream.Step (Step(..))+import Streamly.Internal.Data.Stream.Type (Stream)+import Streamly.Internal.Data.Tuple.Strict (Tuple3Fused'(..))+import Streamly.Internal.Data.Unbox (Unbox(..))+import Streamly.Internal.Data.Unfold.Type (Unfold(..))++import qualified Streamly.Internal.Data.Array.Type as Array+import qualified Streamly.Internal.Data.Fold.Type as Fold+import qualified Streamly.Internal.Data.MutArray.Type as MutArray+import qualified Streamly.Internal.Data.MutByteArray.Type as MutByteArray+import qualified Streamly.Internal.Data.Scanl.Type as Scanl+import qualified Streamly.Internal.Data.Stream.Transform as Stream+import qualified Streamly.Internal.Data.Stream.Type as Stream+-- import qualified Streamly.Internal.Data.Unfold as Unfold+-- XXX check split benchmarks++import Prelude hiding (length, concat, read)++-- $setup+-- >>> :m+-- >>> import qualified Streamly.Internal.Data.Fold as Fold+-- >>> import qualified Streamly.Internal.Data.MutArray as MutArray+-- >>> import qualified Streamly.Internal.Data.RingArray as RingArray+-- >>> import qualified Streamly.Internal.Data.Stream as Stream++-- XXX Need a feature in GHC to disable positional constructors for record+-- types, so that we can safely reorder the fields.+--+-- Empty (zero-sized) rings are not allowed in construction routines though the+-- code supports it. We can allow it if there is a compelling use case.+--+-- We could represent a ring as a tuple of array and ring head (MutArray a,+-- Int). The array never changes, only the head does so the array can be passed+-- as a constant in a loop.+--+-- Performance notes: Replacing the oldest item with the newest is a very+-- common operation, during this operation the only thing that changes is the+-- ring head. Updating the RingArray constructor because of that could be expensive,+-- therefore, either the RingArray constructor should be eliminated via fusion or we+-- should unbox it manually where needed to allow for only the head to change.++-- | A ring buffer is a circular buffer. A new element is inserted at a+-- position called the ring head which points to the oldest element in the+-- ring, an insert overwrites the oldest element. After inserting, the head is+-- moved to point to the next element which is now the oldest element.+--+-- Elements in the ring are indexed relative to the head. RingArray head is+-- designated as the index 0 of the ring buffer, it points to the oldest or the+-- first element in the buffer. Higher positive indices point to the newer+-- elements in the buffer. Index @-1@ points to the newest or the last element+-- in the buffer. Higher negative indices point to older elements.+--+-- The ring is of fixed size and cannot be expanded or reduced after creation.+-- Creation of zero sized rings is not allowed.+--+-- This module provides an unboxed implementation of ring buffers for best+-- performance.+--+data RingArray a = RingArray+ { ringContents :: {-# UNPACK #-} !MutByteArray+ , ringSize :: {-# UNPACK #-} !Int -- size of array in bytes+ , ringHead :: {-# UNPACK #-} !Int -- byte index in the array+ }++{-# DEPRECATED Ring "Please use RingArray instead." #-}+type Ring = RingArray++-------------------------------------------------------------------------------+-- Construction+-------------------------------------------------------------------------------++-- | Given byte offset relative to the ring head, compute the linear byte+-- offset in the array. Offset can be positive or negative. Invariants:+--+-- * RingArray size cannot be zero, this won't work correctly if so.+-- * Absolute value of offset must be less than or equal to the ring size.+-- * Offset must be integer multiple of element size.+{-# INLINE unsafeChangeHeadByOffset #-}+unsafeChangeHeadByOffset :: Int -> Int -> Int -> Int+unsafeChangeHeadByOffset rh rs i =+ let i1 = rh + i+ in if i1 >= rs+ then i1 - rs+ else if i1 < 0+ then i1 + rs+ else i1++-- | Convert a byte offset relative to the ring head to a byte offset in the+-- underlying mutable array. Offset can be positive or negative.+--+-- Throws an error if the offset is greater than or equal to the ring size.+{-# INLINE changeHeadByOffset #-}+changeHeadByOffset :: Int -> Int -> Int -> Int+changeHeadByOffset rh rs i =+ if i < rs && i > -rs+ then unsafeChangeHeadByOffset rh rs i+ else error $ "changeHeadByOffset: absolute value of offset must be less "+ ++ "than the ring size"++-- | Move the ring head forward or backward by n slots. Moves forward if the+-- argument is positive and backward if it is negative.+--+-- Throws an error if the absolute value of count is more than or euqal to the+-- ring size.+{-# INLINE moveBy #-}+moveBy :: forall a. Unbox a => Int -> RingArray a -> RingArray a+moveBy n rb =+ let i = changeHeadByOffset (ringHead rb) (ringSize rb) (n * SIZE_OF(a))+ in rb {ringHead = i}++-- | the offset must be exactly the element size in bytes.+{-# INLINE incrHeadByOffset #-}+incrHeadByOffset :: Int -> Int -> Int -> Int+incrHeadByOffset rh rs n =+ -- Note: This works even if the ring size is 0.+ let rh1 = rh + n+ -- greater than is needed when rs = 0+ in if rh1 >= rs+ then 0+ else rh1++-- | Advance the ring head forward by 1 slot, the ring head will now point to+-- the next (newer) item, and the old ring head position will become the latest+-- or the newest item position.+--+-- >>> moveForward = RingArray.moveBy 1+--+{-# INLINE moveForward #-}+moveForward :: forall a. Unbox a => RingArray a -> RingArray a+moveForward rb@RingArray{..} =+ rb { ringHead = incrHeadByOffset ringHead ringSize (SIZE_OF(a)) }++-- | the offset must be exactly the element size in bytes.+{-# INLINE decrHeadByOffset #-}+decrHeadByOffset :: Int -> Int -> Int -> Int+decrHeadByOffset rh rs n =+ -- Note: This works even if the ring size is 0.+ -- Though the head should never be accessed when ring size is 0, so it+ -- should not matter what it is.+ if rs /= 0+ then (if rh == 0 then rs else rh) - n+ else 0++-- | Move the ring head backward by 1 slot, the ring head will now point to+-- the prev (older) item, when the ring head is at the oldest item it will move+-- to the newest item.+--+-- >>> moveForward = RingArray.moveBy (-1)+--+{-# INLINE moveReverse #-}+moveReverse :: forall a. Unbox a => RingArray a -> RingArray a+moveReverse rb@RingArray{..} =+ rb { ringHead = decrHeadByOffset ringHead ringSize (SIZE_OF(a)) }++-------------------------------------------------------------------------------+-- Conversions+-------------------------------------------------------------------------------++-- | The array must not be a slice, and the index must be within the bounds of+-- the array otherwise unpredictable behavior will occur.+{-# INLINE unsafeCastMutArrayWith #-}+unsafeCastMutArrayWith :: forall a. Unbox a => Int -> MutArray a -> RingArray a+unsafeCastMutArrayWith i arr =+ RingArray+ { ringContents = arrContents arr+ , ringSize = arrEnd arr+ , ringHead = i * SIZE_OF(a)+ }++-- | Cast a MutArray to a ring sharing the same memory without copying. The+-- ring head is at index 0 of the array. The array must not be a slice.+--+-- >>> unsafeCastMutArray = RingArray.unsafeCastMutArrayWith 0+--+{-# INLINE unsafeCastMutArray #-}+unsafeCastMutArray :: forall a. Unbox a => MutArray a -> RingArray a+unsafeCastMutArray = unsafeCastMutArrayWith 0++-- XXX To avoid the failure we can either copy the array or have a ringStart+-- field in the ring. For copying we can have another API though.++-- XXX castMutArray is called unsafeFreeze in the Array module. Make the naming+-- consistent?++-- | @castMutArrayWith index arr@ casts a mutable array to a ring array, and+-- positions the ring head at the given @index@ in the array.+--+-- A MutArray can be a slice which means its memory starts from some offset in+-- the underlying MutableByteArray, and not from 0 offset. RingArray always+-- uses the memory from offset zero in the MutableByteArray, therefore, it+-- refuses to cast if it finds the array does not start from offset zero i.e.+-- if the array was created from some slicing operation over another array. In+-- such cases it returns 'Nothing'.+--+-- To create a RingArray from a sliced MutArray use 'createOfLast', or clone+-- the MutArray and then cast it.+--+-- This operation throws an error if the index is not within the array bounds.+--+{-# INLINE castMutArrayWith #-}+castMutArrayWith :: forall a. Unbox a => Int -> MutArray a -> Maybe (RingArray a)+castMutArrayWith i arr+ | i < 0 || i >= MutArray.length arr+ = error "castMutArray: index must not be negative or >= array size"+ | arrStart arr == 0+ = Just $ unsafeCastMutArrayWith i arr+ | otherwise = Nothing++-- | Cast a MutArray to a ring sharing the same memory without copying. The+-- ring head is positioned at index 0 of the array. The size of the ring is+-- equal to the MutArray length.+--+-- See 'castMutArrayWith' for failure scenarios.+--+-- >>> castMutArray = RingArray.castMutArrayWith 0+--+{-# INLINE castMutArray #-}+castMutArray :: forall a. Unbox a => MutArray a -> Maybe (RingArray a)+castMutArray = castMutArrayWith 0++-------------------------------------------------------------------------------+-- Conversion to/from array+-------------------------------------------------------------------------------++-- | Modify a given index of a ring array using a modifier function.+--+-- /Unimplemented/+modifyIndex :: -- forall m a b. (MonadIO m, Unbox a) =>+ Int -> RingArray a -> (a -> (a, b)) -> m b+modifyIndex = undefined++-- | /O(1)/ Write the given element at the given index relative to the current+-- position of the ring head. Index starts at 0, could be positive or negative.+--+-- Throws an error if the index is more than or equal to the size of the ring.+--+-- Performs in-place mutation of the array.+--+{-# INLINE putIndex #-}+putIndex :: forall m a. (MonadIO m, Unbox a) => Int -> RingArray a -> a -> m ()+-- putIndex ix ring val = modifyIndex ix ring (const (val, ()))+putIndex i ring x =+ -- Note: ring must be of non-zero size.+ let j = changeHeadByOffset (ringHead ring) (ringSize ring) (i * SIZE_OF(a))+ in liftIO $ pokeAt j (ringContents ring) x++-- XXX Expand the ring by inserting the newest element before the head. If the+-- number of elements before the head are lesser than the ones after it then+-- shift them all by one place to the left, moving the first element at the end+-- of the ring. Otherwise, shift the elements after the head by one place to+-- the right. Note this requires adding a capacity field to the ring. Also,+-- like mutarray we can reallocate the ring to expand the capacity.++-- | Insert a new element without replacing an old one. Expands the size of the+-- ring. This is similar to the snoc operation for MutArray.+--+-- /Unimplemented/+{-# INLINE insert #-}+insert :: -- (MonadIO m, Unbox a) =>+ RingArray a -> a -> m (RingArray a)+insert = undefined++-- | Like 'replace' but does not return the old value of overwritten element.+--+-- Same as:+--+-- >>> replace_ rb x = RingArray.putIndex 0 rb x >> pure (RingArray.moveForward rb)+--+{-# INLINE replace_ #-}+replace_ :: forall m a. (MonadIO m, Unbox a) => RingArray a -> a -> m (RingArray a)+replace_ rb newVal = do+ -- Note poke will corrupt memory if the ring size is 0.+ when (ringSize rb /= 0)+ $ liftIO $ pokeAt (ringHead rb) (ringContents rb) newVal+ pure $ moveForward rb++-- | Return the element at the specified index without checking the bounds.+--+-- Unsafe because it does not check the bounds of the ring array.+{-# INLINE unsafeGetRawIndex #-}+unsafeGetRawIndex :: forall m a. (MonadIO m, Unbox a) => Int -> RingArray a -> m a+unsafeGetRawIndex i ring = liftIO $ peekAt i (ringContents ring)++-- | Replace the oldest item in the ring (the item at the ring head) with a new+-- item and move the ring head to the remaining oldest item.+--+-- Throws an error if the ring is empty.+--+{-# INLINE replace #-}+replace :: forall m a. (MonadIO m, Unbox a) => RingArray a -> a -> m (RingArray a, a)+replace rb newVal = do+ -- Note: ring size cannot be zero.+ when (ringSize rb == 0) $+ error "insert: cannot insert in 0 sized ring"+ old <- unsafeGetRawIndex (ringHead rb) rb+ liftIO $ pokeAt (ringHead rb) (ringContents rb) newVal+ pure (moveForward rb, old)++-------------------------------------------------------------------------------+-- Random reads+-------------------------------------------------------------------------------++-- | Like 'getIndex' but does not check the bounds. Unpredictable behavior+-- occurs if the index is more than or equal to the ring size.+{-# INLINE unsafeGetIndex #-}+unsafeGetIndex :: forall m a. (MonadIO m, Unbox a) => Int -> RingArray a -> m a+unsafeGetIndex i ring =+ let rs = ringSize ring+ j = unsafeChangeHeadByOffset (ringHead ring) rs (i * SIZE_OF(a))+ in unsafeGetRawIndex j ring++-- | /O(1)/ Lookup the element at the given index relative to the ring head.+-- Index starts from 0, could be positive or negative. Returns Nothing if the+-- index is more than or equal to the size of the ring.+--+{-# INLINE getIndex #-}+getIndex :: forall m a. (MonadIO m, Unbox a) => Int -> RingArray a -> m (Maybe a)+getIndex i ring =+ let rs = ringSize ring+ in if i < rs && i > -rs+ then Just <$> unsafeGetIndex i ring+ else return Nothing++-- | /O(1)/ Lookup the element at the head position.+--+-- Prefer this over @unsafeGetIndex 0@ as it does not have have to perform an+-- index rollover check.+--+{-# INLINE unsafeGetHead #-}+unsafeGetHead :: (MonadIO m, Unbox a) => RingArray a -> m a+unsafeGetHead ring = unsafeGetRawIndex (ringHead ring) ring++-------------------------------------------------------------------------------+-- Size+-------------------------------------------------------------------------------++-- | /O(1)/ Get the byte length of the ring.+--+{-# INLINE byteLength #-}+byteLength :: RingArray a -> Int+byteLength = ringSize++-- | /O(1)/ Get the length of the ring. i.e. the number of elements in the+-- ring.+--+{-# INLINE length #-}+length :: forall a. Unbox a => RingArray a -> Int+length rb = ringSize rb `div` SIZE_OF(a)++-------------------------------------------------------------------------------+-- Unfolds+-------------------------------------------------------------------------------++-- | Read the entire ring, starting at the ring head i.e. from oldest to+-- newest.+--+{-# INLINE_NORMAL reader #-}+reader :: forall m a. (MonadIO m, Unbox a) => Unfold m (RingArray a) a+reader = Unfold step inject++ where++ inject rb = return (rb, ringSize rb)++ step (rb, n) = do+ if n <= 0+ then return Stop+ else do+ x <- unsafeGetHead rb+ return $ Yield x (moveForward rb, n - SIZE_OF(a))++-- | Read the entire ring in reverse order, starting at the item before the+-- ring head i.e. from newest to oldest+--+{-# INLINE_NORMAL readerRev #-}+readerRev :: forall m a. (MonadIO m, Unbox a) => Unfold m (RingArray a) a+readerRev = Unfold step inject++ where++ inject rb = return (moveReverse rb, ringSize rb)++ step (rb, n) = do+ if n <= 0+ then return Stop+ else do+ x <- unsafeGetHead rb+ return $ Yield x (moveReverse rb, n - SIZE_OF(a))++-- | Read the entire ring as a stream, starting at the ring head i.e. from+-- oldest to newest.+--+{-# INLINE_NORMAL read #-}+read :: forall m a. (MonadIO m, Unbox a) => RingArray a -> Stream m a+read = Stream.unfold reader++-- | Read the entire ring as a stream, starting from newest to oldest elements.+--+{-# INLINE_NORMAL readRev #-}+readRev :: forall m a. (MonadIO m, Unbox a) => RingArray a -> Stream m a+readRev = Stream.unfold readerRev++-------------------------------------------------------------------------------+-- Stream of arrays+-------------------------------------------------------------------------------++-- | @scanRingsOf n@ groups the input stream into a stream of ring arrays of+-- size up to @n@. The first ring would be of size 1, then 2, and so on up to+-- size n, when size n is reached the ring starts sliding out the oldest+-- elements and keeps the newest n elements.+--+-- Note that the ring emitted is a mutable reference, therefore, should not be+-- retained without copying otherwise the contents will change in the next+-- iteration of the stream.+--+{-# INLINE scanRingsOf #-}+scanRingsOf :: forall m a. (MonadIO m, Unbox a) => Int -> Scanl m a (RingArray a)+scanRingsOf n = Scanl step initial extract extract++ where++ rSize = n * SIZE_OF(a)++ initial =+ if n <= 0+ then error "scanRingsOf: window size must be > 0"+ else do+ mba <- liftIO $ MutByteArray.new rSize+ return $ Partial $ Tuple3Fused' mba 0 0++ step (Tuple3Fused' mba rh offset) a = do+ RingArray _ _ rh1 <- replace_ (RingArray mba rSize rh) a+ let offset1 = offset + SIZE_OF(a)+ return $ Partial $ Tuple3Fused' mba rh1 offset1++ -- XXX exitify optimization causes a problem here when modular folds are+ -- used. Sometimes inlining "extract" is helpful.+ {-# INLINE extract #-}+ extract (Tuple3Fused' mba rh offset) =+ let rs = min offset rSize+ rh1 = if offset <= rSize then 0 else rh+ in pure $ RingArray mba rs rh1++-- | @ringsOf n stream@ groups the input stream into a stream of ring arrays of+-- size up to n. See 'scanRingsOf' for more details.+--+{-# INLINE_NORMAL ringsOf #-}+ringsOf :: forall m a. (MonadIO m, Unbox a) =>+ Int -> Stream m a -> Stream m (RingArray a)+ringsOf n = Stream.postscanl (scanRingsOf n)++-- XXX to keep the order intact use RingArray.read. If order is not important for+-- the fold then we can use asMutArray which could be slightly faster.+-- f1 rb = Stream.fold f $ MutArray.read $ fst $ RingArray.asMutArray rb++-- XXX the size and the array pointer are constant in the stream, only the head+-- changes on each tick. So we can just emit the head in the loop and keep the+-- size and pointer global.++{-# INLINE_NORMAL scanCustomFoldRingsBy #-}+scanCustomFoldRingsBy :: forall m a b. (MonadIO m, Unbox a) =>+ (RingArray a -> m b) -> Int -> Scanl m a b+-- Custom RingArray.fold performs better than the idiomatic implementations below,+-- perhaps because of some GHC optimization effect.+scanCustomFoldRingsBy f = Scanl.rmapM f . scanRingsOf++-- | Apply the given fold on sliding windows of the given size. Note that this+-- could be expensive because each operation goes through the entire window.+-- This should be used only if there is no efficient alternative way possible.+--+-- Examples:+--+-- >>> windowRange = RingArray.scanFoldRingsBy Fold.range+-- >>> windowMinimum = RingArray.scanFoldRingsBy Fold.minimum+-- >>> windowMaximum = RingArray.scanFoldRingsBy Fold.maximum+--+{-# INLINE scanFoldRingsBy #-}+scanFoldRingsBy :: forall m a b. (MonadIO m, Unbox a) =>+ Fold m a b -> Int -> Scanl m a b+-- Custom RingArray.fold performs better than the idiomatic implementations below,+-- perhaps because of some GHC optimization effect.+scanFoldRingsBy f = scanCustomFoldRingsBy (fold f)+-- scanFoldRingsBy f = Scanl.rmapM (fold f) . scanRingsOf+-- scanFoldRingsBy f = Scanl.rmapM (Unfold.fold f reader) . scanRingsOf+-- scanFoldRingsBy f = Scanl.rmapM (Stream.fold f . read) . scanRingsOf+++-------------------------------------------------------------------------------+-- Construction+-------------------------------------------------------------------------------++-- | @createOfLast n@ returns the last n elements of the stream in a ring+-- array. @n@ must be non-zero.+--+{-# INLINE createOfLast #-}+createOfLast :: (Unbox a, MonadIO m) => Int -> Fold m a (RingArray a)+createOfLast n = Fold.fromScanl $ scanRingsOf n++-------------------------------------------------------------------------------+-- Casting+-------------------------------------------------------------------------------++-- | Cast a ring having elements of type @a@ into a ring having elements of+-- type @b@. The ring size must be a multiple of the size of type @b@.+--+{-# INLINE unsafeCast #-}+unsafeCast :: RingArray a -> RingArray b+unsafeCast RingArray{..} =+ RingArray+ { ringContents = ringContents+ , ringHead = ringHead+ , ringSize = ringSize+ }++-- | Cast a @RingArray a@ into a @RingArray Word8@.+--+asBytes :: RingArray a -> RingArray Word8+asBytes = unsafeCast++-- | Cast a ring having elements of type @a@ into a ring having elements of+-- type @b@. The length of the ring should be a multiple of the size of the+-- target element otherwise 'Nothing' is returned.+--+{-# INLINE cast #-}+cast :: forall a b. (Unbox b) => RingArray a -> Maybe (RingArray b)+cast ring =+ let len = byteLength ring+ r = len `mod` SIZE_OF(b)+ in if r /= 0+ then Nothing+ else Just $ unsafeCast ring++-------------------------------------------------------------------------------+-- Equality+-------------------------------------------------------------------------------++-- | Like 'eqArray' but compares only N bytes instead of entire length of the+-- ring buffer. If N is bigger than the ring or array size, it is treated as an+-- error.+--+{-# INLINE eqArrayN #-}+eqArrayN :: RingArray a -> Array a -> Int -> IO Bool+eqArrayN RingArray{..} Array.Array{..} nBytes+ | nBytes < 0 = error "eqArrayN: n should be >= 0"+ | arrEnd - arrStart < nBytes = error "eqArrayN: array is shorter than n"+ | ringSize < nBytes = error "eqArrayN: ring is shorter than n"+ | nBytes == 0 = return True+ | nBytes <= p1Len = do+ part1 <-+ MutByteArray.unsafeByteCmp+ arrContents 0 ringContents ringHead nBytes+ pure $ part1 == 0+ | otherwise = do+ part1 <-+ MutByteArray.unsafeByteCmp+ arrContents 0 ringContents ringHead p1Len+ part2 <-+ MutByteArray.unsafeByteCmp arrContents p1Len ringContents 0 p2Len+ pure $ part1 == 0 && part2 == 0+ where+ p1Len = ringSize - ringHead+ p2Len = nBytes - p1Len++-- | Byte compare the entire length of ringBuffer with the given array,+-- starting at the supplied ring head index. Returns true if the Array and+-- the ring have identical contents. If the array is bigger checks only+-- up to the ring length. If array is shorter than then ring, it is treated as+-- an error.+--+{-# INLINE eqArray #-}+eqArray :: RingArray a -> Array a -> IO Bool+eqArray RingArray{..} Array.Array{..}+ | arrEnd - arrStart < ringSize =+ error "eqArrayN: array is shorter than ring"+ | otherwise = do+ part1 <-+ MutByteArray.unsafeByteCmp+ arrContents 0 ringContents ringHead p1Len+ part2 <-+ MutByteArray.unsafeByteCmp+ arrContents p1Len ringContents 0 p2Len+ pure $ part1 == 0 && part2 == 0+ where+ p1Len = ringSize - ringHead+ p2Len = ringHead++-------------------------------------------------------------------------------+-- Folding+-------------------------------------------------------------------------------++-- Note: INLINE_NORMAL is important for use in scanFoldRingsBy++-- | Fold the entire length of a ring buffer starting at the current ring head.+--+{-# INLINE_NORMAL fold #-}+fold :: forall m a b. (MonadIO m, Unbox a)+ => Fold m a b -> RingArray a -> m b+-- These are slower when used in a scan extract. One of the issues is the+-- exitify optimization, there could be others.+-- fold f rb = Unfold.fold f reader rb+-- fold f rb = Stream.fold f $ read rb+fold (Fold step initial _ final) rb = do+ res <- initial+ case res of+ Fold.Partial fs -> go SPEC rh fs+ Fold.Done b -> return b++ where++ rh = ringHead rb++ -- Note: Passing the SPEC arg seems to give better results in windowRange+ -- benchmarks for larger windows, while worse results for smaller windows.+ {-# INLINE go #-}+ go !_ index !fs = do+ x <- unsafeGetRawIndex index rb+ r <- step fs x+ case r of+ Fold.Done b -> return b+ Fold.Partial s -> do+ let next = incrHeadByOffset index (ringSize rb) (SIZE_OF(a))+ if next == rh+ then final s+ else go SPEC next s++-- XXX This was for folding when the ring is not full, now we do not support+-- that so this should not be needed.++-- | Fold the buffer starting from ringStart up to the given index using a pure+-- step function. This is useful to fold the items in the ring when the ring is+-- not full. The supplied index is usually the end of the ring.+--+-- Unsafe because the supplied index is not checked to be in range.+{-# DEPRECATED unsafeFoldRing "This function will be removed in future." #-}+{-# INLINE unsafeFoldRing #-}+unsafeFoldRing :: forall a b. Unbox a+ => Int -> (b -> a -> b) -> b -> RingArray a -> IO b+unsafeFoldRing !len f z rb = go z 0++ where++ go !acc !index+ | index == len = return acc+ | otherwise = do+ x <- unsafeGetRawIndex index rb+ go (f acc x) (index + SIZE_OF(a))++-- | Like unsafeFoldRing but with a monadic step function.+{-# DEPRECATED unsafeFoldRingM "This function will be removed in future." #-}+{-# INLINE unsafeFoldRingM #-}+unsafeFoldRingM :: forall m a b. (MonadIO m, Unbox a)+ => Int -> (b -> a -> m b) -> b -> RingArray a -> m b+unsafeFoldRingM !len f z rb = go z 0++ where++ go !acc !index+ | index == len = return acc+ | otherwise = do+ x <- unsafeGetRawIndex index rb+ acc1 <- f acc x+ go acc1 (index + SIZE_OF(a))++-- | Fold the entire length of a ring buffer starting at the current ring head.+--+-- Note, this will crash on ring of 0 size.+--+{-# INLINE foldlM' #-}+foldlM' :: forall m a b. (MonadIO m, Unbox a)+ => (b -> a -> m b) -> b -> RingArray a -> m b+foldlM' f z = fold (Fold.foldlM' f (pure z))++-- These are slower when used in a scan extract. One of the issues is the+-- exitify optimization, there could be others.+-- foldlM' f z rb = Unfold.fold (Fold.foldlM' f (pure z)) reader rb+-- foldlM' f z rb = Stream.fold (Fold.foldlM' f (pure z)) $ read rb++{-+foldlM' f z rb = go z rh++ where++ rh = ringHead rb++ go !acc !index = do+ x <- unsafeGetRawIndex index rb+ acc' <- f acc x+ let next = incrHeadByOffset index (ringSize rb) (SIZE_OF(a))+ if next == rh+ then return acc'+ else go acc' next+-}++{-# DEPRECATED unsafeFoldRingFullM "This function will be removed in future." #-}+{-# INLINE unsafeFoldRingFullM #-}+unsafeFoldRingFullM :: forall m a b. (MonadIO m, Unbox a)+ => (b -> a -> m b) -> b -> RingArray a -> m b+unsafeFoldRingFullM = foldlM'++-- | Fold @n@ items in the ring starting at the ring head. Won't fold more+-- than the length of the ring even if @n@ is larger.+--+-- Note, this will crash on ring of 0 size.+--+{-# DEPRECATED unsafeFoldRingNM "This function will be removed in future." #-}+{-# INLINE unsafeFoldRingNM #-}+unsafeFoldRingNM :: forall m a b. (MonadIO m, Unbox a)+ => Int -> (b -> a -> m b) -> b -> RingArray a -> m b+unsafeFoldRingNM count f z rb = go count z rh++ where++ rh = ringHead rb++ go 0 acc _ = return acc+ go !n !acc !index = do+ x <- unsafeGetRawIndex index rb+ acc' <- f acc x+ let next = unsafeChangeHeadByOffset index (ringSize rb) (SIZE_OF(a))+ if next == rh || n == 0+ then return acc'+ else go (n - 1) acc' next++-- | Cast the ring to a mutable array. Return the mutable array as well as the+-- current position of the ring head. Note that the array does not start with+-- the current ring head. The array refers to the same memory as the ring.+{-# INLINE asMutArray #-}+asMutArray :: RingArray a -> (MutArray a, Int)+asMutArray rb =+ ( MutArray+ { arrContents = ringContents rb+ , arrStart = 0+ , arrEnd = ringSize rb+ , arrBound = ringSize rb+ }+ , ringHead rb+ )++-- | Like 'asMutArray' but does not return the ring head.+--+-- >>> asMutArray_ = fst . RingArray.asMutArray+--+{-# INLINE asMutArray_ #-}+asMutArray_ :: RingArray a -> MutArray a+asMutArray_ rb =+ MutArray+ { arrContents = ringContents rb+ , arrStart = 0+ , arrEnd = ringSize rb+ , arrBound = ringSize rb+ }++-- XXX We can use bulk copy using memcpy or at least a Word64 at a time.++-- | Copy the ring to a MutArray, the first element of the MutArray is the+-- oldest element of the ring (i.e. ring head) and the last is the newest.+--+-- >>> toMutArray rb = Stream.fold (MutArray.createOf (RingArray.length rb)) $ RingArray.read rb+--+{-# INLINE toMutArray #-}+toMutArray :: (MonadIO m, Unbox a) => RingArray a -> m (MutArray a)+toMutArray rb = MutArray.fromStreamN (length rb) $ read rb+{-+toMutArray rb = do+ -- Using unpinned array here instead of pinned+ arr <- liftIO $ MutArray.emptyOf (length rb)+ let snoc' b a = liftIO $ MutArray.unsafeSnoc b a+ foldlM' snoc' arr rb+-}++-- | Copy the ring to a list, the first element of the list is the oldest+-- element of the ring (i.e. ring head) and the last is the newest.+--+-- >>> toList = Stream.toList . RingArray.read+--+{-# INLINE toList #-}+toList :: (MonadIO m, Unbox a) => RingArray a -> m [a]+toList = Stream.toList . read++-- | Show the contents of a RingArray as a list.+--+-- >>> showRing rb = RingArray.toList rb >>= return . show+--+showRing :: (Unbox a, Show a) => RingArray a -> IO String+showRing rb = show <$> toList rb++{-# ANN type SlidingWindow Fuse #-}+data SlidingWindow a s = SWArray !a !Int !s !Int | SWRing !a !Int !s++-- | Like slidingWindow but also provides the entire ring contents as an Array.+-- The array reflects the state of the ring after inserting the incoming+-- element.+--+-- IMPORTANT NOTE: The ring is mutable, therefore, the result of @(m (Array+-- a))@ action depends on when it is executed. It does not capture the sanpshot+-- of the ring at a particular time.+{-# DEPRECATED slidingWindowWith "Please use Scanl.incrScanWith instead." #-}+{-# INLINE slidingWindowWith #-}+slidingWindowWith :: forall m a b. (MonadIO m, Unbox a)+ => Int -> Fold m ((a, Maybe a), m (MutArray a)) b -> Fold m a b+slidingWindowWith n (Fold step1 initial1 extract1 final1) =+ Fold step initial extract final++ where++ initial = do+ if n <= 0+ then error "Window size must be > 0"+ else do+ r <- initial1+ arr :: MutArray.MutArray a <- liftIO $ MutArray.emptyOf n+ return $+ case r of+ Partial s -> Partial+ $ SWArray (MutArray.arrContents arr) 0 s (n - 1)+ Done b -> Done b++ step (SWArray mba rh st i) a = do+ RingArray _ _ rh1 <- replace_ (RingArray mba (n * SIZE_OF(a)) rh) a+ let size = (n - i) * SIZE_OF(a)+ r <- step1 st ((a, Nothing), pure (MutArray mba 0 size size))+ return $+ case r of+ Partial s ->+ if i > 0+ then Partial $ SWArray mba rh1 s (i - 1)+ else Partial $ SWRing mba rh1 s+ Done b -> Done b++ step (SWRing mba rh st) a = do+ (rb1@(RingArray _ _ rh1), old) <-+ replace (RingArray mba (n * SIZE_OF(a)) rh) a+ r <- step1 st ((a, Just old), toMutArray rb1)+ return $+ case r of+ Partial s -> Partial $ SWRing mba rh1 s+ Done b -> Done b++ extract (SWArray _ _ st _) = extract1 st+ extract (SWRing _ _ st) = extract1 st++ final (SWArray _ _ st _) = final1 st+ final (SWRing _ _ st) = final1 st++-- | @slidingWindow collector@ is an incremental sliding window+-- fold that does not require all the intermediate elements in a computation.+-- This maintains @n@ elements in the window, when a new element comes it slides+-- out the oldest element and the new element along with the old element are+-- supplied to the collector fold.+--+-- The 'Maybe' type is for the case when initially the window is filling and+-- there is no old element.+--+{-# DEPRECATED slidingWindow "Please use Scanl.incrScan instead." #-}+{-# INLINE slidingWindow #-}+slidingWindow :: forall m a b. (MonadIO m, Unbox a)+ => Int -> Fold m (a, Maybe a) b -> Fold m a b+slidingWindow n f = slidingWindowWith n (lmap fst f)
+ src/Streamly/Internal/Data/RingArray/Generic.hs view
@@ -0,0 +1,189 @@+-- |+-- Module : Streamly.Internal.Data.RingArray.Generic+-- Copyright : (c) 2021 Composewell Technologies+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--++module Streamly.Internal.Data.RingArray.Generic+ ( RingArray(..)+ , Ring++ -- * Generation+ , emptyOf+ , createOf++ -- * Modification+ , seek+ , unsafeInsertRingWith++ -- * Conversion+ , toMutArray+ , copyToMutArray+ , toStreamWith+ ) where++#include "assert.hs"++import Control.Monad.IO.Class (liftIO, MonadIO)+import Streamly.Internal.Data.Stream.Type (Stream)+import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))+import Streamly.Internal.Data.Fold.Type (Fold(..))+import Streamly.Internal.Data.MutArray.Generic (MutArray(..))++-- import qualified Streamly.Internal.Data.Stream.Type as Stream+import qualified Streamly.Internal.Data.Fold.Type as Fold+import qualified Streamly.Internal.Data.MutArray.Generic as MutArray++-- XXX Use MutableArray rather than keeping a MutArray here.+data RingArray a = RingArray+ { ringArr :: MutArray a+ -- XXX We can keep the current fill amount, Or we can keep a count of total+ -- elements inserted and compute ring head as well using mod on that,+ -- assuming it won't overflow. But mod could be expensive.+ , ringHead :: !Int -- current index to be over-written+ , ringMax :: !Int -- first index beyond allocated memory+ }++{-# DEPRECATED Ring "Please use RingArray instead." #-}+type Ring = RingArray++-------------------------------------------------------------------------------+-- Generation+-------------------------------------------------------------------------------++-- XXX If we align the ringMax to nearest power of two then computation of the+-- index to write could be cheaper.+{-# INLINE emptyOf #-}+emptyOf :: MonadIO m => Int -> m (RingArray a)+emptyOf count = liftIO $ do+ arr <- MutArray.emptyOf count+ arr1 <- MutArray.uninit arr count+ return (RingArray+ { ringArr = arr1+ , ringHead = 0+ , ringMax = count+ })+++-- | Note that it is not safe to return a reference to the mutable RingArray using a+-- scan as the RingArray is continuously getting mutated. You could however copy out+-- the RingArray.+{-# INLINE createOf #-}+createOf :: MonadIO m => Int -> Fold m a (RingArray a)+createOf n = Fold step initial extract extract++ where++ initial = do+ if n <= 0+ then Fold.Done <$> emptyOf 0+ else do+ rb <- emptyOf n+ return $ Fold.Partial $ Tuple' rb (0 :: Int)++ step (Tuple' rb cnt) x = do+ rh1 <- liftIO $ unsafeInsertRingWith rb x+ return $ Fold.Partial $ Tuple' (rb {ringHead = rh1}) (cnt + 1)++ extract (Tuple' rb@RingArray{..} cnt) =+ return $+ if cnt < ringMax+ then RingArray ringArr 0 ringHead+ else rb++-------------------------------------------------------------------------------+-- Modification+-------------------------------------------------------------------------------++-- XXX This is safe+-- Take the ring head and return the new ring head.+{-# INLINE unsafeInsertRingWith #-}+unsafeInsertRingWith :: RingArray a -> a -> IO Int+unsafeInsertRingWith RingArray{..} x = do+ assertM(ringMax >= 1)+ assertM(ringHead < ringMax)+ MutArray.unsafePutIndex ringHead ringArr x+ let rh1 = ringHead + 1+ next = if rh1 == ringMax then 0 else rh1+ return next++-- | Move the ring head clockwise (+ve adj) or counter clockwise (-ve adj) by+-- the given amount.+{-# INLINE seek #-}+seek :: MonadIO m => Int -> RingArray a -> m (RingArray a)+seek adj rng@RingArray{..}+ | ringMax > 0 = liftIO $ do+ -- XXX try avoiding mod when in bounds+ let idx1 = ringHead + adj+ next = mod idx1 ringMax+ return $ RingArray ringArr next ringMax+ | otherwise = pure rng++-------------------------------------------------------------------------------+-- Conversion+-------------------------------------------------------------------------------++-- | @toMutArray rignHeadAdjustment lengthToRead ring@.+-- Convert the ring into a boxed mutable array. Note that the returned MutArray+-- shares the same underlying memory as the RingArray, the user of this API needs to+-- ensure that the ring is not mutated during and after the conversion.+--+{-# INLINE toMutArray #-}+toMutArray :: MonadIO m => Int -> Int -> RingArray a -> m (MutArray a)+toMutArray adj n RingArray{..} =+ -- XXX for empty RingArray it will raise an Exception: divide by zero+ if ringMax <= 0+ then MutArray.nil+ else do+ let len = min ringMax n+ let idx = mod (ringHead + adj) ringMax+ end = idx + len+ if end <= ringMax+ then+ return $ ringArr { arrStart = idx, arrEnd = end }+ else do+ -- XXX Just swap the elements in the existing ring and return the+ -- same array without reallocation.+ arr <- liftIO $ MutArray.emptyOf len+ arr1 <- MutArray.uninit arr len+ MutArray.unsafePutSlice ringArr idx arr1 0 (ringMax - idx)+ MutArray.unsafePutSlice ringArr 0 arr1 (ringMax - idx) (end - ringMax)+ return arr1++-- | Copy out the mutable ring to a mutable Array.+{-# INLINE copyToMutArray #-}+copyToMutArray :: MonadIO m => Int -> Int -> RingArray a -> m (MutArray a)+copyToMutArray adj n RingArray{..} = do+ if ringMax <= 0+ then MutArray.nil+ else do+ let len = min ringMax n+ let idx = mod (ringHead + adj) ringMax+ end = idx + len+ arr <- MutArray.emptyOf len+ arr1 <- MutArray.uninit arr len+ MutArray.unsafePutSlice ringArr idx arr1 0 (ringMax - idx)+ MutArray.unsafePutSlice ringArr 0 arr1 (ringMax - idx) (end - ringMax)+ return arr1++-- This would be theoretically slower than toMutArray because of a branch+-- introduced for each element in the second half of the ring.++-- | Seek by n and then read the entire ring. Use 'take' on the stream to+-- restrict the reads.+toStreamWith :: Int -> RingArray a -> Stream m a+toStreamWith = undefined+{-+toStreamWith n RingArray{..}+ | ringMax > 0 = concatEffect $ liftIO $ do+ idx <- readIORef ringHead+ let idx1 = idx + adj+ next = mod idx1 ringMax+ s1 = undefined -- stream initial slice+ s2 = undefined -- stream next slice+ return (s1 `Stream.append` s2)+ | otherwise = Stream.nil+-}
+ src/Streamly/Internal/Data/Scanl.hs view
@@ -0,0 +1,71 @@+{-# LANGUAGE CPP #-}+-- |+-- Module : Streamly.Internal.Data.Scanl+-- Copyright : (c) 2024 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- Left scans.+--+-- == Scanl vs Fold+--+-- Folds and scans both are consumers of streams. A left scan is a+-- generalization of a fold. While the output of a fold is a singleton value,+-- the output of a scan is a stream. A fold is equivalent to a left scan which+-- produces only the final value in the output stream.+--+-- Like folds, a scan has an internal state. Unlike a fold, a scan produces an+-- output on each input, the output is a function of the scan state and the+-- input.+--+-- A @Scanl m a b@ can represent a @Fold m a b@ by discarding the intermediate+-- outputs and keeping only the final output of the scan.+--+-- Since folds do not care about intermediate values, we do not need the+-- extract function for folds. Because folds do not have a requirement for+-- intermediate values, they can be used for implementing combinators like+-- splitWith where intermediate values are not meaningful and are expensive to+-- compute. Folds provide an applicative and monad behavior to consume the+-- stream in parts and compose the folded results. Scans provide Category like+-- composition and stream zip applicative behavior. The finalization function+-- of a fold would return a single value whereas for scan it may be a stream+-- draining the scan buffer. For these reasons, scans and folds are required as+-- independent abstractions.+--+-- == Scanl vs Pipe+--+-- A scan is a simpler version of the consumer side of pipes. A left scan+-- always produces an output whereas a pipe has an additional ability to skip+-- output. Scans are simpler abstractions to think about compared to pipes and+-- easier for the compiler to optimize and fuse.+--+-- == Compositions+--+-- Scans can be chained in the same way as function composition (Category) and+-- can distribute input (tee Applicative). Folds provide an applicative and+-- monad behavior to consume the stream in parts and compose the folded+-- results. Folds are also a special case of parsers.++-- TBD: A scan can produce more than one output on an input, in other words,+-- it can produce output on its own.+--+module Streamly.Internal.Data.Scanl+ (+ -- * Imports+ -- $setup++ module Streamly.Internal.Data.Scanl.Type+ , module Streamly.Internal.Data.Scanl.Window+ , module Streamly.Internal.Data.Scanl.Combinators+ , module Streamly.Internal.Data.Scanl.Container+ )+where++import Streamly.Internal.Data.Scanl.Window+import Streamly.Internal.Data.Scanl.Combinators+import Streamly.Internal.Data.Scanl.Container+import Streamly.Internal.Data.Scanl.Type++#include "DocTestDataFold.hs"
+ src/Streamly/Internal/Data/Scanl/Combinators.hs view
@@ -0,0 +1,2393 @@+{-# LANGUAGE CPP #-}+-- |+-- Module : Streamly.Internal.Data.Scanl.Combinators+-- Copyright : (c) 2024 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--++module Streamly.Internal.Data.Scanl.Combinators+ (+ -- * Scans++ -- ** Accumulators+ -- *** Semigroups and Monoids+ sconcat+ , mconcat+ , foldMap+ , foldMapM++ -- *** Reducers+ , drainMapM+ , the+ , mean+ , rollingHash+ , defaultSalt+ , rollingHashWithSalt+ , rollingHashFirstN+ -- , rollingHashLastN++ -- *** Saturating Reducers+ -- | 'product' terminates if it becomes 0. Other scans can theoretically+ -- saturate on bounded types, and therefore terminate, however, they will+ -- run forever on unbounded types like Integer/Double.+ , sum+ , product++ -- *** Collectors+ -- | Avoid using these scans in scalable or performance critical+ -- applications, they buffer all the input in GC memory which can be+ -- detrimental to performance if the input is large.+ , toListRev+ -- $toListRev+ , toStream+ , toStreamRev+ , topBy+ , top+ , bottomBy+ , bottom++ -- *** Scanners+ -- | Stateful transformation of the elements. Useful in combination with+ -- the 'postscanlMaybe' combinator. For scanners the result of the scan is+ -- usually a transformation of the current element rather than an+ -- aggregation of all elements till now.+ -- , nthLast -- using RingArray array+ , indexingWith+ , indexing+ , indexingRev+ , rollingMap+ , rollingMapM++ -- *** Filters+ -- | Useful in combination with the 'postscanlMaybe' combinator.+ , deleteBy+ , uniqBy+ , uniq+ , repeated+ , findIndices+ , elemIndices++ {-+ -- *** Singleton scans+ -- | Scans that terminate after consuming exactly one input element. All+ -- these can be implemented in terms of the 'maybe' scan.+ , one+ , null -- XXX not very useful and could be problematic, remove it?+ , satisfy+ , maybe+ -}++ -- *** Multi scans+ -- | Terminate after consuming one or more elements.+ , drainN+ {-+ -- , lastN+ -- , (!!)+ , genericIndex+ , index+ , findM+ , find+ , lookup+ , findIndex+ , elemIndex+ , elem+ , notElem+ , all+ , any+ , and+ , or+ -}++ -- ** Trimmers+ -- | Useful in combination with the 'postscanlMaybe' combinator.+ , takingEndByM+ , takingEndBy+ , takingEndByM_+ , takingEndBy_+ , droppingWhileM+ , droppingWhile+ , prune++ -- -- * Running A Scanl+ -- , drive+ -- , breakStream++ -- -- * Building Incrementally+ -- , addStream++ -- * Combinators+ -- ** Utilities+ , with++ -- -- ** Sliding Window+ -- , slide2++ -- ** Scanning Input+ , scanl+ , scanlMany+ -- , runScan+ , pipe+ , indexed++ -- ** Zipping Input+ , zipStreamWithM+ , zipStream++ -- ** Filtering Input+ , mapMaybeM+ , mapMaybe+ , sampleFromthen++ {-+ -- ** Insertion+ -- | Insertion adds more elements to the stream.++ , insertBy+ , intersperseM++ -- ** Reordering+ , reverse+ -}++ -- -- ** Trimming++ -- By elements+ -- , takeEndBySeq+ -- , takeEndBySeq_+ {-+ , drop+ , dropWhile+ , dropWhileM+ -}++ -- -- ** Serial Append+ -- , tail+ -- , init+ -- , splitAt -- spanN+ -- , splitIn -- sessionN++ -- ** Parallel Distribution+ , tee+ , distribute+ -- , distributeFst+ -- , distributeMin++ -- ** Unzipping+ , unzip+ -- These two can be expressed using lmap/lmapM and unzip+ , unzipWith+ , unzipWithM+ -- , unzipWithFstM+ -- , unzipWithMaxM++ -- ** Partitioning+ , partitionByM+ -- , partitionByFstM+ -- , partitionByMinM+ , partitionBy+ , partition++ -- -- ** Splitting+ -- , chunksBetween+ -- , intersperseWithQuotes++ -- ** Nesting+ , unfoldMany+ -- , concatSequence+ )+where++#include "inline.hs"+#include "ArrayMacros.h"++-- import Control.Monad (void)+import Control.Monad.IO.Class (MonadIO(..))+import Data.Bifunctor (first)+-- import Data.Bits (shiftL, shiftR, (.|.), (.&.))+-- import Data.Either (isLeft, isRight, fromLeft, fromRight)+import Data.Int (Int64)+-- import Data.Proxy (Proxy(..))+-- import Data.Word (Word32)+import Streamly.Internal.Data.Unbox (Unbox(..))+import Streamly.Internal.Data.MutArray.Type (MutArray(..))+import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)+import Streamly.Internal.Data.Pipe.Type (Pipe (..))+-- import Streamly.Internal.Data.Scan (Scan (..))+import Streamly.Internal.Data.Stream.Type (Stream)+import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))+import Streamly.Internal.Data.Unfold.Type (Unfold(..))++import qualified Prelude+import qualified Streamly.Internal.Data.MutArray.Type as MA+-- import qualified Streamly.Internal.Data.Array.Type as Array+import qualified Streamly.Internal.Data.Scanl.Window as Scanl+import qualified Streamly.Internal.Data.Pipe.Type as Pipe+-- import qualified Streamly.Internal.Data.RingArray as RingArray+import qualified Streamly.Internal.Data.Stream.Type as StreamD++import Streamly.Internal.Data.Scanl.Type+import Prelude hiding+ ( Foldable(..), filter, drop, dropWhile, take, takeWhile, zipWith+ , map, mapM_, sequence, all, any+ , notElem, head, last, tail+ , reverse, iterate, init, and, or, lookup, (!!)+ , scanl, scanl1, replicate, concatMap, mconcat, unzip+ , span, splitAt, break, mapM, zip, maybe, const)++#include "DocTestDataScanl.hs"++------------------------------------------------------------------------------+-- Running+------------------------------------------------------------------------------++{-+-- | Drive a fold using the supplied 'Stream', reducing the resulting+-- expression strictly at each step.+--+-- Definition:+--+-- >>> drive = flip Stream.toList $ Stream.scanl+--+-- Example:+--+-- >>> Fold.drive (Stream.enumerateFromTo 1 100) Fold.sum+-- 5050+--+{-# INLINE drive #-}+drive :: Monad m => Stream m a -> Fold m a b -> m b+drive = flip StreamD.fold++{-+-- | Like 'drive' but also returns the remaining stream. The resulting stream+-- would be 'Stream.nil' if the stream finished before the fold.+--+-- Definition:+--+-- >>> breakStream = flip Stream.toList $ Stream.scanlBreak+--+-- /CPS/+--+{-# INLINE breakStreamK #-}+breakStreamK :: Monad m => StreamK m a -> Fold m a b -> m (b, StreamK m a)+breakStreamK strm fl = fmap f $ K.foldBreak fl (Stream.toStreamK strm)++ where++ f (b, str) = (b, Stream.fromStreamK str)+-}++-- | Append a stream to a fold to build the fold accumulator incrementally. We+-- can repeatedly call 'addStream' on the same fold to continue building the+-- fold and finally use 'drive' to finish the fold and extract the result. Also+-- see the 'Streamly.Data.Fold.addOne' operation which is a singleton version+-- of 'addStream'.+--+-- Definitions:+--+-- >>> addStream stream = Fold.drive stream . Fold.duplicate+--+-- Example, build a list incrementally:+--+-- >>> :{+-- pure (Fold.toList :: Fold IO Int [Int])+-- >>= Fold.addOne 1+-- >>= Fold.addStream (Stream.enumerateFromTo 2 4)+-- >>= Fold.drive Stream.nil+-- >>= print+-- :}+-- [1,2,3,4]+--+-- This can be used as an O(n) list append compared to the O(n^2) @++@ when+-- used for incrementally building a list.+--+-- Example, build a stream incrementally:+--+-- >>> :{+-- pure (Fold.toStream :: Fold IO Int (Stream Identity Int))+-- >>= Fold.addOne 1+-- >>= Fold.addStream (Stream.enumerateFromTo 2 4)+-- >>= Fold.drive Stream.nil+-- >>= print+-- :}+-- fromList [1,2,3,4]+--+-- This can be used as an O(n) stream append compared to the O(n^2) @<>@ when+-- used for incrementally building a stream.+--+-- Example, build an array incrementally:+--+-- >>> :{+-- pure (Array.write :: Fold IO Int (Array Int))+-- >>= Fold.addOne 1+-- >>= Fold.addStream (Stream.enumerateFromTo 2 4)+-- >>= Fold.drive Stream.nil+-- >>= print+-- :}+-- fromList [1,2,3,4]+--+-- Example, build an array stream incrementally:+--+-- >>> :{+-- let f :: Fold IO Int (Stream Identity (Array Int))+-- f = Fold.groupsOf 2 (Array.writeN 3) Fold.toStream+-- in pure f+-- >>= Fold.addOne 1+-- >>= Fold.addStream (Stream.enumerateFromTo 2 4)+-- >>= Fold.drive Stream.nil+-- >>= print+-- :}+-- fromList [fromList [1,2],fromList [3,4]]+--+addStream :: Monad m => Stream m a -> Scanl m a b -> m (Scanl m a b)+addStream stream = drive stream . duplicate+-}++------------------------------------------------------------------------------+-- Transformations on fold inputs+------------------------------------------------------------------------------++-- |+-- >>> mapMaybeM f = Scanl.lmapM f . Scanl.catMaybes+--+{-# INLINE mapMaybeM #-}+mapMaybeM :: Monad m => (a -> m (Maybe b)) -> Scanl m b r -> Scanl m a r+mapMaybeM f = lmapM f . catMaybes++-- | @mapMaybe f scan@ maps a 'Maybe' returning function @f@ on the input of+-- the scan, filters out 'Nothing' elements, and return the values extracted+-- from 'Just'.+--+-- >>> mapMaybe f = Scanl.lmap f . Scanl.catMaybes+-- >>> mapMaybe f = Scanl.mapMaybeM (return . f)+--+-- >>> f x = if even x then Just x else Nothing+-- >>> scn = Scanl.mapMaybe f Scanl.toList+-- >>> Stream.toList $ Stream.scanl scn (Stream.enumerateFromTo 1 10)+-- [[],[],[2],[2],[2,4],[2,4],[2,4,6],[2,4,6],[2,4,6,8],[2,4,6,8],[2,4,6,8,10]]+--+{-# INLINE mapMaybe #-}+mapMaybe :: Monad m => (a -> Maybe b) -> Scanl m b r -> Scanl m a r+mapMaybe f = lmap f . catMaybes++------------------------------------------------------------------------------+-- Transformations on scan inputs+------------------------------------------------------------------------------++-- XXX rather scanl the input of a pipe? And scanr the output?+-- pipe :: Monad m => Scanl m a b -> Pipe m b c -> Scanl m a c+-- Can we do this too (in the pipe module):+-- pipe :: Monad m => Scanl m a b -> Pipe m b c -> Pipe m a c++-- | Attach a 'Pipe' on the input of a 'Scanl'.+--+-- /Pre-release/+{-# INLINE pipe #-}+pipe :: Monad m => Pipe m a b -> Scanl m b c -> Scanl m a c+pipe (Pipe consume produce pinitial) (Scanl fstep finitial fextract ffinal) =+ Scanl step initial extract final++ where++ initial = first (Tuple' pinitial) <$> finitial++ step (Tuple' cs fs) x = do+ r <- consume cs x+ go fs r++ where++ -- XXX use SPEC?+ go acc (Pipe.YieldC cs1 b) = do+ acc1 <- fstep acc b+ return+ $ case acc1 of+ Partial s -> Partial $ Tuple' cs1 s+ Done b1 -> Done b1+ -- XXX this case is recursive may cause fusion issues.+ -- To remove recursion we will need a produce mode in scans which makes+ -- scans similar to pipes except that they do not yield intermediate+ -- values.+ go acc (Pipe.YieldP ps1 b) = do+ acc1 <- fstep acc b+ r <- produce ps1+ case acc1 of+ Partial s -> go s r+ Done b1 -> return $ Done b1+ go acc (Pipe.SkipC cs1) =+ return $ Partial $ Tuple' cs1 acc+ -- XXX this case is recursive may cause fusion issues.+ go acc (Pipe.SkipP ps1) = do+ r <- produce ps1+ go acc r+ -- XXX a Stop in consumer means we dropped the input.+ -- XXX Need to use a "Done b" in pipes as well to represent the same+ -- behavior as scans.+ go acc Pipe.Stop = Done <$> ffinal acc++ extract (Tuple' _ fs) = fextract fs++ final (Tuple' _ fs) = ffinal fs++{-+{-# INLINE runScanWith #-}+runScanWith :: Monad m => Bool -> Scan m a b -> Fold m b c -> Scanl m a c+runScanWith isMany+ (Scan stepL initialL)+ (Fold stepR initialR extractR finalR) =+ Fold step initial extract final++ where++ step (sL, sR) x = do+ rL <- stepL sL x+ case rL of+ StreamD.Yield b sL1 -> do+ rR <- stepR sR b+ case rR of+ Partial sR1 -> return $ Partial (sL1, sR1)+ Done bR -> return (Done bR)+ StreamD.Skip sL1 -> return $ Partial (sL1, sR)+ -- XXX We have dropped the input.+ -- XXX Need same behavior for Stop in Fold so that the driver can+ -- consistently assume it is dropped.+ StreamD.Stop ->+ if isMany+ then return $ Partial (initialL, sR)+ else Done <$> finalR sR++ initial = do+ r <- initialR+ case r of+ Partial sR -> return $ Partial (initialL, sR)+ Done b -> return $ Done b++ extract = extractR . snd++ final = finalR . snd++-- | Scan the input of a 'Fold' to change it in a stateful manner using a+-- 'Scan'. The scan stops as soon as the fold terminates.+--+-- /Pre-release/+{-# INLINE runScan #-}+runScan :: Monad m => Scan m a b -> Fold m b c -> Scanl m a c+runScan = runScanWith False+-}++{-# INLINE scanWith #-}+scanWith :: Monad m => Bool -> Scanl m a b -> Scanl m b c -> Scanl m a c+scanWith isMany+ (Scanl stepL initialL extractL finalL)+ (Scanl stepR initialR extractR finalR) =+ Scanl step initial extract final++ where++ {-# INLINE runStep #-}+ runStep actionL sR = do+ rL <- actionL+ case rL of+ Done bL -> do+ rR <- stepR sR bL+ case rR of+ Partial sR1 ->+ if isMany+ -- XXX recursive call. If initialL returns Done then it+ -- will not terminate. In that case we should return+ -- error in the beginning itself. And we should remove+ -- this recursion, assuming it won't return Done.+ then runStep initialL sR1+ else Done <$> finalR sR1+ Done bR -> return $ Done bR+ Partial sL -> do+ !b <- extractL sL+ rR <- stepR sR b+ case rR of+ Partial sR1 -> return $ Partial (sL, sR1)+ Done bR -> finalL sL >> return (Done bR)++ initial = do+ r <- initialR+ case r of+ Partial sR -> runStep initialL sR+ Done b -> return $ Done b++ step (sL, sR) x = runStep (stepL sL x) sR++ extract = extractR . snd++ final (sL, sR) = finalL sL *> finalR sR++-- | Scan the input of a 'Scanl' to change it in a stateful manner using+-- another 'Scanl'. The scan stops as soon as any of the scans terminates.+--+-- This is basically an append operation.+--+-- /Pre-release/+{-# INLINE scanl #-}+scanl :: Monad m => Scanl m a b -> Scanl m b c -> Scanl m a c+scanl = scanWith False++-- XXX This does not fuse beacuse of the recursive step. Need to investigate.++-- | Scan the input of a 'Scanl' to change it in a stateful manner using+-- another 'Scanl'. The scan restarts with a fresh state if it terminates.+--+-- /Pre-release/+{-# INLINE scanlMany #-}+scanlMany :: Monad m => Scanl m a b -> Scanl m b c -> Scanl m a c+scanlMany = scanWith True++------------------------------------------------------------------------------+-- Filters+------------------------------------------------------------------------------++-- | Returns the latest element omitting the first occurrence that satisfies+-- the given equality predicate.+--+-- Example:+--+-- >>> input = Stream.fromList [1,3,3,5]+-- >>> Stream.toList $ Stream.postscanlMaybe (Scanl.deleteBy (==) 3) input+-- [1,3,5]+--+{-# INLINE_NORMAL deleteBy #-}+deleteBy :: Monad m => (a -> a -> Bool) -> a -> Scanl m a (Maybe a)+deleteBy eq x0 = fmap extract $ mkScanl step (Tuple' False Nothing)++ where++ step (Tuple' False _) x =+ if eq x x0+ then Tuple' True Nothing+ else Tuple' False (Just x)+ step (Tuple' True _) x = Tuple' True (Just x)++ extract (Tuple' _ x) = x++{-+-- | Provide a sliding window of length 2 elements.+--+-- See "Streamly.Internal.Data.Scanl.Window".+--+{-# INLINE slide2 #-}+slide2 :: Monad m => Fold m (a, Maybe a) b -> Scanl m a b+slide2 (Fold step1 initial1 extract1 final1) = Fold step initial extract final++ where++ initial =+ first (Tuple' Nothing) <$> initial1++ step (Tuple' prev s) cur =+ first (Tuple' (Just cur)) <$> step1 s (cur, prev)++ extract (Tuple' _ s) = extract1 s++ final (Tuple' _ s) = final1 s+-}++-- XXX Compare this with the implementation in Scanl.Window, preferrably use the+-- latter if performance is good.++-- | Apply a function on every two successive elements of a stream. The first+-- argument of the map function is the previous element and the second argument+-- is the current element. When processing the very first element in the+-- stream, the previous element is 'Nothing'.+--+-- /Pre-release/+--+{-# INLINE rollingMapM #-}+rollingMapM :: Monad m => (Maybe a -> a -> m b) -> Scanl m a b+rollingMapM f = Scanl step initial extract extract++ where++ -- XXX We need just a postscan. We do not need an initial result here.+ -- Or we can supply a default initial result as an argument to rollingMapM.+ initial = return $ Partial (Nothing, error "Empty stream")++ step (prev, _) cur = do+ x <- f prev cur+ return $ Partial (Just cur, x)++ extract = return . snd++-- |+-- >>> rollingMap f = Scanl.rollingMapM (\x y -> return $ f x y)+--+{-# INLINE rollingMap #-}+rollingMap :: Monad m => (Maybe a -> a -> b) -> Scanl m a b+rollingMap f = rollingMapM (\x y -> return $ f x y)++-- | Return the latest unique element using the supplied comparison function.+-- Returns 'Nothing' if the current element is same as the last element+-- otherwise returns 'Just'.+--+-- Example, strip duplicate path separators:+--+-- >>> input = Stream.fromList "//a//b"+-- >>> f x y = x == '/' && y == '/'+-- >>> Stream.toList $ Stream.postscanlMaybe (Scanl.uniqBy f) input+-- "/a/b"+--+-- Space: @O(1)@+--+-- /Pre-release/+--+{-# INLINE uniqBy #-}+uniqBy :: Monad m => (a -> a -> Bool) -> Scanl m a (Maybe a)+uniqBy eq = rollingMap f++ where++ f pre curr =+ case pre of+ Nothing -> Just curr+ Just x -> if x `eq` curr then Nothing else Just curr++-- | See 'uniqBy'.+--+-- Definition:+--+-- >>> uniq = Scanl.uniqBy (==)+--+{-# INLINE uniq #-}+uniq :: (Monad m, Eq a) => Scanl m a (Maybe a)+uniq = uniqBy (==)++-- | Strip all leading and trailing occurrences of an element passing a+-- predicate and make all other consecutive occurrences uniq.+--+-- >> prune p = Stream.dropWhileAround p $ Stream.uniqBy (x y -> p x && p y)+--+-- @+-- > Stream.prune isSpace (Stream.fromList " hello world! ")+-- "hello world!"+--+-- @+--+-- Space: @O(1)@+--+-- /Unimplemented/+{-# INLINE prune #-}+prune ::+ -- (Monad m, Eq a) =>+ (a -> Bool) -> Scanl m a (Maybe a)+prune = error "Not implemented yet!"++-- | Emit only repeated elements, once.+--+-- /Unimplemented/+repeated :: -- (Monad m, Eq a) =>+ Scanl m a (Maybe a)+repeated = error "Not implemented yet!"++------------------------------------------------------------------------------+-- Left scans+------------------------------------------------------------------------------++------------------------------------------------------------------------------+-- Run Effects+------------------------------------------------------------------------------++-- |+-- Definitions:+--+-- >>> drainMapM f = Scanl.lmapM f Scanl.drain+-- >>> drainMapM f = Scanl.foldMapM (void . f)+--+-- Drain all input after passing it through a monadic function. This is the+-- dual of mapM_ on stream producers.+--+{-# INLINE drainMapM #-}+drainMapM :: Monad m => (a -> m b) -> Scanl m a ()+drainMapM f = lmapM f drain++-- | Terminates with 'Nothing' as soon as it finds an element different than+-- the previous one, returns 'the' element if the entire input consists of the+-- same element.+--+{-# INLINE the #-}+the :: (Monad m, Eq a) => Scanl m a (Maybe a)+the = mkScant step initial id++ where++ initial = Partial Nothing++ step Nothing x = Partial (Just x)+ step old@(Just x0) x =+ if x0 == x+ then Partial old+ else Done Nothing++------------------------------------------------------------------------------+-- To Summary+------------------------------------------------------------------------------++-- | Determine the sum of all elements of a stream of numbers. Returns additive+-- identity (@0@) when the stream is empty. Note that this is not numerically+-- stable for floating point numbers.+--+-- >>> sum = Scanl.cumulativeScan Scanl.incrSum+--+-- Same as following but numerically stable:+--+-- >>> sum = Scanl.mkScanl (+) 0+-- >>> sum = fmap Data.Monoid.getSum $ Scanl.foldMap Data.Monoid.Sum+--+{-# INLINE sum #-}+sum :: (Monad m, Num a) => Scanl m a a+sum = Scanl.cumulativeScan Scanl.incrSum++-- | Determine the product of all elements of a stream of numbers. Returns+-- multiplicative identity (@1@) when the stream is empty. The scan terminates+-- when it encounters (@0@) in its input.+--+-- Same as the following but terminates on multiplication by @0@:+--+-- >>> product = fmap Data.Monoid.getProduct $ Scanl.foldMap Data.Monoid.Product+--+{-# INLINE product #-}+product :: (Monad m, Num a, Eq a) => Scanl m a a+product = mkScant step (Partial 1) id++ where++ step x a =+ if a == 0+ then Done 0+ else Partial $ x * a++------------------------------------------------------------------------------+-- To Summary (Statistical)+------------------------------------------------------------------------------++-- | Compute a numerically stable arithmetic mean of all elements in the input+-- stream.+--+{-# INLINE mean #-}+mean :: (Monad m, Fractional a) => Scanl m a a+mean = fmap done $ mkScanl step begin++ where++ begin = Tuple' 0 0++ step (Tuple' x n) y =+ let n1 = n + 1+ in Tuple' (x + (y - x) / n1) n1++ done (Tuple' x _) = x++-- | Compute an 'Int' sized polynomial rolling hash+--+-- > H = salt * k ^ n + c1 * k ^ (n - 1) + c2 * k ^ (n - 2) + ... + cn * k ^ 0+--+-- Where @c1@, @c2@, @cn@ are the elements in the input stream and @k@ is a+-- constant.+--+-- This hash is often used in Rabin-Karp string search algorithm.+--+-- See https://en.wikipedia.org/wiki/Rolling_hash+--+{-# INLINE rollingHashWithSalt #-}+rollingHashWithSalt :: (Monad m, Enum a) => Int64 -> Scanl m a Int64+rollingHashWithSalt = mkScanl step++ where++ k = 2891336453 :: Int64++ step cksum a = cksum * k + fromIntegral (fromEnum a)++-- | A default salt used in the implementation of 'rollingHash'.+{-# INLINE defaultSalt #-}+defaultSalt :: Int64+defaultSalt = -2578643520546668380++-- | Compute an 'Int' sized polynomial rolling hash of a stream.+--+-- >>> rollingHash = Scanl.rollingHashWithSalt Scanl.defaultSalt+--+{-# INLINE rollingHash #-}+rollingHash :: (Monad m, Enum a) => Scanl m a Int64+rollingHash = rollingHashWithSalt defaultSalt++-- | Compute an 'Int' sized polynomial rolling hash of the first n elements of+-- a stream.+--+-- >>> rollingHashFirstN n = Scanl.take n Scanl.rollingHash+--+-- /Pre-release/+{-# INLINE rollingHashFirstN #-}+rollingHashFirstN :: (Monad m, Enum a) => Int -> Scanl m a Int64+rollingHashFirstN n = take n rollingHash++------------------------------------------------------------------------------+-- Monoidal left scans+------------------------------------------------------------------------------++-- | Semigroup concat. Append the elements of an input stream to a provided+-- starting value.+--+-- Definition:+--+-- >>> sconcat = Scanl.mkScanl (<>)+--+-- >>> semigroups = fmap Data.Monoid.Sum $ Stream.enumerateFromTo 1 3+-- >>> Stream.toList $ Stream.scanl (Scanl.sconcat 3) semigroups+-- [Sum {getSum = 3},Sum {getSum = 4},Sum {getSum = 6},Sum {getSum = 9}]+--+{-# INLINE sconcat #-}+sconcat :: (Monad m, Semigroup a) => a -> Scanl m a a+sconcat = mkScanl (<>)++-- | Monoid concat. Scan an input stream consisting of monoidal elements using+-- 'mappend' and 'mempty'.+--+-- Definition:+--+-- >>> mconcat = Scanl.sconcat mempty+--+-- >>> monoids = fmap Data.Monoid.Sum $ Stream.enumerateFromTo 1 3+-- >>> Stream.toList $ Stream.scanl Scanl.mconcat monoids+-- [Sum {getSum = 0},Sum {getSum = 1},Sum {getSum = 3},Sum {getSum = 6}]+--+{-# INLINE mconcat #-}+mconcat ::+ ( Monad m+ , Monoid a) => Scanl m a a+mconcat = sconcat mempty++-- |+-- Definition:+--+-- >>> foldMap f = Scanl.lmap f Scanl.mconcat+--+-- Make a scan from a pure function that scans the output of the function+-- using 'mappend' and 'mempty'.+--+-- >>> sum = Scanl.foldMap Data.Monoid.Sum+-- >>> Stream.toList $ Stream.scanl sum $ Stream.enumerateFromTo 1 3+-- [Sum {getSum = 0},Sum {getSum = 1},Sum {getSum = 3},Sum {getSum = 6}]+--+{-# INLINE foldMap #-}+foldMap :: (Monad m, Monoid b) => (a -> b) -> Scanl m a b+foldMap f = lmap f mconcat++-- |+-- Definition:+--+-- >>> foldMapM f = Scanl.lmapM f Scanl.mconcat+--+-- Make a scan from a monadic function that scans the output of the function+-- using 'mappend' and 'mempty'.+--+-- >>> sum = Scanl.foldMapM (return . Data.Monoid.Sum)+-- >>> Stream.toList $ Stream.scanl sum $ Stream.enumerateFromTo 1 3+-- [Sum {getSum = 0},Sum {getSum = 1},Sum {getSum = 3},Sum {getSum = 6}]+--+{-# INLINE foldMapM #-}+foldMapM :: (Monad m, Monoid b) => (a -> m b) -> Scanl m a b+foldMapM act = mkScanlM step (pure mempty)++ where++ step m a = do+ m' <- act a+ return $! mappend m m'++------------------------------------------------------------------------------+-- To Containers+------------------------------------------------------------------------------++-- $toListRev+-- This is more efficient than 'Streamly.Internal.Data.Scanl.toList'. toList is+-- exactly the same as reversing the list after 'toListRev'.++-- | Buffers the input stream to a list in the reverse order of the input.+--+-- Definition:+--+-- >>> toListRev = Scanl.mkScanl (flip (:)) []+--+-- /Warning!/ working on large lists accumulated as buffers in memory could be+-- very inefficient, consider using "Streamly.Array" instead.+--++-- xn : ... : x2 : x1 : []+{-# INLINE toListRev #-}+toListRev :: Monad m => Scanl m a [a]+toListRev = mkScanl (flip (:)) []++------------------------------------------------------------------------------+-- Partial Scans+------------------------------------------------------------------------------++-- | A scan that drains the first n elements of its input, running the effects+-- and discarding the results.+--+-- Definition:+--+-- >>> drainN n = Scanl.take n Scanl.drain+--+-- /Pre-release/+{-# INLINE drainN #-}+drainN :: Monad m => Int -> Scanl m a ()+drainN n = take n drain++{-+------------------------------------------------------------------------------+-- To Elements+------------------------------------------------------------------------------++-- | Like 'index', except with a more general 'Integral' argument+--+-- /Pre-release/+{-# INLINE genericIndex #-}+genericIndex :: (Integral i, Monad m) => i -> Scanl m a (Maybe a)+genericIndex i = mkScant step (Partial 0) (const Nothing)++ where++ step j a =+ if i == j+ then Done $ Just a+ else Partial (j + 1)++-- | Return the element at the given index.+--+-- Definition:+--+-- >>> index = Scanl.genericIndex+--+{-# INLINE index #-}+index :: Monad m => Int -> Scanl m a (Maybe a)+index = genericIndex++-- | Consume a single input and transform it using the supplied 'Maybe'+-- returning function.+--+-- /Pre-release/+--+{-# INLINE maybe #-}+maybe :: Monad m => (a -> Maybe b) -> Scanl m a (Maybe b)+maybe f = mkScant (const (Done . f)) (Partial Nothing) id++-- | Consume a single element and return it if it passes the predicate else+-- return 'Nothing'.+--+-- Definition:+--+-- >>> satisfy f = Scanl.maybe (\a -> if f a then Just a else Nothing)+--+-- /Pre-release/+{-# INLINE satisfy #-}+satisfy :: Monad m => (a -> Bool) -> Scanl m a (Maybe a)+satisfy f = maybe (\a -> if f a then Just a else Nothing)+{-+satisfy f = Fold step (return $ Partial ()) (const (return Nothing))++ where++ step () a = return $ Done $ if f a then Just a else Nothing+-}++-- Naming notes:+--+-- "head" and "next" are two alternative names for the same API. head sounds+-- apt in the context of lists but next sounds more apt in the context of+-- streams where we think in terms of generating and consuming the next element+-- rather than taking the head of some static/persistent structure.+--+-- We also want to keep the nomenclature consistent across folds and parsers,+-- "head" becomes even more unintuitive for parsers because there are two+-- possible variants viz. peek and next.+--+-- Also, the "head" fold creates confusion in situations like+-- https://github.com/composewell/streamly/issues/1404 where intuitive+-- expectation from head is to consume the entire stream and just give us the+-- head. There we want to convey the notion that we consume one element from+-- the stream and stop. The name "one" already being used in parsers for this+-- purpose sounds more apt from this perspective.+--+-- The source of confusion is perhaps due to the fact that some folds consume+-- the entire stream and others terminate early. It may have been clearer if we+-- had separate abstractions for the two use cases.++-- XXX We can possibly use "head" for the purposes of reducing the entire+-- stream to the head element i.e. take the head and drain the rest.++-- | Take one element from the stream and stop.+--+-- Definition:+--+-- >>> one = Scanl.maybe Just+--+-- This is similar to the stream 'Stream.uncons' operation.+--+{-# INLINE one #-}+one :: Monad m => Scanl m a (Maybe a)+one = maybe Just++-- | Returns the first element that satisfies the given predicate.+--+-- /Pre-release/+{-# INLINE findM #-}+findM :: Monad m => (a -> m Bool) -> Scanl m a (Maybe a)+findM predicate =+ Scanl step (return $ Partial ()) extract extract++ where++ step () a =+ let f r =+ if r+ then Done (Just a)+ else Partial ()+ in f <$> predicate a++ extract = const $ return Nothing++-- | Returns the first element that satisfies the given predicate.+--+{-# INLINE find #-}+find :: Monad m => (a -> Bool) -> Scanl m a (Maybe a)+find p = findM (return . p)++-- | 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@.+--+-- Definition:+--+-- >>> lookup x = fmap snd <$> Scanl.find ((== x) . fst)+--+{-# INLINE lookup #-}+lookup :: (Eq a, Monad m) => a -> Scanl m (a,b) (Maybe b)+lookup a0 = mkScant step (Partial ()) (const Nothing)++ where++ step () (a, b) =+ if a == a0+ then Done $ Just b+ else Partial ()++-- | Returns the first index that satisfies the given predicate.+--+{-# INLINE findIndex #-}+findIndex :: Monad m => (a -> Bool) -> Scanl m a (Maybe Int)+findIndex predicate = mkScant step (Partial 0) (const Nothing)++ where++ step i a =+ if predicate a+ then Done $ Just i+ else Partial (i + 1)+-}++-- | Returns the index of the latest element if the element satisfies the given+-- predicate.+--+{-# INLINE findIndices #-}+findIndices :: Monad m => (a -> Bool) -> Scanl m a (Maybe Int)+findIndices predicate =+ -- XXX implement by combining indexing and filtering scans+ fmap (either (Prelude.const Nothing) Just) $ mkScanl step (Left (-1))++ where++ step i a =+ if predicate a+ then Right (either id id i + 1)+ else Left (either id id i + 1)++-- | Returns the index of the latest element if the element matches the given+-- value.+--+-- Definition:+--+-- >>> elemIndices a = Scanl.findIndices (== a)+--+{-# INLINE elemIndices #-}+elemIndices :: (Monad m, Eq a) => a -> Scanl m a (Maybe Int)+elemIndices a = findIndices (== a)++{-+-- | Returns the first index where a given value is found in the stream.+--+-- Definition:+--+-- >>> elemIndex a = Scanl.findIndex (== a)+--+{-# INLINE elemIndex #-}+elemIndex :: (Eq a, Monad m) => a -> Scanl m a (Maybe Int)+elemIndex a = findIndex (== a)++------------------------------------------------------------------------------+-- To Boolean+------------------------------------------------------------------------------++-- Similar to 'eof' parser, but the fold consumes and discards an input element+-- when not at eof. XXX Remove or Rename to "eof"?++-- | Consume one element, return 'True' if successful else return 'False'. In+-- other words, test if the input is empty or not.+--+-- WARNING! It consumes one element if the stream is not empty. If that is not+-- what you want please use the eof parser instead.+--+-- Definition:+--+-- >>> null = fmap isJust Scanl.one+--+{-# INLINE null #-}+null :: Monad m => Scanl m a Bool+null = mkScant (\() _ -> Done False) (Partial ()) (const True)++-- | Returns 'True' if any element of the input satisfies the predicate.+--+-- Definition:+--+-- >>> any p = Scanl.lmap p Scanl.or+--+-- Example:+--+-- >>> Stream.toList $ Stream.scanl (Scanl.any (== 0)) $ Stream.fromList [1,0,1]+-- True+--+{-# INLINE any #-}+any :: Monad m => (a -> Bool) -> Scanl m a Bool+any predicate = mkScant step initial id++ where++ initial = Partial False++ step _ a =+ if predicate a+ then Done True+ else Partial False++-- | Return 'True' if the given element is present in the stream.+--+-- Definition:+--+-- >>> elem a = Scanl.any (== a)+--+{-# INLINE elem #-}+elem :: (Eq a, Monad m) => a -> Scanl m a Bool+elem a = any (== a)++-- | Returns 'True' if all elements of the input satisfy the predicate.+--+-- Definition:+--+-- >>> all p = Scanl.lmap p Scanl.and+--+-- Example:+--+-- >>> Stream.toList $ Stream.scanl (Scanl.all (== 0)) $ Stream.fromList [1,0,1]+-- False+--+{-# INLINE all #-}+all :: Monad m => (a -> Bool) -> Scanl m a Bool+all predicate = mkScant step initial id++ where++ initial = Partial True++ step _ a =+ if predicate a+ then Partial True+ else Done False++-- | Returns 'True' if the given element is not present in the stream.+--+-- Definition:+--+-- >>> notElem a = Scanl.all (/= a)+--+{-# INLINE notElem #-}+notElem :: (Eq a, Monad m) => a -> Scanl m a Bool+notElem a = all (/= a)++-- | Returns 'True' if all elements are 'True', 'False' otherwise+--+-- Definition:+--+-- >>> and = Scanl.all (== True)+--+{-# INLINE and #-}+and :: Monad m => Scanl m Bool Bool+and = all id++-- | Returns 'True' if any element is 'True', 'False' otherwise+--+-- Definition:+--+-- >>> or = Scanl.any (== True)+--+{-# INLINE or #-}+or :: Monad m => Scanl m Bool Bool+or = any id+-}++------------------------------------------------------------------------------+-- 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 = Stream.toList $ Stream.scanl (Fold.splitAt n Fold.toList Fold.toList) $ Stream.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],[])+--+-- > splitAt n f1 f2 = Fold.splitWith (,) (Fold.take n f1) f2+--+-- /Internal/++{-# INLINE splitAt #-}+splitAt+ :: Monad m+ => Int+ -> Scanl m a b+ -> Scanl m a c+ -> Scanl m a (b, c)+splitAt n fld = splitWith (,) (take n fld)+-}++------------------------------------------------------------------------------+-- Element Aware APIs+------------------------------------------------------------------------------+--+------------------------------------------------------------------------------+-- Binary APIs+------------------------------------------------------------------------------++{-# INLINE takingEndByM #-}+takingEndByM :: Monad m => (a -> m Bool) -> Scanl m a (Maybe a)+takingEndByM p = Scanl step initial extract extract++ where++ initial = return $ Partial Nothing'++ step _ a = do+ r <- p a+ return+ $ if r+ then Done $ Just a+ else Partial $ Just' a++ extract = return . toMaybe++-- |+--+-- >>> takingEndBy p = Scanl.takingEndByM (return . p)+--+{-# INLINE takingEndBy #-}+takingEndBy :: Monad m => (a -> Bool) -> Scanl m a (Maybe a)+takingEndBy p = takingEndByM (return . p)++{-# INLINE takingEndByM_ #-}+takingEndByM_ :: Monad m => (a -> m Bool) -> Scanl m a (Maybe a)+takingEndByM_ p = Scanl step initial extract extract++ where++ initial = return $ Partial Nothing'++ step _ a = do+ r <- p a+ return+ $ if r+ then Done Nothing+ else Partial $ Just' a++ extract = return . toMaybe++-- |+--+-- >>> takingEndBy_ p = Scanl.takingEndByM_ (return . p)+--+{-# INLINE takingEndBy_ #-}+takingEndBy_ :: Monad m => (a -> Bool) -> Scanl m a (Maybe a)+takingEndBy_ p = takingEndByM_ (return . p)++{-# INLINE droppingWhileM #-}+droppingWhileM :: Monad m => (a -> m Bool) -> Scanl m a (Maybe a)+droppingWhileM p = Scanl step initial extract extract++ where++ initial = return $ Partial Nothing'++ step Nothing' a = do+ r <- p a+ return+ $ Partial+ $ if r+ then Nothing'+ else Just' a+ step _ a = return $ Partial $ Just' a++ extract = return . toMaybe++-- |+-- >>> droppingWhile p = Scanl.droppingWhileM (return . p)+--+{-# INLINE droppingWhile #-}+droppingWhile :: Monad m => (a -> Bool) -> Scanl m a (Maybe a)+droppingWhile p = droppingWhileM (return . p)++------------------------------------------------------------------------------+-- Binary splitting on a separator+------------------------------------------------------------------------------++{-+data SplitOnSeqState acc a rb rh w ck =+ SplitOnSeqEmpty !acc+ | SplitOnSeqSingle !acc !a+ | SplitOnSeqWord !acc !Int !w+ | SplitOnSeqWordLoop !acc !w+ | SplitOnSeqKR !acc !Int !rb !rh+ | SplitOnSeqKRLoop !acc !ck !rb !rh++-- XXX Need to add tests for takeEndBySeq, we have tests for takeEndBySeq_ .++-- | Continue taking the input until the input sequence matches the supplied+-- sequence, taking the supplied sequence as well. If the pattern is empty this+-- acts as an identity fold.+--+-- >>> s = Stream.fromList "hello there. How are you?"+-- >>> f = Fold.takeEndBySeq (Array.fromList "re") Fold.toList+-- >>> Stream.toList $ Stream.scanl f s+-- "hello there"+--+-- >>> Stream.toList $ Stream.scanl Fold.toList $ Stream.toList $ Stream.scanlMany f s+-- ["hello there",". How are"," you?"]+--+-- /Pre-release/+{-# INLINE takeEndBySeq #-}+takeEndBySeq :: forall m a b. (MonadIO m, Unbox a, Enum a, Eq a) =>+ Array.Array a+ -> Scanl m a b+ -> Scanl m a b+takeEndBySeq patArr (Fold fstep finitial fextract ffinal) =+ Fold step initial extract final++ where++ patLen = Array.length patArr++ initial = do+ res <- finitial+ case res of+ Partial acc+ | patLen == 0 ->+ -- XXX Should we match nothing or everything on empty+ -- pattern?+ -- Done <$> ffinal acc+ return $ Partial $ SplitOnSeqEmpty acc+ | patLen == 1 -> do+ pat <- liftIO $ Array.unsafeGetIndexIO 0 patArr+ return $ Partial $ SplitOnSeqSingle acc pat+ | SIZE_OF(a) * patLen <= sizeOf (Proxy :: Proxy Word) ->+ return $ Partial $ SplitOnSeqWord acc 0 0+ | otherwise -> do+ rb <- liftIO $ RingArray.emptyOf patLen+ return $ Partial $ SplitOnSeqKR acc 0 rb 0+ Done b -> return $ Done b++ -- Word pattern related+ maxIndex = patLen - 1++ elemBits = SIZE_OF(a) * 8++ wordMask :: Word+ wordMask = (1 `shiftL` (elemBits * patLen)) - 1++ wordPat :: Word+ wordPat = wordMask .&. Array.scanl' addToWord 0 patArr++ addToWord wd a = (wd `shiftL` elemBits) .|. fromIntegral (fromEnum a)++ -- For Rabin-Karp search+ 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?+ -- XXX Need to keep this cached across fold calls in foldmany+ -- XXX We may need refold to inject the cached state instead of+ -- initializing the state every time.+ -- XXX Allocation of ring buffer should also be done once+ patHash = Array.scanl' addCksum 0 patArr++ step (SplitOnSeqEmpty s) x = do+ res <- fstep s x+ case res of+ Partial s1 -> return $ Partial $ SplitOnSeqEmpty s1+ Done b -> return $ Done b+ step (SplitOnSeqSingle s pat) x = do+ res <- fstep s x+ case res of+ Partial s1+ | pat /= x -> return $ Partial $ SplitOnSeqSingle s1 pat+ | otherwise -> Done <$> ffinal s1+ Done b -> return $ Done b+ step (SplitOnSeqWord s idx wrd) x = do+ res <- fstep s x+ let wrd1 = addToWord wrd x+ case res of+ Partial s1+ | idx == maxIndex -> do+ if wrd1 .&. wordMask == wordPat+ then Done <$> ffinal s1+ else return $ Partial $ SplitOnSeqWordLoop s1 wrd1+ | otherwise ->+ return $ Partial $ SplitOnSeqWord s1 (idx + 1) wrd1+ Done b -> return $ Done b+ step (SplitOnSeqWordLoop s wrd) x = do+ res <- fstep s x+ let wrd1 = addToWord wrd x+ case res of+ Partial s1+ | wrd1 .&. wordMask == wordPat ->+ Done <$> ffinal s1+ | otherwise ->+ return $ Partial $ SplitOnSeqWordLoop s1 wrd1+ Done b -> return $ Done b+ step (SplitOnSeqKR s idx rb rh) x = do+ res <- fstep s x+ case res of+ Partial s1 -> do+ rh1 <- liftIO $ RingArray.unsafeInsert rb rh x+ if idx == maxIndex+ then do+ let fld = RingArray.unsafeFoldRing (RingArray.ringCapacity rb)+ let !ringHash = fld addCksum 0 rb+ if ringHash == patHash && RingArray.unsafeEqArray rb rh1 patArr+ then Done <$> ffinal s1+ else return $ Partial $ SplitOnSeqKRLoop s1 ringHash rb rh1+ else+ return $ Partial $ SplitOnSeqKR s1 (idx + 1) rb rh1+ Done b -> return $ Done b+ step (SplitOnSeqKRLoop s cksum rb rh) x = do+ res <- fstep s x+ case res of+ Partial s1 -> do+ (old :: a) <- RingArray.unsafeGetIndex rh rb+ rh1 <- liftIO $ RingArray.unsafeInsert rb rh x+ let ringHash = deltaCksum cksum old x+ if ringHash == patHash && RingArray.unsafeEqArray rb rh1 patArr+ then Done <$> ffinal s1+ else return $ Partial $ SplitOnSeqKRLoop s1 ringHash rb rh1+ Done b -> return $ Done b++ extractFunc fex state =+ let st =+ case state of+ SplitOnSeqEmpty s -> s+ SplitOnSeqSingle s _ -> s+ SplitOnSeqWord s _ _ -> s+ SplitOnSeqWordLoop s _ -> s+ SplitOnSeqKR s _ _ _ -> s+ SplitOnSeqKRLoop s _ _ _ -> s+ in fex st++ extract = extractFunc fextract++ final = extractFunc ffinal++-- | Like 'takeEndBySeq' but discards the matched sequence.+--+-- /Pre-release/+--+{-# INLINE takeEndBySeq_ #-}+takeEndBySeq_ :: forall m a b. (MonadIO m, Unbox a, Enum a, Eq a) =>+ Array.Array a+ -> Scanl m a b+ -> Scanl m a b+takeEndBySeq_ patArr (Fold fstep finitial fextract ffinal) =+ Fold step initial extract final++ where++ patLen = Array.length patArr++ initial = do+ res <- finitial+ case res of+ Partial acc+ | patLen == 0 ->+ -- XXX Should we match nothing or everything on empty+ -- pattern?+ -- Done <$> ffinal acc+ return $ Partial $ SplitOnSeqEmpty acc+ | patLen == 1 -> do+ pat <- liftIO $ Array.unsafeGetIndexIO 0 patArr+ return $ Partial $ SplitOnSeqSingle acc pat+ -- XXX Need to add tests for this case+ | SIZE_OF(a) * patLen <= sizeOf (Proxy :: Proxy Word) ->+ return $ Partial $ SplitOnSeqWord acc 0 0+ | otherwise -> do+ rb <- liftIO $ RingArray.emptyOf patLen+ return $ Partial $ SplitOnSeqKR acc 0 rb 0+ Done b -> return $ Done b++ -- Word pattern related+ maxIndex = patLen - 1++ elemBits = SIZE_OF(a) * 8++ wordMask :: Word+ wordMask = (1 `shiftL` (elemBits * patLen)) - 1++ elemMask :: Word+ elemMask = (1 `shiftL` elemBits) - 1++ wordPat :: Word+ wordPat = wordMask .&. Array.scanl' addToWord 0 patArr++ addToWord wd a = (wd `shiftL` elemBits) .|. fromIntegral (fromEnum a)++ -- For Rabin-Karp search+ 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?+ -- XXX Need to keep this cached across fold calls in foldMany+ -- XXX We may need refold to inject the cached state instead of+ -- initializing the state every time.+ -- XXX Allocation of ring buffer should also be done once+ patHash = Array.scanl' addCksum 0 patArr++ step (SplitOnSeqEmpty s) x = do+ res <- fstep s x+ case res of+ Partial s1 -> return $ Partial $ SplitOnSeqEmpty s1+ Done b -> return $ Done b+ step (SplitOnSeqSingle s pat) x = do+ if pat /= x+ then do+ res <- fstep s x+ case res of+ Partial s1 -> return $ Partial $ SplitOnSeqSingle s1 pat+ Done b -> return $ Done b+ else Done <$> ffinal s+ step (SplitOnSeqWord s idx wrd) x = do+ let wrd1 = addToWord wrd x+ if idx == maxIndex+ then do+ if wrd1 .&. wordMask == wordPat+ then Done <$> ffinal s+ else return $ Partial $ SplitOnSeqWordLoop s wrd1+ else return $ Partial $ SplitOnSeqWord s (idx + 1) wrd1+ step (SplitOnSeqWordLoop s wrd) x = do+ let wrd1 = addToWord wrd x+ old = (wordMask .&. wrd)+ `shiftR` (elemBits * (patLen - 1))+ res <- fstep s (toEnum $ fromIntegral old)+ case res of+ Partial s1+ | wrd1 .&. wordMask == wordPat ->+ Done <$> ffinal s1+ | otherwise ->+ return $ Partial $ SplitOnSeqWordLoop s1 wrd1+ Done b -> return $ Done b+ step (SplitOnSeqKR s idx rb rh) x = do+ rh1 <- liftIO $ RingArray.unsafeInsert rb rh x+ if idx == maxIndex+ then do+ let fld = RingArray.unsafeFoldRing (RingArray.ringCapacity rb)+ let !ringHash = fld addCksum 0 rb+ if ringHash == patHash && RingArray.unsafeEqArray rb rh1 patArr+ then Done <$> ffinal s+ else return $ Partial $ SplitOnSeqKRLoop s ringHash rb rh1+ else return $ Partial $ SplitOnSeqKR s (idx + 1) rb rh1+ step (SplitOnSeqKRLoop s cksum rb rh) x = do+ old <- RingArray.unsafeGetIndex rh rb+ res <- fstep s old+ case res of+ Partial s1 -> do+ rh1 <- liftIO $ RingArray.unsafeInsert rb rh x+ let ringHash = deltaCksum cksum old x+ if ringHash == patHash && RingArray.unsafeEqArray rb rh1 patArr+ then Done <$> ffinal s1+ else return $ Partial $ SplitOnSeqKRLoop s1 ringHash rb rh1+ Done b -> return $ Done b++ -- XXX extract should return backtrack count as well. If the fold+ -- terminates early inside extract, we may still have buffered data+ -- remaining which will be lost if we do not communicate that to the+ -- driver.+ extractFunc fex state = do+ let consumeWord s n wrd = do+ if n == 0+ then fex s+ else do+ let old = elemMask .&. (wrd `shiftR` (elemBits * (n - 1)))+ r <- fstep s (toEnum $ fromIntegral old)+ case r of+ Partial s1 -> consumeWord s1 (n - 1) wrd+ Done b -> return b++ let consumeRing s n rb rh =+ if n == 0+ then fex s+ else do+ old <- RingArray.unsafeGetIndex rh rb+ let rh1 = RingArray.advance rb rh+ r <- fstep s old+ case r of+ Partial s1 -> consumeRing s1 (n - 1) rb rh1+ Done b -> return b++ case state of+ SplitOnSeqEmpty s -> fex s+ SplitOnSeqSingle s _ -> fex s+ SplitOnSeqWord s idx wrd -> consumeWord s idx wrd+ SplitOnSeqWordLoop s wrd -> consumeWord s patLen wrd+ SplitOnSeqKR s idx rb _ -> consumeRing s idx rb 0+ SplitOnSeqKRLoop s _ rb rh -> consumeRing s patLen rb rh++ extract = extractFunc fextract++ final = extractFunc ffinal+ -}++------------------------------------------------------------------------------+-- Distributing+------------------------------------------------------------------------------+--+-- | Distribute one copy of the stream to each scan and zip the results.+--+-- @+-- |-------Scanl m a b--------|+-- ---stream m a---| |---m (b,c)+-- |-------Scanl m a c--------|+-- @+--+-- Definition:+--+-- >>> tee = Scanl.teeWith (,)+--+-- Example:+--+-- >>> t = Scanl.tee Scanl.sum Scanl.length+-- >>> Stream.toList $ Stream.scanl t (Stream.enumerateFromTo 1.0 10.0)+-- [(0.0,0),(1.0,1),(3.0,2),(6.0,3),(10.0,4),(15.0,5),(21.0,6),(28.0,7),(36.0,8),(45.0,9),(55.0,10)]+--+{-# INLINE tee #-}+tee :: Monad m => Scanl m a b -> Scanl m a c -> Scanl m a (b,c)+tee = teeWith (,)++-- XXX use unboxed Array for output to scale it to a large number of consumers?++-- | Distribute one copy of the stream to each scan and collect the results in+-- a container.+--+-- @+--+-- |-------Scanl m a b--------|+-- ---stream m a---| |---m [b]+-- |-------Scanl m a b--------|+-- | |+-- ...+-- @+--+-- >>> Stream.toList $ Stream.scanl (Scanl.distribute [Scanl.sum, Scanl.length]) (Stream.enumerateFromTo 1 5)+-- [[0,0],[1,1],[3,2],[6,3],[10,4],[15,5]]+--+-- >>> distribute = Prelude.foldr (Scanl.teeWith (:)) (Scanl.const [])+--+-- This is the consumer side dual of the producer side 'sequence' operation.+--+-- Stops as soon as any of the scans stop.+--+{-# INLINE distribute #-}+distribute :: Monad m => [Scanl m a b] -> Scanl m a [b]+distribute = Prelude.foldr (teeWith (:)) (const [])++------------------------------------------------------------------------------+-- Partitioning+------------------------------------------------------------------------------++{-+{-# INLINE partitionByMUsing #-}+partitionByMUsing :: Monad m =>+ ( (x -> y -> (x, y))+ -> Scanl m (Either b c) x+ -> Scanl m (Either b c) y+ -> Scanl m (Either b c) (x, y)+ )+ -> (a -> m (Either b c))+ -> Scanl m b x+ -> Scanl m c y+ -> Scanl m a (x, y)+partitionByMUsing t f fld1 fld2 =+ let l = lmap (fromLeft undefined) fld1 -- :: Fold m (Either b c) x+ r = lmap (fromRight undefined) fld2 -- :: Fold m (Either b c) y+ in lmapM f (t (,) (filter isLeft l) (filter isRight r))+ -}++data PartState sL sR = PartLeft !sL !sR | PartRight !sL !sR++-- | Partition the input over two scans using an 'Either' partitioning+-- predicate.+--+-- @+--+-- |-------Scanl b x--------|+-- -----stream m a --> (Either b c)----| |----(x,y)+-- |-------Scanl c y--------|+-- @+--+-- Example, send input to either scan randomly:+--+-- >>> :set -package random+-- >>> import System.Random (randomIO)+-- >>> randomly a = randomIO >>= \x -> return $ if x then Left a else Right a+-- >>> f = Scanl.partitionByM randomly Scanl.length Scanl.length+-- >>> Stream.toList $ Stream.scanl f (Stream.enumerateFromTo 1 10)+-- ...+--+-- Example, send input to the two scans in a proportion of 2:1:+--+-- >>> :set -fno-warn-unrecognised-warning-flags+-- >>> :set -fno-warn-x-partial+-- >>> :{+-- proportionately m n = do+-- ref <- newIORef $ cycle $ concat [replicate m Left, replicate n Right]+-- return $ \a -> do+-- r <- readIORef ref+-- writeIORef ref $ tail r+-- return $ Prelude.head r a+-- :}+--+-- >>> :{+-- main = do+-- g <- proportionately 2 1+-- let f = Scanl.partitionByM g Scanl.length Scanl.length+-- r <- Stream.toList $ Stream.scanl f (Stream.enumerateFromTo (1 :: Int) 10)+-- print r+-- :}+--+-- >>> main+-- ...+--+--+-- This is the consumer side dual of the producer side 'mergeBy' operation.+--+-- Terminates as soon as any of the scans terminate.+--+-- /Pre-release/+{-# INLINE partitionByM #-}+partitionByM :: Monad m+ => (a -> m (Either b c)) -> Scanl m b x -> Scanl m c x -> Scanl m a x+partitionByM f+ (Scanl stepL initialL extractL finalL)+ (Scanl stepR initialR extractR finalR) =+ Scanl step initial extract final++ where++ initial = do+ resL <- initialL+ resR <- initialR+ return+ $ case resL of+ Done bl -> Done bl+ Partial sl ->+ case resR of+ Partial sr -> Partial $ PartLeft sl sr+ Done br -> Done br++ runBoth sL sR a = do+ pRes <- f a+ case pRes of+ Left b -> do+ resL <- stepL sL b+ case resL of+ Partial s -> return $ Partial $ PartLeft s sR+ Done x -> return $ Done x+ Right c -> do+ resR <- stepR sR c+ case resR of+ Partial s -> return $ Partial $ PartRight sL s+ Done x -> return $ Done x++ step (PartLeft sL sR) = runBoth sL sR+ step (PartRight sL sR) = runBoth sL sR++ extract (PartLeft sL _) = extractL sL+ extract (PartRight _ sR) = extractR sR++ final (PartLeft sL sR) = finalR sR *> finalL sL+ final (PartRight sL sR) = finalL sL *> finalR sR++{-+-- | Similar to 'partitionByM' but terminates when the first fold terminates.+--+{-# INLINE partitionByFstM #-}+partitionByFstM :: Monad m+ => (a -> m (Either b c)) -> Scanl m b x -> Scanl m c y -> Scanl m a (x, y)+partitionByFstM = partitionByMUsing teeWithFst++-- | Similar to 'partitionByM' but terminates when any fold terminates.+--+{-# INLINE partitionByMinM #-}+partitionByMinM :: Monad m =>+ (a -> m (Either b c)) -> Scanl m b x -> Scanl m c y -> Scanl m a (x, y)+partitionByMinM = partitionByMUsing teeWithMin+-}++-- Note: we could use (a -> Bool) instead of (a -> Either b c), but the latter+-- makes the signature clearer as to which case belongs to which scan.+-- XXX need to check the performance in both cases.++-- | Same as 'partitionByM' but with a pure partition function.+--+-- Example, count even and odd numbers in a stream:+--+-- >>> :{+-- let f = Scanl.partitionBy (\n -> if even n then Left n else Right n)+-- (fmap (("Even " ++) . show) Scanl.length)+-- (fmap (("Odd " ++) . show) Scanl.length)+-- in Stream.toList $ Stream.postscanl f (Stream.enumerateFromTo 1 10)+-- :}+-- ["Odd 1","Even 1","Odd 2","Even 2","Odd 3","Even 3","Odd 4","Even 4","Odd 5","Even 5"]+--+-- /Pre-release/+{-# INLINE partitionBy #-}+partitionBy :: Monad m+ => (a -> Either b c) -> Scanl m b x -> Scanl m c x -> Scanl m a x+partitionBy f = partitionByM (return . f)++-- | Compose two scans such that the combined scan accepts a stream of 'Either'+-- and routes the 'Left' values to the first scan and 'Right' values to the+-- second scan.+--+-- Definition:+--+-- >>> partition = Scanl.partitionBy id+--+{-# INLINE partition #-}+partition :: Monad m+ => Scanl m b x -> Scanl m c x -> Scanl m (Either b c) x+partition = partitionBy id++{-+-- | Send one item to each fold in a round-robin fashion. This is the consumer+-- side dual of producer side 'mergeN' operation.+--+-- partitionN :: Monad m => [Scanl m a b] -> Scanl m a [b]+-- partitionN fs = Fold step begin done+-}++------------------------------------------------------------------------------+-- Unzipping+------------------------------------------------------------------------------++{-# INLINE unzipWithMUsing #-}+unzipWithMUsing :: Monad m =>+ ( (x -> y -> (x, y))+ -> Scanl m (b, c) x+ -> Scanl m (b, c) y+ -> Scanl m (b, c) (x, y)+ )+ -> (a -> m (b, c))+ -> Scanl m b x+ -> Scanl m c y+ -> Scanl m a (x, y)+unzipWithMUsing t f fld1 fld2 =+ let f1 = lmap fst fld1 -- :: Scanl m (b, c) b+ f2 = lmap snd fld2 -- :: Scanl m (b, c) c+ in lmapM f (t (,) f1 f2)++-- | Like 'unzipWith' but with a monadic splitter function.+--+-- Definition:+--+-- >>> unzipWithM k f1 f2 = Scanl.lmapM k (Scanl.unzip f1 f2)+--+-- /Pre-release/+{-# INLINE unzipWithM #-}+unzipWithM :: Monad m+ => (a -> m (b,c)) -> Scanl m b x -> Scanl m c y -> Scanl m a (x,y)+unzipWithM = unzipWithMUsing teeWith++{-+-- | Similar to 'unzipWithM' but terminates when the first fold terminates.+--+{-# INLINE unzipWithFstM #-}+unzipWithFstM :: Monad m =>+ (a -> m (b, c)) -> Scanl m b x -> Scanl m c y -> Scanl m a (x, y)+unzipWithFstM = unzipWithMUsing teeWithFst++-- | Similar to 'unzipWithM' but terminates when any fold terminates.+--+{-# INLINE unzipWithMaxM #-}+unzipWithMaxM :: Monad m =>+ (a -> m (b,c)) -> Scanl m b x -> Scanl m c y -> Scanl m a (x,y)+unzipWithMaxM = unzipWithMUsing teeWithMax+-}++-- | Split elements in the input stream into two parts using a pure splitter+-- function, direct each part to a different scan and zip the results.+--+-- Definitions:+--+-- >>> unzipWith f = Scanl.unzipWithM (return . f)+-- >>> unzipWith f fld1 fld2 = Scanl.lmap f (Scanl.unzip fld1 fld2)+--+-- This scan terminates as soon as any of the input scans terminate.+--+-- /Pre-release/+{-# INLINE unzipWith #-}+unzipWith :: Monad m+ => (a -> (b,c)) -> Scanl m b x -> Scanl m c y -> Scanl m a (x,y)+unzipWith f = unzipWithM (return . f)++-- | Send the elements of tuples in a stream of tuples through two different+-- scans.+--+-- @+--+-- |-------Scanl m a x--------|+-- ---------stream of (a,b)--| |----m (x,y)+-- |-------Scanl m b y--------|+--+-- @+--+-- Definition:+--+-- >>> unzip = Scanl.unzipWith id+--+-- This is the consumer side dual of the producer side 'zip' operation.+--+{-# INLINE unzip #-}+unzip :: Monad m => Scanl m a x -> Scanl m b y -> Scanl m (a,b) (x,y)+unzip = unzipWith id++------------------------------------------------------------------------------+-- Combining streams and scans - Zipping+------------------------------------------------------------------------------++-- XXX These can be implemented using the fold scan, using the stream as a+-- state.+-- XXX Stream Skip state cannot be efficiently handled in folds but can be+-- handled in parsers using the Continue facility. See zipWithM in the Parser+-- module.+--+-- cmpBy, eqBy, isPrefixOf, isSubsequenceOf etc can be implemented using+-- zipStream.++-- | Zip a stream with the input of a scan using the supplied function.+--+-- /Unimplemented/+--+{-# INLINE zipStreamWithM #-}+zipStreamWithM :: -- Monad m =>+ (a -> b -> m c) -> Stream m a -> Scanl m c x -> Scanl m b x+zipStreamWithM = undefined++-- | Zip a stream with the input of a scan.+--+-- >>> zip = Scanl.zipStreamWithM (curry return)+--+-- /Unimplemented/+--+{-# INLINE zipStream #-}+zipStream :: Monad m => Stream m a -> Scanl m (a, b) x -> Scanl m b x+zipStream = zipStreamWithM (curry return)++-- | Pair each element of a scan input with its index, starting from index 0.+--+{-# INLINE indexingWith #-}+indexingWith :: Monad m => Int -> (Int -> Int) -> Scanl m a (Maybe (Int, a))+indexingWith i f = fmap toMaybe $ mkScanl step initial++ where++ initial = Nothing'++ step Nothing' a = Just' (i, a)+ step (Just' (n, _)) a = Just' (f n, a)++-- |+-- >>> indexing = Scanl.indexingWith 0 (+ 1)+--+{-# INLINE indexing #-}+indexing :: Monad m => Scanl m a (Maybe (Int, a))+indexing = indexingWith 0 (+ 1)++-- |+-- >>> indexingRev n = Scanl.indexingWith n (subtract 1)+--+{-# INLINE indexingRev #-}+indexingRev :: Monad m => Int -> Scanl m a (Maybe (Int, a))+indexingRev n = indexingWith n (subtract 1)++-- | Pair each element of a scan input with its index, starting from index 0.+--+-- >>> indexed = Scanl.postscanlMaybe Scanl.indexing+--+{-# INLINE indexed #-}+indexed :: Monad m => Scanl m (Int, a) b -> Scanl m a b+indexed = postscanlMaybe indexing++-- | Change the predicate function of a Scanl from @a -> b@ to accept an+-- additional state input @(s, a) -> b@. Convenient to filter with an+-- addiitonal index or time input.+--+-- >>> filterWithIndex = Scanl.with Scanl.indexed Scanl.filter+--+-- @+-- filterWithAbsTime = with timestamped filter+-- filterWithRelTime = with timeIndexed filter+-- @+--+-- /Pre-release/+{-# INLINE with #-}+with ::+ (Scanl m (s, a) b -> Scanl m a b)+ -> (((s, a) -> c) -> Scanl m (s, a) b -> Scanl m (s, a) b)+ -> (((s, a) -> c) -> Scanl m a b -> Scanl m a b)+with f comb g = f . comb g . lmap snd++-- XXX Implement as a filter+-- sampleFromthen :: Monad m => Int -> Int -> Scanl m a (Maybe a)++-- | @sampleFromthen offset stride@ samples the element at @offset@ index and+-- then every element at strides of @stride@.+--+{-# INLINE sampleFromthen #-}+sampleFromthen :: Monad m => Int -> Int -> Scanl m a b -> Scanl m a b+sampleFromthen offset size =+ with indexed filter (\(i, _) -> (i + offset) `mod` size == 0)++------------------------------------------------------------------------------+-- Nesting+------------------------------------------------------------------------------++{-+-- | @concatSequence f t@ applies folds from stream @t@ sequentially and+-- collects the results using the fold @f@.+--+-- /Unimplemented/+--+{-# INLINE concatSequence #-}+concatSequence ::+ -- IsStream t =>+ Fold m b c -> t (Scanl m a b) -> Scanl m a c+concatSequence _f _p = undefined++-- | Group the input stream into groups of elements between @low@ and @high@.+-- Collection starts in chunks of @low@ and then keeps doubling until we reach+-- @high@. Each chunk is folded using the provided fold function.+--+-- This could be useful, for example, when we are folding a stream of unknown+-- size to a stream of arrays and we want to minimize the number of+-- allocations.+--+-- NOTE: this would be an application of "many" using a terminating fold.+--+-- /Unimplemented/+--+{-# INLINE chunksBetween #-}+chunksBetween :: -- Monad m =>+ Int -> Int -> Scanl m a b -> Scanl m b c -> Scanl m a c+chunksBetween _low _high _f1 _f2 = undefined+-}++-- | A scan 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.Data.Array" instead.+--+-- >>> toStream = fmap Stream.fromList Scanl.toList+--+-- /Pre-release/+{-# INLINE toStream #-}+toStream :: (Monad m, Monad n) => Scanl m a (Stream n a)+toStream = fmap StreamD.fromList toList++-- 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.+--+-- >>> toStreamRev = fmap Stream.fromList Scanl.toListRev+--+-- /Warning!/ working on large streams accumulated as buffers in memory could+-- be very inefficient, consider using "Streamly.Data.Array" instead.+--+-- /Pre-release/++-- xn : ... : x2 : x1 : []+{-# INLINE toStreamRev #-}+toStreamRev :: (Monad m, Monad n) => Scanl m a (Stream n a)+toStreamRev = fmap StreamD.fromList toListRev++-- XXX This does not fuse. It contains a recursive step function. We will need+-- a Skip input constructor in the fold type to make it fuse.++-- | Unfold and flatten the input stream of a scan.+--+-- @+-- Stream.scanl (unfoldMany u f) == Stream.scanl f . Stream.unfoldMany u+-- @+--+-- /Pre-release/+{-# INLINE unfoldMany #-}+unfoldMany :: Monad m => Unfold m a b -> Scanl m b c -> Scanl m a c+unfoldMany (Unfold ustep inject) (Scanl fstep initial extract final) =+ Scanl consume initial extract final++ where++ {-# INLINE produce #-}+ produce fs us = do+ ures <- ustep us+ case ures of+ StreamD.Yield b us1 -> do+ fres <- fstep fs b+ case fres of+ Partial fs1 -> produce fs1 us1+ -- XXX What to do with the remaining stream?+ Done c -> return $ Done c+ StreamD.Skip us1 -> produce fs us1+ StreamD.Stop -> return $ Partial fs++ {-# INLINE_LATE consume #-}+ consume s a = inject a >>= produce s++-- | Get the bottom most @n@ elements using the supplied comparison function.+--+{-# INLINE bottomBy #-}+bottomBy :: (MonadIO m, Unbox a) =>+ (a -> a -> Ordering)+ -> Int+ -> Scanl m a (MutArray a)+bottomBy cmp n = Scanl step initial extract extract++ where++ initial = do+ arr <- MA.emptyOf' n+ if n <= 0+ then return $ Done arr+ else return $ Partial (arr, 0)++ step (arr, i) x =+ if i < n+ then do+ arr' <- MA.snoc arr x+ MA.bubble cmp arr'+ return $ Partial (arr', i + 1)+ else do+ x1 <- MA.unsafeGetIndex (i - 1) arr+ case x `cmp` x1 of+ LT -> do+ MA.unsafePutIndex (i - 1) arr x+ MA.bubble cmp arr+ return $ Partial (arr, i)+ _ -> return $ Partial (arr, i)++ extract = return . fst++-- | Get the top @n@ elements using the supplied comparison function.+--+-- To get bottom n elements instead:+--+-- >>> bottomBy cmp = Scanl.topBy (flip cmp)+--+-- Example:+--+-- >>> stream = Stream.fromList [2::Int,7,9,3,1,5,6,11,17]+-- >>> Stream.toList (Stream.scanl (Scanl.topBy compare 3) stream) >>= mapM MutArray.toList+-- [[],[17],[17,11],[17,11,9],[17,11,9],[17,11,9],[17,11,9],[17,11,9],[17,11,9],[17,11,9]]+--+-- /Pre-release/+--+{-# INLINE topBy #-}+topBy :: (MonadIO m, Unbox a) =>+ (a -> a -> Ordering)+ -> Int+ -> Scanl m a (MutArray a)+topBy cmp = bottomBy (flip cmp)++-- | Scan the input stream to top n elements.+--+-- Definition:+--+-- >>> top = Scanl.topBy compare+--+-- >>> stream = Stream.fromList [2::Int,7,9,3,1,5,6,11,17]+-- >>> Stream.toList (Stream.scanl (Scanl.top 3) stream) >>= mapM MutArray.toList+-- [[],[17],[17,11],[17,11,9],[17,11,9],[17,11,9],[17,11,9],[17,11,9],[17,11,9],[17,11,9]]+--+-- /Pre-release/+{-# INLINE top #-}+top :: (MonadIO m, Unbox a, Ord a) => Int -> Scanl m a (MutArray a)+top = bottomBy $ flip compare++-- | Scan the input stream to bottom n elements.+--+-- Definition:+--+-- >>> bottom = Scanl.bottomBy compare+--+-- >>> stream = Stream.fromList [2::Int,7,9,3,1,5,6,11,17]+-- >>> Stream.toList (Stream.scanl (Scanl.bottom 3) stream) >>= mapM MutArray.toList+-- [[],[1],[1,2],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3]]+--+-- /Pre-release/+{-# INLINE bottom #-}+bottom :: (MonadIO m, Unbox a, Ord a) => Int -> Scanl m a (MutArray a)+bottom = bottomBy compare++{-+------------------------------------------------------------------------------+-- Interspersed parsing+------------------------------------------------------------------------------++data IntersperseQState fs ps =+ IntersperseQUnquoted !fs !ps+ | IntersperseQQuoted !fs !ps+ | IntersperseQQuotedEsc !fs !ps++-- Useful for parsing CSV with quoting and escaping+{-# INLINE intersperseWithQuotes #-}+intersperseWithQuotes :: (Monad m, Eq a) =>+ a -> a -> a -> Scanl m a b -> Scanl m b c -> Scanl m a c+intersperseWithQuotes+ quote+ esc+ separator+ (Scanl stepL initialL _ finalL)+ (Scanl stepR initialR extractR finalR) = Scanl step initial extract final++ where++ errMsg p status =+ error $ "intersperseWithQuotes: " ++ p ++ " parsing fold cannot "+ ++ status ++ " without input"++ {-# INLINE initL #-}+ initL mkState = do+ resL <- initialL+ case resL of+ Partial sL ->+ return $ Partial $ mkState sL+ Done _ ->+ errMsg "content" "succeed"++ initial = do+ res <- initialR+ case res of+ Partial sR -> initL (IntersperseQUnquoted sR)+ Done b -> return $ Done b++ {-# INLINE collect #-}+ collect nextS sR b = do+ res <- stepR sR b+ case res of+ Partial s ->+ initL (nextS s)+ Done c -> return (Done c)++ {-# INLINE process #-}+ process a sL sR nextState = do+ r <- stepL sL a+ case r of+ Partial s -> return $ Partial (nextState sR s)+ Done b -> collect nextState sR b++ {-# INLINE processQuoted #-}+ processQuoted a sL sR nextState = do+ r <- stepL sL a+ case r of+ Partial s -> return $ Partial (nextState sR s)+ Done _ -> do+ _ <- finalR sR+ error "Collecting fold finished inside quote"++ step (IntersperseQUnquoted sR sL) a+ | a == separator = do+ b <- finalL sL+ collect IntersperseQUnquoted sR b+ | a == quote = processQuoted a sL sR IntersperseQQuoted+ | otherwise = process a sL sR IntersperseQUnquoted++ step (IntersperseQQuoted sR sL) a+ | a == esc = processQuoted a sL sR IntersperseQQuotedEsc+ | a == quote = process a sL sR IntersperseQUnquoted+ | otherwise = processQuoted a sL sR IntersperseQQuoted++ step (IntersperseQQuotedEsc sR sL) a =+ processQuoted a sL sR IntersperseQQuoted++ extract (IntersperseQUnquoted sR _) = extractR sR+ extract (IntersperseQQuoted _ _) =+ error "intersperseWithQuotes: finished inside quote"+ extract (IntersperseQQuotedEsc _ _) =+ error "intersperseWithQuotes: finished inside quote, at escape char"++ final (IntersperseQUnquoted sR sL) = finalL sL *> finalR sR+ final (IntersperseQQuoted sR sL) = do+ _ <- finalR sR+ _ <- finalL sL+ error "intersperseWithQuotes: finished inside quote"+ final (IntersperseQQuotedEsc sR sL) = do+ _ <- finalR sR+ _ <- finalL sL+ error "intersperseWithQuotes: finished inside quote, at escape char"+-}
+ src/Streamly/Internal/Data/Scanl/Container.hs view
@@ -0,0 +1,850 @@+{-# LANGUAGE CPP #-}+-- |+-- Module : Streamly.Internal.Data.Scanl.Container+-- Copyright : (c) 2019 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--++module Streamly.Internal.Data.Scanl.Container+ (+ -- * Set operations+ toSet+ , toIntSet+ , countDistinct+ , countDistinctInt+ , nub+ , nubInt++ -- * Map operations+ -- , frequency++ -- ** Demultiplexing+ -- | Direct values in the input stream to different scans using an n-ary+ -- scan selector. 'demux' is a generalization of 'classify' (and+ -- 'partition') where each key of the classifier can use a different scan.+ --+ -- You need to see only 'demux' if you are looking to find the capabilities+ -- of these combinators, all others are variants of that.++ {-+ -- *** Output is a container+ -- | The fold state snapshot returns the key-value container of in-progress+ -- folds.+ , demuxToContainer+ , demuxToContainerIO+ , demuxToMap+ , demuxToMapIO++ -- *** Input is explicit key-value tuple+ -- | Like above but inputs are in explicit key-value pair form.+ , demuxKvToContainer+ , demuxKvToMap++ -- *** Scan of finished fold results+ -- | Like above, but the resulting fold state snapshot contains the key+ -- value container as well as the finished key result if a fold in the+ -- container finished.+ -}+ , demuxGeneric+ , demux+ , demuxGenericIO+ , demuxIO++ -- TODO: These can be implemented using the above operations+ -- , demuxSel -- Stop when the fold for the specified key stops+ -- , demuxMin -- Stop when any of the folds stop+ -- , demuxAll -- Stop when all the folds stop (run once)++ -- ** Classifying+ -- | In an input stream of key value pairs fold values for different keys+ -- in individual output buckets using the given fold. 'classify' is a+ -- special case of 'demux' where all the branches of the demultiplexer use+ -- the same scan.+ --+ -- Different types of maps can be used with these combinators via the IsMap+ -- type class. Hashmap performs better when there are more collisions, trie+ -- Map performs better otherwise. Trie has an advantage of sorting the keys+ -- at the same time. For example if we want to store a dictionary of words+ -- and their meanings then trie Map would be better if we also want to+ -- display them in sorted order.++ {-+ , kvToMap++ , toContainer+ , toContainerIO+ , toMap+ , toMapIO+ -}++ , classifyGeneric+ , classify+ , classifyGenericIO+ , classifyIO+ -- , toContainerSel+ -- , toContainerMin+ )+where++#include "inline.hs"+#include "ArrayMacros.h"++import Control.Monad.IO.Class (MonadIO(..))+import Data.IORef (newIORef, readIORef, writeIORef)+import Data.Map.Strict (Map)+import Data.IntSet (IntSet)+import Data.Set (Set)+import Streamly.Internal.Data.IsMap (IsMap(..))+import Streamly.Internal.Data.Tuple.Strict (Tuple'(..), Tuple3'(..))++import qualified Data.IntSet as IntSet+import qualified Data.Set as Set+import qualified Streamly.Internal.Data.IsMap as IsMap++import Prelude hiding (Foldable(..))+import Streamly.Internal.Data.Scanl.Type+-- import Streamly.Internal.Data.Scanl.Combinators++#include "DocTestDataScanl.hs"++-- | Scan the input adding it to a set.+--+-- Definition:+--+-- >>> toSet = Scanl.mkScanl (flip Set.insert) Set.empty+--+{-# INLINE toSet #-}+toSet :: (Monad m, Ord a) => Scanl m a (Set a)+toSet = mkScanl (flip Set.insert) Set.empty++-- | Scan the input adding it to an int set. For integer inputs this performs+-- better than 'toSet'.+--+-- Definition:+--+-- >>> toIntSet = Scanl.mkScanl (flip IntSet.insert) IntSet.empty+--+{-# INLINE toIntSet #-}+toIntSet :: Monad m => Scanl m Int IntSet+toIntSet = mkScanl (flip IntSet.insert) IntSet.empty++-- XXX Name as nubOrd? Or write a nubGeneric++-- | Returns 'Just' for the first occurrence of an element, returns 'Nothing'+-- for any other occurrences.+--+-- Example:+--+-- >>> stream = Stream.fromList [1::Int,1,2,3,4,4,5,1,5,7]+-- >>> Stream.toList $ Stream.postscanlMaybe Scanl.nub stream+-- [1,2,3,4,5,7]+--+-- /Pre-release/+{-# INLINE nub #-}+nub :: (Monad m, Ord a) => Scanl m a (Maybe a)+nub = fmap (\(Tuple' _ x) -> x) $ mkScanl step initial++ where++ initial = Tuple' Set.empty Nothing++ step (Tuple' set _) x =+ if Set.member x set+ then Tuple' set Nothing+ else Tuple' (Set.insert x set) (Just x)++-- | Like 'nub' but specialized to a stream of 'Int', for better performance.+--+-- /Pre-release/+{-# INLINE nubInt #-}+nubInt :: Monad m => Scanl m Int (Maybe Int)+nubInt = fmap (\(Tuple' _ x) -> x) $ mkScanl step initial++ where++ initial = Tuple' IntSet.empty Nothing++ step (Tuple' set _) x =+ if IntSet.member x set+ then Tuple' set Nothing+ else Tuple' (IntSet.insert x set) (Just x)++-- XXX Try Hash set+-- XXX Add a countDistinct window fold+-- XXX Add a bloom filter fold++-- | Count non-duplicate elements in the stream.+--+-- Definition:+--+-- >>> countDistinct = fmap Set.size Scanl.toSet+-- >>> countDistinct = Scanl.postscanl Scanl.nub $ Scanl.catMaybes $ Scanl.length+--+-- The memory used is proportional to the number of distinct elements in the+-- stream, to guard against using too much memory use it as a scan and+-- terminate if the count reaches more than a threshold.+--+-- /Space/: \(\mathcal{O}(n)\)+--+-- /Pre-release/+--+{-# INLINE countDistinct #-}+countDistinct :: (Monad m, Ord a) => Scanl m a Int+-- countDistinct = postscan nub $ catMaybes length+countDistinct = fmap Set.size toSet+{-+countDistinct = fmap (\(Tuple' _ n) -> n) $ foldl' step initial++ where++ initial = Tuple' Set.empty 0++ step (Tuple' set n) x = do+ if Set.member x set+ then+ Tuple' set n+ else+ let cnt = n + 1+ in Tuple' (Set.insert x set) cnt+-}++-- | Like 'countDistinct' but specialized to a stream of 'Int', for better+-- performance.+--+-- Definition:+--+-- >>> countDistinctInt = fmap IntSet.size Scanl.toIntSet+-- >>> countDistinctInt = Scanl.postscanl Scanl.nubInt $ Scanl.catMaybes $ Scanl.length+--+-- /Pre-release/+{-# INLINE countDistinctInt #-}+countDistinctInt :: Monad m => Scanl m Int Int+-- countDistinctInt = postscan nubInt $ catMaybes length+countDistinctInt = fmap IntSet.size toIntSet+{-+countDistinctInt = fmap (\(Tuple' _ n) -> n) $ foldl' step initial++ where++ initial = Tuple' IntSet.empty 0++ step (Tuple' set n) x = do+ if IntSet.member x set+ then+ Tuple' set n+ else+ let cnt = n + 1+ in Tuple' (IntSet.insert x set) cnt+ -}++------------------------------------------------------------------------------+-- demux: in a key value stream fold each key sub-stream with a different fold+------------------------------------------------------------------------------++-- TODO Demultiplex an input element into a number of typed variants. We want+-- to statically restrict the target values within a set of predefined types,+-- an enumeration of a GADT.+--+-- This is the consumer side dual of the producer side 'mux' operation (XXX to+-- be implemented).+--+-- XXX If we use Refold in it, it can perhaps fuse/be more efficient. For+-- example we can store just the result rather than storing the whole fold in+-- the Map. This would be similar to a refold based classify.+--+-- Note: There are separate functions to determine Key and Fold from the input+-- because key is to be determined on each input whereas fold is to be+-- determined only once for a key.+--+-- XXX If a scan terminates do not start it again? This can be easily done by+-- installing a drain fold after a fold is done.+--+-- XXX We can use the Scan drain step to drain the buffered map in the end.++-- | This is the most general of all demux, classify operations.+--+-- The first component of the output tuple is a key-value Map of in-progress+-- scans. The scan returns the scan result as the second component of the+-- output tuple.+--+-- See 'demux' for documentation.+{-# INLINE demuxGeneric #-}+demuxGeneric :: (Monad m, IsMap f, Traversable f) =>+ (a -> Key f)+ -> (Key f -> m (Maybe (Scanl m a b)))+ -> Scanl m a (m (f b), Maybe (Key f, b))+demuxGeneric getKey getFold =+ Scanl (\s a -> Partial <$> step s a) (Partial <$> initial) extract final++ where++ initial = return $ Tuple' IsMap.mapEmpty Nothing++ {-# INLINE runFold #-}+ runFold kv (Scanl step1 initial1 extract1 final1) (k, a) = do+ res <- initial1+ case res of+ Partial s -> do+ res1 <- step1 s a+ case res1 of+ Partial ss -> do+ b <- extract1 ss+ let fld = Scanl step1 (return res1) extract1 final1+ return+ $ Tuple'+ (IsMap.mapInsert k fld kv) (Just (k, b))+ Done b ->+ return+ $ Tuple' (IsMap.mapDelete k kv) (Just (k, b))+ Done b ->+ -- Done in "initial" is possible only for the very first time+ -- the fold is initialized, and in that case we have not yet+ -- inserted it in the Map, so we do not need to delete it.+ return $ Tuple' kv (Just (k, b))++ step (Tuple' kv _) a = do+ let k = getKey a+ case IsMap.mapLookup k kv of+ Nothing -> do+ mfld <- getFold k+ case mfld of+ Nothing -> pure $ Tuple' kv Nothing+ Just fld -> runFold kv fld (k, a)+ Just f -> runFold kv f (k, a)++ extract (Tuple' kv x) = return (Prelude.mapM f kv, x)++ where++ f (Scanl _ i e _) = do+ r <- i+ case r of+ Partial s -> e s+ _ -> error "demuxGeneric: unreachable code"++ final (Tuple' kv x) = return (Prelude.mapM f kv, x)++ where++ f (Scanl _ i _ fin) = do+ r <- i+ case r of+ Partial s -> fin s+ _ -> error "demuxGeneric: unreachable code"++{-# INLINE demuxUsingMap #-}+demuxUsingMap :: (Monad m, Ord k) =>+ (a -> k)+ -> (k -> m (Maybe (Scanl m a b)))+ -> Scanl m a (m (Map k b), Maybe (k, b))+demuxUsingMap = demuxGeneric++-- | @demux getKey getScan@: In a key value stream, scan values corresponding+-- to each key using a key specific scan. @getScan@ is invoked to generate a+-- key specific scan when a key is encountered for the first time in the+-- stream. If a scan does not exist corresponding to the key then 'Nothing' is+-- returned otherwise the result of the scan is returned.+--+-- If a scan terminates, another instance of the scan is started upon receiving+-- an input with that key, @getScan@ is invoked again whenever the key is+-- encountered again.+--+-- This can be used to scan a stream, splitting it based on different keys.+--+-- Since the scan generator function is monadic we can add scans dynamically.+-- For example, we can maintain a Map of keys to scans in an IORef and lookup+-- the scan from that corresponding to a key. This Map can be changed+-- dynamically, scans for new keys can be added or scans for old keys can be+-- deleted or modified.+--+-- Compare with 'classify', the scan in 'classify' is a static scan.+--+-- /Pre-release/+--+{-# INLINE demux #-}+demux :: (Monad m, Ord k) =>+ (a -> k)+ -> (k -> m (Maybe (Scanl m a b)))+ -> Scanl m a (Maybe (k, b))+demux getKey = fmap snd . demuxUsingMap getKey++-- XXX We can use the Scan drain step to drain the buffered map in the end.++-- | This is specialized version of 'demuxGeneric' that uses mutable IO cells+-- as scan accumulators for better performance.+--+-- Keep in mind that the values in the returned Map may be changed by the+-- ongoing scan if you are using those concurrently in another thread.+--+{-# INLINE demuxGenericIO #-}+demuxGenericIO :: (MonadIO m, IsMap f, Traversable f) =>+ (a -> Key f)+ -> (Key f -> m (Maybe (Scanl m a b)))+ -> Scanl m a (m (f b), Maybe (Key f, b))+demuxGenericIO getKey getFold =+ Scanl (\s a -> Partial <$> step s a) (Partial <$> initial) extract final++ where++ initial = return $ Tuple' IsMap.mapEmpty Nothing++ {-# INLINE initFold #-}+ initFold kv (Scanl step1 initial1 extract1 final1) (k, a) = do+ res <- initial1+ case res of+ Partial s -> do+ res1 <- step1 s a+ case res1 of+ Partial ss -> do+ -- XXX Instead of using a Fold type here use a custom+ -- type with an IORef (possibly unboxed) for the+ -- accumulator. That will reduce the allocations.+ let fld = Scanl step1 (return res1) extract1 final1+ ref <- liftIO $ newIORef fld+ b <- extract1 ss+ return+ $ Tuple' (IsMap.mapInsert k ref kv) (Just (k, b))+ Done b -> return $ Tuple' kv (Just (k, b))+ Done b -> return $ Tuple' kv (Just (k, b))++ {-# INLINE runFold #-}+ runFold kv ref (Scanl step1 initial1 extract1 final1) (k, a) = do+ res <- initial1+ case res of+ Partial s -> do+ res1 <- step1 s a+ case res1 of+ Partial ss -> do+ let fld = Scanl step1 (return res1) extract1 final1+ liftIO $ writeIORef ref fld+ b <- extract1 ss+ return $ Tuple' kv (Just (k, b))+ Done b ->+ let kv1 = IsMap.mapDelete k kv+ in return $ Tuple' kv1 (Just (k, b))+ Done _ -> error "demuxGenericIO: unreachable"++ step (Tuple' kv _) a = do+ let k = getKey a+ case IsMap.mapLookup k kv of+ Nothing -> do+ res <- getFold k+ case res of+ Nothing -> pure $ Tuple' kv Nothing+ Just f -> initFold kv f (k, a)+ Just ref -> do+ f <- liftIO $ readIORef ref+ runFold kv ref f (k, a)++ extract (Tuple' kv x) = return (Prelude.mapM f kv, x)++ where++ f ref = do+ Scanl _ i e _ <- liftIO $ readIORef ref+ r <- i+ case r of+ Partial s -> e s+ _ -> error "demuxGenericIO: unreachable code"++ final (Tuple' kv x) = return (Prelude.mapM f kv, x)++ where++ f ref = do+ Scanl _ i _ fin <- liftIO $ readIORef ref+ r <- i+ case r of+ Partial s -> fin s+ _ -> error "demuxGenericIO: unreachable code"++{-# INLINE demuxUsingMapIO #-}+demuxUsingMapIO :: (MonadIO m, Ord k) =>+ (a -> k)+ -> (k -> m (Maybe (Scanl m a b)))+ -> Scanl m a (m (Map k b), Maybe (k, b))+demuxUsingMapIO = demuxGenericIO++-- | This is specialized version of 'demux' that uses mutable IO cells as scan+-- accumulators for better performance.+--+{-# INLINE demuxIO #-}+demuxIO :: (MonadIO m, Ord k) =>+ (a -> k)+ -> (k -> m (Maybe (Scanl m a b)))+ -> Scanl m a (Maybe (k, b))+demuxIO getKey = fmap snd . demuxUsingMapIO getKey++{-+-- | Fold a key value stream to a key-value Map. If the same key appears+-- multiple times, only the last value is retained.+{-# INLINE kvToMapOverwriteGeneric #-}+kvToMapOverwriteGeneric :: (Monad m, IsMap f) => Scanl m (Key f, a) (f a)+kvToMapOverwriteGeneric =+ mkScanl (\kv (k, v) -> IsMap.mapInsert k v kv) IsMap.mapEmpty++{-# INLINE demuxToContainer #-}+demuxToContainer :: (Monad m, IsMap f, Traversable f) =>+ (a -> Key f) -> (Key f -> m (Scanl m a b)) -> Scanl m a (f b)+demuxToContainer getKey getFold =+ let+ classifier = demuxGeneric getKey getFold+ getMap Nothing = pure IsMap.mapEmpty+ getMap (Just action) = action+ aggregator =+ teeWith IsMap.mapUnion+ (rmapM getMap $ lmap fst latest)+ (lmap snd $ catMaybes kvToMapOverwriteGeneric)+ in postscan classifier aggregator++-- | This collects all the results of 'demux' in a Map.+--+{-# INLINE demuxToMap #-}+demuxToMap :: (Monad m, Ord k) =>+ (a -> k) -> (k -> m (Scanl m a b)) -> Scanl m a (Map k b)+demuxToMap = demuxToContainer++{-# INLINE demuxToContainerIO #-}+demuxToContainerIO :: (MonadIO m, IsMap f, Traversable f) =>+ (a -> Key f) -> (Key f -> m (Scanl m a b)) -> Scanl m a (f b)+demuxToContainerIO getKey getFold =+ let+ classifier = demuxGenericIO getKey getFold+ getMap Nothing = pure IsMap.mapEmpty+ getMap (Just action) = action+ aggregator =+ teeWith IsMap.mapUnion+ (rmapM getMap $ lmap fst latest)+ (lmap snd $ catMaybes kvToMapOverwriteGeneric)+ in postscan classifier aggregator++-- | Same as 'demuxToMap' but uses 'demuxIO' for better performance.+--+{-# INLINE demuxToMapIO #-}+demuxToMapIO :: (MonadIO m, Ord k) =>+ (a -> k) -> (k -> m (Scanl m a b)) -> Scanl m a (Map k b)+demuxToMapIO = demuxToContainerIO++{-# INLINE demuxKvToContainer #-}+demuxKvToContainer :: (Monad m, IsMap f, Traversable f) =>+ (Key f -> m (Scanl m a b)) -> Scanl m (Key f, a) (f b)+demuxKvToContainer f = demuxToContainer fst (fmap (lmap snd) . f)++-- | Fold a stream of key value pairs using a function that maps keys to folds.+--+-- Definition:+--+-- >>> demuxKvToMap f = Fold.demuxToContainer fst (Fold.lmap snd . f)+--+-- Example:+--+-- >>> import Data.Map (Map)+-- >>> :{+-- let f "SUM" = return Fold.sum+-- f _ = return Fold.product+-- input = Stream.fromList [("SUM",1),("PRODUCT",2),("SUM",3),("PRODUCT",4)]+-- in Stream.fold (Fold.demuxKvToMap f) input :: IO (Map String Int)+-- :}+-- fromList [("PRODUCT",8),("SUM",4)]+--+-- /Pre-release/+{-# INLINE demuxKvToMap #-}+demuxKvToMap :: (Monad m, Ord k) =>+ (k -> m (Scanl m a b)) -> Scanl m (k, a) (Map k b)+demuxKvToMap = demuxKvToContainer+-}++------------------------------------------------------------------------------+-- Classify: Like demux but uses the same fold for all keys.+------------------------------------------------------------------------------++-- XXX Change these to make the behavior similar to demux* variants. We can+-- implement this using classifyScanManyWith. Maintain a set of done folds in+-- the underlying monad, and when initial is called look it up, if the fold is+-- done then initial would set a flag in the state to ignore the input or+-- return an error.++-- XXX Use a Refold m k a b so that we can make the fold key specifc.+-- XXX Is using a function (a -> k) better than using the input (k,a)?+--+-- XXX We can use the Scan drain step to drain the buffered map in the end.++{-# INLINE classifyGeneric #-}+classifyGeneric :: (Monad m, IsMap f, Traversable f, Ord (Key f)) =>+ -- Note: we need to return the Map itself to display the in-progress values+ -- e.g. to implement top. We could possibly create a separate abstraction+ -- for that use case. We return an action because we want it to be lazy so+ -- that the downstream consumers can choose to process or discard it.+ (a -> Key f) -> Scanl m a b -> Scanl m a (m (f b), Maybe (Key f, b))+classifyGeneric f (Scanl step1 initial1 extract1 final1) =+ Scanl (\s a -> Partial <$> step s a) (Partial <$> initial) extract final++ where++ -- XXX Instead of keeping a Set, after a scan terminates just install a+ -- scan that always returns Partial/Nothing.+ initial = return $ Tuple3' IsMap.mapEmpty Set.empty Nothing++ {-# INLINE initFold #-}+ initFold kv set k a = do+ x <- initial1+ case x of+ Partial s -> do+ r <- step1 s a+ case r of+ Partial s1 -> do+ b <- extract1 s1+ return+ $ Tuple3' (IsMap.mapInsert k s1 kv) set (Just (k, b))+ Done b ->+ return $ Tuple3' kv set (Just (k, b))+ Done b -> return (Tuple3' kv (Set.insert k set) (Just (k, b)))++ step (Tuple3' kv set _) a = do+ let k = f a+ case IsMap.mapLookup k kv of+ Nothing -> do+ if Set.member k set+ then return (Tuple3' kv set Nothing)+ else initFold kv set k a+ Just s -> do+ r <- step1 s a+ case r of+ Partial s1 -> do+ b <- extract1 s1+ return $ Tuple3' (IsMap.mapInsert k s1 kv) set (Just (k,b))+ Done b ->+ let kv1 = IsMap.mapDelete k kv+ in return $ Tuple3' kv1 (Set.insert k set) (Just (k, b))++ extract (Tuple3' kv _ x) = return (Prelude.mapM extract1 kv, x)++ final (Tuple3' kv set x) = return (IsMap.mapTraverseWithKey f1 kv, x)++ where++ f1 k s = do+ if Set.member k set+ -- XXX Why are we doing this? If it is in the set then it will not+ -- be in the map and vice-versa.+ then extract1 s+ else final1 s++{-# INLINE classifyUsingMap #-}+classifyUsingMap :: (Monad m, Ord k) =>+ (a -> k) -> Scanl m a b -> Scanl m a (m (Map k b), Maybe (k, b))+classifyUsingMap = classifyGeneric++-- XXX Make it consistent with denux.++-- | Scans the values for each key using the supplied scan.+--+-- Once the scan for a key terminates, any future values of the key are ignored.+--+-- Equivalent to the following except that the scan is not restarted:+--+-- >>> classify f fld = Scanl.demux f (const fld)+--+{-# INLINE classify #-}+classify :: (MonadIO m, Ord k) =>+ (a -> k) -> Scanl m a b -> Scanl m a (Maybe (k, b))+classify getKey = fmap snd . classifyUsingMap getKey++-- XXX we can use a Prim IORef if we can constrain the state "s" to be Prim+--+-- The code is almost the same as classifyGeneric except the IORef operations.+--+-- XXX We can use the Scan drain step to drain the buffered map in the end.++-- | Be aware that the values in the intermediate Maps would be mutable.+--+{-# INLINE classifyGenericIO #-}+classifyGenericIO :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) =>+ (a -> Key f) -> Scanl m a b -> Scanl m a (m (f b), Maybe (Key f, b))+classifyGenericIO f (Scanl step1 initial1 extract1 final1) =+ Scanl (\s a -> Partial <$> step s a) (Partial <$> initial) extract final++ where++ initial = return $ Tuple3' IsMap.mapEmpty Set.empty Nothing++ {-# INLINE initFold #-}+ initFold kv set k a = do+ x <- initial1+ case x of+ Partial s -> do+ r <- step1 s a+ case r of+ Partial s1 -> do+ ref <- liftIO $ newIORef s1+ b <- extract1 s1+ return+ $ Tuple3'+ (IsMap.mapInsert k ref kv) set (Just (k, b))+ Done b ->+ return $ Tuple3' kv set (Just (k, b))+ Done b -> return (Tuple3' kv (Set.insert k set) (Just (k, b)))++ step (Tuple3' kv set _) a = do+ let k = f a+ case IsMap.mapLookup k kv of+ Nothing -> do+ if Set.member k set+ then return (Tuple3' kv set Nothing)+ else initFold kv set k a+ Just ref -> do+ s <- liftIO $ readIORef ref+ r <- step1 s a+ case r of+ Partial s1 -> do+ liftIO $ writeIORef ref s1+ b <- extract1 s1+ return $ Tuple3' kv set (Just (k, b))+ Done b ->+ let kv1 = IsMap.mapDelete k kv+ in return+ $ Tuple3' kv1 (Set.insert k set) (Just (k, b))++ extract (Tuple3' kv _ x) = return (Prelude.mapM g kv, x)++ where++ g ref = liftIO (readIORef ref) >>= extract1++ final (Tuple3' kv set x) = return (IsMap.mapTraverseWithKey g kv, x)++ where++ g k ref = do+ s <- liftIO $ readIORef ref+ if Set.member k set+ then extract1 s+ else final1 s++{-# INLINE classifyUsingMapIO #-}+classifyUsingMapIO :: (MonadIO m, Ord k) =>+ (a -> k) -> Scanl m a b -> Scanl m a (m (Map k b), Maybe (k, b))+classifyUsingMapIO = classifyGenericIO++-- | Same as classify except that it uses mutable IORef cells in the+-- Map, providing better performance.+--+-- Equivalent to the following except that the scan is not restarted:+--+-- >>> classifyIO f fld = Scanl.demuxIO f (const fld)+--+{-# INLINE classifyIO #-}+classifyIO :: (MonadIO m, Ord k) =>+ (a -> k) -> Scanl m a b -> Scanl m a (Maybe (k, b))+classifyIO getKey = fmap snd . classifyUsingMapIO getKey++{-+{-# INLINE toContainer #-}+toContainer :: (Monad m, IsMap f, Traversable f, Ord (Key f)) =>+ (a -> Key f) -> Scanl m a b -> Scanl m a (f b)+toContainer f fld =+ let+ classifier = classifyGeneric f fld+ getMap Nothing = pure IsMap.mapEmpty+ getMap (Just action) = action+ aggregator =+ teeWith IsMap.mapUnion+ (rmapM getMap $ lmap fst latest)+ (lmap snd $ catMaybes kvToMapOverwriteGeneric)+ in postscan classifier aggregator++-- | Split the input stream based on a key field and fold each split using the+-- given fold. Useful for map/reduce, bucketizing the input in different bins+-- or for generating histograms.+--+-- Example:+--+-- >>> import Data.Map.Strict (Map)+-- >>> :{+-- let input = Stream.fromList [("ONE",1),("ONE",1.1),("TWO",2), ("TWO",2.2)]+-- classify = Fold.toMap fst (Fold.lmap snd Fold.toList)+-- in Stream.fold classify input :: IO (Map String [Double])+-- :}+-- fromList [("ONE",[1.0,1.1]),("TWO",[2.0,2.2])]+--+-- Once the classifier fold terminates for a particular key any further inputs+-- in that bucket are ignored.+--+-- Space used is proportional to the number of keys seen till now and+-- monotonically increases because it stores whether a key has been seen or+-- not.+--+-- See 'demuxToMap' for a more powerful version where you can use a different+-- fold for each key. A simpler version of 'toMap' retaining only the last+-- value for a key can be written as:+--+-- >>> toMap = Fold.foldl' (\kv (k, v) -> Map.insert k v kv) Map.empty+--+-- /Stops: never/+--+-- /Pre-release/+--+{-# INLINE toMap #-}+toMap :: (Monad m, Ord k) =>+ (a -> k) -> Scanl m a b -> Scanl m a (Map k b)+toMap = toContainer++{-# INLINE toContainerIO #-}+toContainerIO :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) =>+ (a -> Key f) -> Scanl m a b -> Scanl m a (f b)+toContainerIO f fld =+ let+ classifier = classifyGenericIO f fld+ getMap Nothing = pure IsMap.mapEmpty+ getMap (Just action) = action+ aggregator =+ teeWith IsMap.mapUnion+ (rmapM getMap $ lmap fst latest)+ (lmap snd $ catMaybes kvToMapOverwriteGeneric)+ in postscan classifier aggregator++-- | Same as 'toMap' but maybe faster because it uses mutable cells as+-- fold accumulators in the Map.+--+{-# INLINE toMapIO #-}+toMapIO :: (MonadIO m, Ord k) =>+ (a -> k) -> Scanl m a b -> Scanl m a (Map k b)+toMapIO = toContainerIO++-- | Given an input stream of key value pairs and a fold for values, fold all+-- the values belonging to each key. Useful for map/reduce, bucketizing the+-- input in different bins or for generating histograms.+--+-- Definition:+--+-- >>> kvToMap = Fold.toMap fst . Fold.lmap snd+--+-- Example:+--+-- >>> :{+-- let input = Stream.fromList [("ONE",1),("ONE",1.1),("TWO",2), ("TWO",2.2)]+-- in Stream.fold (Fold.kvToMap Fold.toList) input+-- :}+-- fromList [("ONE",[1.0,1.1]),("TWO",[2.0,2.2])]+--+-- /Pre-release/+{-# INLINE kvToMap #-}+kvToMap :: (Monad m, Ord k) => Scanl m a b -> Scanl m (k, a) (Map k b)+kvToMap = toMap fst . lmap snd++-- | Determine the frequency of each element in the stream.+--+-- You can just collect the keys of the resulting map to get the unique+-- elements in the stream.+--+-- Definition:+--+-- >>> frequency = Fold.toMap id Fold.length+--+{-# INLINE frequency #-}+frequency :: (Monad m, Ord a) => Scanl m a (Map a Int)+frequency = toMap id length+-}
+ src/Streamly/Internal/Data/Scanl/Type.hs view
@@ -0,0 +1,2030 @@+{-# LANGUAGE CPP #-}+-- |+-- Module : Streamly.Internal.Data.Scanl.Type+-- Copyright : (c) 2019 Composewell Technologies+-- (c) 2013 Gabriel Gonzalez+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- Scanl vs Pipe:+--+-- A scanl is a simpler version of pipes. A scan always produces an output and+-- may or may not consume an input. It can consume at most one input on one+-- output. Whereas a pipe may consume input even without producing anything or+-- it can consume multiple inputs on a single output. Scans are simpler+-- abstractions to think about and easier for the compiler to optimize.+--+-- Returning a stream on "extract":+--+-- Make the extract function return a Step and call extract until Done or+-- alternatively if a fold wants to return multiple values during finalization+-- then we can make the fold output itself a list or stream (on each step).+--+-- Maybe the extract function draining the buffer should be represented by a+-- pipe rather than a scan? It makes the scan behave like a pipe in the+-- finalization case.+--+-- Scan type:+--+-- We can represent the scan as:+--+-- step ::+-- Partial s+-- Done b+-- extract :: s -> b+-- final :: s -> b+--+-- This type allows the accumulator to be returned even if there is no input,+-- using final. This can implement "scanl" as well as "scanl1".+--+-- We can call extract any time, means that it can always produce a valid+-- value. If the input is not last the driver can call "extract", if it is last+-- then it can call "final".+--+-- This does not allow "id" to be implemented for Category instance. Because it+-- requires an output even if there is no input.+--+-- How about the following type?+--+-- step ::+-- Partial s b+-- Done b+-- final :: ()+--+-- This cannot produce output without an input. It can implement scanl1 but not+-- scanl. This can allow category instance, because "id" can be implemented.+-- But this cannot compose with Foldl type, as "final" does not return a value,+-- so the fold cannot return a value.+--+-- How about the following type?+--+-- step ::+-- Partial s b+-- Done b+-- final :: s -> b+--+-- In this case we may not be able to avoid duplicate output. If the fold has+-- already consumed an input, Partial would have returned an output on the last+-- input, then we decide to stop the fold and use "final" on it, which will+-- again produce possibly the same output.+--+module Streamly.Internal.Data.Scanl.Type+ (+ module Streamly.Internal.Data.Fold.Step++ -- * Scanl Type+ , Scanl (..)++ -- * Constructors+ , mkScanl+ , mkScanlM+ , mkScanl1+ , mkScanl1M+ , mkScant+ , mkScantM+ , mkScanr+ , mkScanrM++ -- * Scans+ , const+ -- , fromPure+ , constM+ -- , fromEffect+ , fromRefold+ -- , fromScan+ , drain+ , latest+ , functionM+ , toList+ , toStreamK+ , toStreamKRev+ , genericLength+ , length -- call it "count"?++ , maximumBy+ , maximum+ , minimumBy+ , minimum+ , rangeBy+ , range++ -- * Combinators++ -- ** Mapping output+ , rmapM++ -- ** Mapping Input+ , lmap+ , lmapM+ , postscanl++ -- ** Filtering+ , catMaybes+ , postscanlMaybe+ , filter+ , filtering+ , filterM+ , catLefts+ , catRights+ , catEithers++ -- ** Trimming+ , take+ , taking+ , takeEndBy_+ , takeEndBy+ , dropping++ {-+ -- ** Sequential application+ -- , splitWith -- rename to "append"+ -- , split_++ -- ** Repeated Application (Splitting)+ , ManyState+ , many+ , manyPost+ , groupsOf+ , refoldMany+ , refoldMany1++ -- ** Nested Application+ -- , concatMap+ -- , duplicate+ , refold+ -}++ -- ** Parallel Distribution+ , teeWith+ -- , teeWithFst+ -- , teeWithMax++ {-+ -- ** Parallel Alternative+ , shortest+ , longest++ -- * Running A Fold+ , extractM+ , reduce+ , snoc+ , addOne+ , snocM+ , snocl+ , snoclM+ , close+ , isClosed+ -}++ -- * Transforming inner monad+ , morphInner+ , generalizeInner+ )+where++#include "inline.hs"++#if !MIN_VERSION_base(4,18,0)+import Control.Applicative (liftA2)+#endif+import Control.Monad ((>=>))+-- import Data.Bifunctor (Bifunctor(..))+import Data.Either (fromLeft, fromRight, isLeft, isRight)+import Data.Functor ((<&>))+import Data.Functor.Identity (Identity(..))+import Fusion.Plugin.Types (Fuse(..))+import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)+import Streamly.Internal.Data.Refold.Type (Refold(..))+-- import Streamly.Internal.Data.Scan (Scan(..))+import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))++--import qualified Streamly.Internal.Data.Stream.Step as Stream+import qualified Streamly.Internal.Data.StreamK.Type as K++import Prelude hiding (Foldable(..), concatMap, filter, map, take, const)++-- Entire module is exported, do not import selectively+import Streamly.Internal.Data.Fold.Step++#include "DocTestDataScanl.hs"++------------------------------------------------------------------------------+-- The Scanl type+------------------------------------------------------------------------------++-- An fold is akin to a writer. It is the streaming equivalent of a writer.+-- The type @b@ is the accumulator of the writer. That's the reason the+-- default folds in various modules are called "write".++-- An alternative to using an "extract" function is to use "Partial s b" style+-- partial value so that we always emit the output value and there is no need+-- to extract. Then extract can be used for cleanup purposes. But in this case+-- in some cases we may need a "Continue" constructor where an output value is+-- not available, this was implicit earlier. Also, "b" should be lazy here so+-- that we do not always compute it even if we do not need it.+--+-- Partial s b --> extract :: s -> b+-- Continue --> extract :: s -> Maybe b+--+-- But keeping 'b' lazy does not let the fold optimize well. It leads to+-- significant regressions in the key-value folds.+--+-- The "final" function complicates combinators that take other folds as+-- argument because we need to call their finalizers at right places. An+-- alternative to reduce this complexity where it is not required is to use a+-- separate type for bracketed folds but then we need to manage the complexity+-- of two different fold types.++-- XXX The "final" function in a scan should not return an output. The output+-- from final would only be a duplicate of the last generated output. Since a+-- scan generates an ouput at each input, there should be nothing remaining to+-- be emitted during finalization.++-- | The type @Scanl m a b@ represents a consumer of an input stream of values+-- of type @a@ and returning a final value of type @b@ in 'Monad' @m@. The+-- constructor of a scan is @Scanl step initial extract final@.+--+-- The scan uses an internal state of type @s@. The initial value of the state+-- @s@ is created by @initial@. This function is called once and only once+-- before the scan starts consuming input. Any resource allocation can be done+-- in this function.+--+-- The @step@ function is called on each input, it consumes an input and+-- returns the next intermediate state (see 'Step') or the final result @b@ if+-- the scan terminates.+--+-- The @extract@ function is used by the scan+-- driver to map the current state @s@ of the scan to the scan result. Thus+-- @extract@ can be called multiple times.+--+-- Before a scan terminates, @final@ is called once and only once (unless the+-- scan terminated in @initial@ itself). Any resources allocated by @initial@+-- can be released in @final@. In scan that do not require any cleanup+-- @extract@ and @final@ are typically the same.+--+-- When implementing scan combinators, care should be taken to cleanup any+-- state of the argument folds held by the fold by calling the respective+-- @final@ at all exit points of the scan. Also, @final@ should not be called+-- more than once. Note that if a scan terminates by 'Done' constructor, there+-- is no state to cleanup.+--+-- NOTE: The constructor is not yet released, smart constructors are provided+-- to create scans.+--+data Scanl m a b =+ -- | @Scanl@ @step@ @initial@ @extract@ @final@+ forall s. Scanl (s -> a -> m (Step s b)) (m (Step s b)) (s -> m b) (s -> m b)++{-+-- XXX Change the type to as follows. This takes care of the unfoldMany case+-- where we need to continue in produce mode. Though we need to see how it+-- impacts the key-value scans.+--+data Step s b =+ YieldC s b -- ^ Yield and consume+ | YieldP s b -- ^ Yield and produce+ | Stop b++data Scanl m a b =+ forall s. Scanl+ (s -> a -> m (Step s b)) -- consume step+ (m (Step s b)) -- initial+ (s -> m (Step s b)) -- produce step+ (s -> m (Step s b)) -- drain step+-}++------------------------------------------------------------------------------+-- Mapping on the output+------------------------------------------------------------------------------++-- | Map a monadic function on the output of a scan.+--+{-# INLINE rmapM #-}+rmapM :: Monad m => (b -> m c) -> Scanl m a b -> Scanl m a c+rmapM f (Scanl step initial extract final) =+ Scanl step1 initial1 (extract >=> f) (final >=> f)++ where++ initial1 = initial >>= mapMStep f+ step1 s a = step s a >>= mapMStep f++------------------------------------------------------------------------------+-- Left fold constructors+------------------------------------------------------------------------------++-- | Make a scan from a left fold style pure step function and initial value of+-- the accumulator.+--+-- If your 'Scanl' returns only 'Partial' (i.e. never returns a 'Done') then+-- you can use @mkScanl*@ constructors.+--+{-# INLINE mkScanl #-}+mkScanl :: Monad m => (b -> a -> b) -> b -> Scanl m a b+mkScanl step initial =+ Scanl+ (\s a -> return $ Partial $ step s a)+ (return (Partial initial))+ return+ return++-- | Make a scan from a left fold style monadic step function and initial value+-- of the accumulator.+--+{-# INLINE mkScanlM #-}+mkScanlM :: Monad m => (b -> a -> m b) -> m b -> Scanl m a b+mkScanlM step initial =+ Scanl (\s a -> Partial <$> step s a) (Partial <$> initial) return return++-- | Maps a function on the output of the scan (the type @b@).+instance Functor m => Functor (Scanl m a) where+ {-# INLINE fmap #-}+ fmap f (Scanl step1 initial1 extract final) =+ Scanl step initial (fmap2 f extract) (fmap2 f final)++ where++ initial = fmap2 f initial1+ step s b = fmap2 f (step1 s b)+ fmap2 g = fmap (fmap g)++-- | Make a strict left scan, for non-empty streams, using first element as the+-- starting value. Returns Nothing if the stream is empty.+--+-- /Pre-release/+{-# INLINE mkScanl1 #-}+mkScanl1 :: Monad m => (a -> a -> a) -> Scanl m a (Maybe a)+mkScanl1 step = fmap toMaybe $ mkScanl step1 Nothing'++ where++ step1 Nothing' a = Just' a+ step1 (Just' x) a = Just' $ step x a++-- | Like 'mkScanl1' but with a monadic step function.+--+-- /Pre-release/+{-# INLINE mkScanl1M #-}+mkScanl1M :: Monad m => (a -> a -> m a) -> Scanl m a (Maybe a)+mkScanl1M step = fmap toMaybe $ mkScanlM step1 (return Nothing')++ where++ step1 Nothing' a = return $ Just' a+ step1 (Just' x) a = Just' <$> step x a++{-+data FromScan s b = FromScanInit !s | FromScanGo !s !b++-- XXX we can attach a scan on the last fold e.g. "runScan s last". Or run a+-- scan on a fold that supplies a default value?+--+-- If we are pushing a value to a scan and the scan stops we will lose the+-- input. Only those scans that do not use the Stop constructor can be used as+-- folds or with folds? The Stop constructor makes them suitable to be composed+-- with pull based streams, push based folds cannot work with that. Do we need+-- two types of scans then, scans for streams and scans for folds? ScanR and+-- ScanL?++-- | This does not work correctly yet. We lose the last input.+--+{-# INLINE fromScan #-}+fromScan :: Monad m => Scan m a b -> Scanl m a (Maybe b)+fromScan (Scan consume initial) =+ Fold fstep (return $ Partial (FromScanInit initial)) fextract fextract++ where++ fstep (FromScanInit ss) a = do+ r <- consume ss a+ return $ case r of+ Stream.Yield b s -> Partial (FromScanGo s b)+ Stream.Skip s -> Partial (FromScanInit s)+ -- XXX We have lost the input here.+ -- XXX Need to change folds to always return Done on the next input+ Stream.Stop -> Done Nothing+ fstep (FromScanGo ss acc) a = do+ r <- consume ss a+ return $ case r of+ Stream.Yield b s -> Partial (FromScanGo s b)+ Stream.Skip s -> Partial (FromScanGo s acc)+ -- XXX We have lost the input here.+ Stream.Stop -> Done (Just acc)++ fextract (FromScanInit _) = return Nothing+ fextract (FromScanGo _ acc) = return (Just acc)+-}++------------------------------------------------------------------------------+-- Right fold constructors+------------------------------------------------------------------------------++-- | Make a scan using a right fold style step function and a terminal value.+-- It performs a strict right fold via a left fold using function composition.+-- Note that a strict right fold can only be useful for constructing strict+-- structures in memory. For reductions this will be very inefficient.+--+-- Definitions:+--+-- >>> mkScanr f z = fmap (flip appEndo z) $ Scanl.foldMap (Endo . f)+-- >>> mkScanr f z = fmap ($ z) $ Scanl.mkScanl (\g x -> g . f x) id+--+-- Example:+--+-- >>> Stream.toList $ Stream.scanl (Scanl.mkScanr (:) []) $ Stream.enumerateFromTo 1 5+-- [[],[1],[1,2],[1,2,3],[1,2,3,4],[1,2,3,4,5]]+--+{-# INLINE mkScanr #-}+mkScanr :: Monad m => (a -> b -> b) -> b -> Scanl m a b+mkScanr f z = fmap ($ z) $ mkScanl (\g x -> g . f x) id++-- XXX we have not seen any use of this yet, not releasing until we have a use+-- case.++-- | Like mkScanr but with a monadic step function.+--+-- Example:+--+-- >>> toList = Scanl.mkScanrM (\a xs -> return $ a : xs) (return [])+--+-- /Pre-release/+{-# INLINE mkScanrM #-}+mkScanrM :: Monad m => (a -> b -> m b) -> m b -> Scanl m a b+mkScanrM g z =+ rmapM (z >>=) $ mkScanlM (\f x -> return $ g x >=> f) (return return)++------------------------------------------------------------------------------+-- General scan constructors+------------------------------------------------------------------------------++-- XXX If the Step yield gives the result each time along with the state then+-- we can make the type of this as+--+-- mkFold :: Monad m => (s -> a -> Step s b) -> Step s b -> Scanl m a b+--+-- Then similar to foldl' and foldr we can just fmap extract on it to extend+-- it to the version where an 'extract' function is required. Or do we even+-- need that?+--+-- Until we investigate this we are not releasing these.+--+-- XXX The above text would apply to+-- Streamly.Internal.Data.Parser.ParserD.Type.parser++-- | Make a terminating scan using a pure step function, a pure initial state+-- and a pure state extraction function.+--+-- /Pre-release/+--+{-# INLINE mkScant #-}+mkScant :: Monad m => (s -> a -> Step s b) -> Step s b -> (s -> b) -> Scanl m a b+mkScant step initial extract =+ Scanl+ (\s a -> return $ step s a)+ (return initial)+ (return . extract)+ (return . extract)++-- | Make a terminating scan with an effectful step function and initial state,+-- and a state extraction function.+--+-- >>> mkScantM = Scanl.Scanl+--+-- We can just use 'Scanl' but it is provided for completeness.+--+-- /Pre-release/+--+{-# INLINE mkScantM #-}+mkScantM :: (s -> a -> m (Step s b)) -> m (Step s b) -> (s -> m b) -> Scanl m a b+mkScantM step initial extract = Scanl step initial extract extract++------------------------------------------------------------------------------+-- Refold+------------------------------------------------------------------------------++-- This is similar to how we run an Unfold to generate a Stream. A Fold is like+-- a Stream and a Fold2 is like an Unfold.++-- | Make a scan from a consumer.+--+-- /Internal/+fromRefold :: Refold m c a b -> c -> Scanl m a b+fromRefold (Refold step inject extract) c =+ Scanl step (inject c) extract extract++------------------------------------------------------------------------------+-- Basic Scans+------------------------------------------------------------------------------++-- | A scan that drains all its input, running the effects and discarding the+-- results.+--+-- >>> drain = Scanl.drainMapM (const (return ()))+-- >>> drain = Scanl.mkScanl (\_ _ -> ()) ()+--+{-# INLINE drain #-}+drain :: Monad m => Scanl m a ()+drain = mkScanl (\_ _ -> ()) ()++-- | Returns the latest element of the input stream, if any.+--+-- >>> latest = Scanl.mkScanl1 (\_ x -> x)+-- >>> latest = fmap getLast $ Scanl.foldMap (Last . Just)+--+{-# INLINE latest #-}+latest :: Monad m => Scanl m a (Maybe a)+latest = mkScanl1 (\_ x -> x)++-- | Lift a Maybe returning function to a scan.+functionM :: Monad m => (a -> m (Maybe b)) -> Scanl m a (Maybe b)+functionM f = Scanl step initial return return++ where++ initial = return $ Partial Nothing++ step _ x = f x <&> Partial++-- | Scans the input stream building a list.+--+-- /Warning!/ working on large lists accumulated as buffers in memory could be+-- very inefficient, consider using "Streamly.Data.Array"+-- instead.+--+-- >>> toList = Scanl.mkScanr (:) []+--+{-# INLINE toList #-}+toList :: Monad m => Scanl m a [a]+toList = mkScanr (:) []++-- | Buffers the input stream to a pure stream in the reverse order of the+-- input.+--+-- This is more efficient than 'toStreamK'. toStreamK has exactly the same+-- performance as reversing the stream after toStreamKRev.+--+-- /Pre-release/++-- xn : ... : x2 : x1 : []+{-# INLINE toStreamKRev #-}+toStreamKRev :: Monad m => Scanl m a (K.StreamK n a)+toStreamKRev = mkScanl (flip K.cons) K.nil++-- | Scans its input building a pure stream.+--+-- >>> toStreamK = fmap StreamK.reverse Scanl.toStreamKRev+--+-- /Internal/+{-# INLINE toStreamK #-}+toStreamK :: Monad m => Scanl m a (K.StreamK n a)+toStreamK = mkScanr K.cons K.nil++-- | Like 'length', except with a more general 'Num' return value+--+-- Definition:+--+-- >>> genericLength = fmap getSum $ Scanl.foldMap (Sum . const 1)+-- >>> genericLength = Scanl.mkScanl (\n _ -> n + 1) 0+--+-- /Pre-release/+{-# INLINE genericLength #-}+genericLength :: (Monad m, Num b) => Scanl m a b+genericLength = mkScanl (\n _ -> n + 1) 0++-- | Determine the length of the input stream.+--+-- Definition:+--+-- >>> length = Scanl.genericLength+-- >>> length = fmap getSum $ Scanl.foldMap (Sum . const 1)+--+{-# INLINE length #-}+length :: Monad m => Scanl m a Int+length = genericLength++------------------------------------------------------------------------------+-- To Summary (Maybe)+------------------------------------------------------------------------------++{-# INLINE maxBy #-}+maxBy :: (a -> a -> Ordering) -> a -> a -> a+maxBy cmp x y =+ case cmp x y of+ GT -> x+ _ -> y++-- | Determine the maximum element in a stream using the supplied comparison+-- function.+--+{-# INLINE maximumBy #-}+maximumBy :: Monad m => (a -> a -> Ordering) -> Scanl m a (Maybe a)+maximumBy cmp = mkScanl1 (maxBy cmp)++-- | Determine the maximum element in a stream.+--+-- Definitions:+--+-- >>> maximum = Scanl.maximumBy compare+-- >>> maximum = Scanl.mkScanl1 max+--+-- Same as the following but without a default maximum. The 'Max' Monoid uses+-- the 'minBound' as the default maximum:+--+-- >>> maximum = fmap Data.Semigroup.getMax $ Scanl.foldMap Data.Semigroup.Max+--+{-# INLINE maximum #-}+maximum :: (Monad m, Ord a) => Scanl m a (Maybe a)+maximum = mkScanl1 max++{-# INLINE minBy #-}+minBy :: (a -> a -> Ordering) -> a -> a -> a+minBy cmp x y =+ case cmp x y of+ GT -> y+ _ -> x++-- | Computes the minimum element with respect to the given comparison function+--+{-# INLINE minimumBy #-}+minimumBy :: Monad m => (a -> a -> Ordering) -> Scanl m a (Maybe a)+minimumBy cmp = mkScanl1 (minBy cmp)++-- | Determine the minimum element in a stream using the supplied comparison+-- function.+--+-- Definitions:+--+-- >>> minimum = Scanl.minimumBy compare+-- >>> minimum = Scanl.mkScanl1 min+--+-- Same as the following but without a default minimum. The 'Min' Monoid uses the+-- 'maxBound' as the default maximum:+--+-- >>> maximum = fmap Data.Semigroup.getMin $ Scanl.foldMap Data.Semigroup.Min+--+{-# INLINE minimum #-}+minimum :: (Monad m, Ord a) => Scanl m a (Maybe a)+minimum = mkScanl1 min++extractRange :: Range a -> Maybe (a, a)+extractRange RangeNone = Nothing+extractRange (Range mn mx) = Just (mn, mx)++data Range a = RangeNone | Range !a !a++-- | Find minimum and maximum element using the provided comparison function.+--+{-# INLINE rangeBy #-}+rangeBy :: Monad m => (a -> a -> Ordering) -> Scanl m a (Maybe (a, a))+rangeBy cmp = fmap extractRange $ mkScanl step RangeNone++ where++ step RangeNone x = Range x x+ step (Range mn mx) x = Range (minBy cmp mn x) (maxBy cmp mx x)++-- | Find minimum and maximum elements i.e. (min, max).+--+{-# INLINE range #-}+range :: (Monad m, Ord a) => Scanl m a (Maybe (a, a))+range = fmap extractRange $ mkScanl step RangeNone++ where++ step RangeNone x = Range x x+ step (Range mn mx) x = Range (min mn x) (max mx x)++------------------------------------------------------------------------------+-- Instances+------------------------------------------------------------------------------++-- XXX These are singleton folds that are closed for input. The correspondence+-- to a nil stream would be a nil fold that returns "Done" in "initial" i.e. it+-- does not produce any accumulator value. However, we do not have a+-- representation of an empty value in folds, because the Done constructor+-- always produces a value (Done b). We can potentially use "Partial s b" and+-- "Done" to make the type correspond to the stream type. That may be possible+-- if we introduce the "Skip" constructor as well because after the last+-- "Partial s b" we have to emit a "Skip to Done" state to keep cranking the+-- fold until it is done.+--+-- There is also the asymmetry between folds and streams because folds have an+-- "initial" to initialize the fold without any input. A similar concept is+-- possible in streams as well to stop the stream. That would be a "closing"+-- operation for the stream which can be called even without consuming any item+-- from the stream or when we are done consuming.+--+-- However, the initial action in folds creates a discrepancy with the CPS+-- folds, and the same may be the case if we have a stop/cleanup operation in+-- streams.++{-+-- | Make a scan that yields the supplied value without consuming any input.+--+-- /Pre-release/+--+{-# INLINE fromPure #-}+fromPure :: Applicative m => b -> Scanl m a b+fromPure b = Scanl undefined (pure $ Done b) pure pure+-}++-- | Make a scan that yields the supplied value on any input.+--+-- /Pre-release/+--+{-# INLINE const #-}+const :: Applicative m => b -> Scanl m a b+const b = Scanl (\s _ -> pure $ Partial s) (pure $ Partial b) pure pure++{-+-- | Make a scan that yields the result of the supplied effectful action+-- without consuming further input.+--+-- /Pre-release/+--+{-# INLINE fromEffect #-}+fromEffect :: Applicative m => m b -> Scanl m a b+fromEffect b = Scanl undefined (Done <$> b) pure pure+-}++-- | Make a scan that runs the supplied effect once and then yields the result+-- on any input.+--+-- /Pre-release/+--+{-# INLINE constM #-}+constM :: Applicative m => m b -> Scanl m a b+constM b = Scanl (\s _ -> pure $ Partial s) (Partial <$> b) pure pure++{-+{-# ANN type SeqFoldState Fuse #-}+data SeqFoldState sl f sr = SeqFoldL !sl | SeqFoldR !f !sr++-- | Sequential fold application. Apply two folds sequentially to an input+-- stream. The input is provided to the first fold, when it is done - the+-- remaining input is provided to the second fold. When the second fold is done+-- or if the input stream is over, the outputs of the two folds are combined+-- using the supplied function.+--+-- Example:+--+-- >>> header = Scanl.take 8 Scanl.toList+-- >>> line = Scanl.takeEndBy (== '\n') Scanl.toList+-- >>> f = Scanl.splitWith (,) header line+-- >>> Stream.fold f $ Stream.fromList "header: hello\n"+-- ("header: ","hello\n")+--+-- Note: This is dual to appending streams using 'Data.Stream.append'.+--+-- Note: this implementation allows for stream fusion but has quadratic time+-- complexity, because each composition adds a new branch that each subsequent+-- fold's input element has to traverse, therefore, it cannot scale to a large+-- number of compositions. After around 100 compositions the performance starts+-- dipping rapidly compared to a CPS style implementation.+--+-- For larger number of compositions you can convert the fold to a parser and+-- use ParserK.+--+-- /Time: O(n^2) where n is the number of compositions./+--+{-# INLINE splitWith #-}+splitWith :: Monad m =>+ (a -> b -> c) -> Fold m x a -> Fold m x b -> Fold m x c+splitWith func+ (Fold stepL initialL _ finalL)+ (Fold stepR initialR _ finalR) =+ Scanl step initial extract final++ where++ {-# INLINE runR #-}+ runR action f = bimap (SeqFoldR f) f <$> action++ {-# INLINE runL #-}+ runL action = do+ resL <- action+ chainStepM (return . SeqFoldL) (runR initialR . func) resL++ initial = runL initialL++ step (SeqFoldL st) a = runL (stepL st a)+ step (SeqFoldR f st) a = runR (stepR st a) f++ -- XXX splitWith should not be used for scanning+ -- It would rarely make sense and resource tracking and cleanup would be+ -- expensive. especially when multiple splitWith are chained.+ extract _ = error "splitWith: cannot be used for scanning"++ final (SeqFoldR f sR) = fmap f (finalR sR)+ final (SeqFoldL sL) = do+ rL <- finalL sL+ res <- initialR+ fmap (func rL)+ $ case res of+ Partial sR -> finalR sR+ Done rR -> return rR++{-# ANN type SeqFoldState_ Fuse #-}+data SeqFoldState_ sl sr = SeqFoldL_ !sl | SeqFoldR_ !sr++-- | Same as applicative '*>'. Run two folds serially one after the other+-- discarding the result of the first.+--+-- This was written in the hope that it might be faster than implementing it+-- using splitWith, but the current benchmarks show that it has the same+-- performance. So do not expose it unless some benchmark shows benefit.+--+{-# INLINE split_ #-}+split_ :: Monad m => Fold m x a -> Fold m x b -> Fold m x b+split_ (Fold stepL initialL _ finalL) (Fold stepR initialR _ finalR) =+ Scanl step initial extract final++ where++ initial = do+ resL <- initialL+ case resL of+ Partial sl -> return $ Partial $ SeqFoldL_ sl+ Done _ -> do+ resR <- initialR+ return $ first SeqFoldR_ resR++ step (SeqFoldL_ st) a = do+ r <- stepL st a+ case r of+ Partial s -> return $ Partial (SeqFoldL_ s)+ Done _ -> do+ resR <- initialR+ return $ first SeqFoldR_ resR+ step (SeqFoldR_ st) a = do+ resR <- stepR st a+ return $ first SeqFoldR_ resR++ -- XXX split_ should not be used for scanning+ -- See splitWith for more details.+ extract _ = error "split_: cannot be used for scanning"++ final (SeqFoldR_ sR) = finalR sR+ final (SeqFoldL_ sL) = do+ _ <- finalL sL+ res <- initialR+ case res of+ Partial sR -> finalR sR+ Done rR -> return rR++-- | 'Applicative' form of 'splitWith'. Split the input serially over two+-- folds. Note that this fuses but performance degrades quadratically with+-- respect to the number of compositions. It should be good to use for less+-- than 8 compositions.+instance Monad m => Applicative (Fold m a) where+ {-# INLINE pure #-}+ pure = fromPure++ {-# INLINE (<*>) #-}+ (<*>) = splitWith id++ {-# INLINE (*>) #-}+ (*>) = split_++ {-# INLINE liftA2 #-}+ liftA2 f x = (<*>) (fmap f x)++{-# ANN type TeeState Fuse #-}+data TeeState sL sR bL bR+ = TeeBoth !sL !sR+ | TeeLeft !bR !sL+ | TeeRight !bL !sR++-- | @teeWithMax k f1 f2@ distributes its input to both @f1@ and @f2@ until+-- both of them terminate. The output of the two scans is combined using the+-- function @k@.+--+-- XXX There are two choices:+--+-- 1. If one of them terminates before the other, the final value of+-- the other is used in the zipping function.+-- 2. Use a (Maybe a -> Maybe b -> c) zipping function+--+-- Which is better? We will find out based on the actual use cases.+--+{-# INLINE teeWithMax #-}+teeWithMax :: Monad m =>+ (a -> b -> c) -> Scanl m x a -> Scanl m x b -> Scanl m x c+teeWithMax f+ (Scanl stepL initialL extractL finalL)+ (Scanl stepR initialR extractR finalR) =+ Scanl step initial extract final++ where++ {-# INLINE runBoth #-}+ runBoth actionL actionR = do+ resL <- actionL+ resR <- actionR+ return+ $ case resL of+ Partial sl ->+ Partial+ $ case resR of+ Partial sr -> TeeBoth sl sr+ Done br -> TeeLeft br sl+ Done bl -> bimap (TeeRight bl) (f bl) resR++ initial = runBoth initialL initialR++ step (TeeBoth sL sR) a = runBoth (stepL sL a) (stepR sR a)+ step (TeeLeft bR sL) a = bimap (TeeLeft bR) (`f` bR) <$> stepL sL a+ step (TeeRight bL sR) a = bimap (TeeRight bL) (f bL) <$> stepR sR a++ extract (TeeBoth sL sR) = f <$> extractL sL <*> extractR sR+ extract (TeeLeft bR sL) = (`f` bR) <$> extractL sL+ extract (TeeRight bL sR) = f bL <$> extractR sR++ final (TeeBoth sL sR) = f <$> finalL sL <*> finalR sR+ final (TeeLeft bR sL) = (`f` bR) <$> finalL sL+ final (TeeRight bL sR) = f bL <$> finalR sR++{-# ANN type TeeFstState Fuse #-}+data TeeFstState sL sR b+ = TeeFstBoth !sL !sR+ | TeeFstLeft !b !sL++-- | Like 'teeWith' but terminates only when the first scan terminates. If the+-- second scan terminates earlier then its final value is used in the zipping+-- function.+--+-- /Pre-release/+--+{-# INLINE teeWithFst #-}+teeWithFst :: Monad m =>+ (b -> c -> d) -> Scanl m a b -> Scanl m a c -> Scanl m a d+teeWithFst f+ (Scanl stepL initialL extractL finalL)+ (Scanl stepR initialR extractR finalR) =+ Scanl step initial extract final++ where++ {-# INLINE runBoth #-}+ runBoth actionL actionR = do+ resL <- actionL+ resR <- actionR++ case resL of+ Partial sl ->+ return+ $ Partial+ $ case resR of+ Partial sr -> TeeFstBoth sl sr+ Done br -> TeeFstLeft br sl+ Done bl -> do+ Done . f bl <$>+ case resR of+ Partial sr -> finalR sr+ Done br -> return br++ initial = runBoth initialL initialR++ step (TeeFstBoth sL sR) a = runBoth (stepL sL a) (stepR sR a)+ step (TeeFstLeft bR sL) a = bimap (TeeFstLeft bR) (`f` bR) <$> stepL sL a++ extract (TeeFstBoth sL sR) = f <$> extractL sL <*> extractR sR+ extract (TeeFstLeft bR sL) = (`f` bR) <$> extractL sL++ final (TeeFstBoth sL sR) = f <$> finalL sL <*> finalR sR+ final (TeeFstLeft bR sL) = (`f` bR) <$> finalL sL+-}++-- | @teeWith k f1 f2@ distributes its input to both @f1@ and @f2@ until any+-- one of them terminates. The outputs of the two scans are combined using the+-- function @k@.+--+-- Definition:+--+-- >>> teeWith k f1 f2 = fmap (uncurry k) (Scanl.tee f1 f2)+--+-- Example:+--+-- >>> avg = Scanl.teeWith (/) Scanl.sum (fmap fromIntegral Scanl.length)+-- >>> Stream.toList $ Stream.postscanl avg $ Stream.fromList [1.0..10.0]+-- [1.0,1.5,2.0,2.5,3.0,3.5,4.0,4.5,5.0,5.5]+--+-- Note that nested applications of teeWith do not fuse.+--+-- /Pre-release/+--+{-# INLINE teeWith #-}+teeWith :: Monad m =>+ (b -> c -> d) -> Scanl m a b -> Scanl m a c -> Scanl m a d+teeWith f+ (Scanl stepL initialL extractL finalL)+ (Scanl stepR initialR extractR finalR) =+ Scanl step initial extract final++ where++ {-# INLINE runBoth #-}+ runBoth actionL actionR = do+ resL <- actionL+ resR <- actionR+ case resL of+ Partial sl -> do+ case resR of+ Partial sr -> return $ Partial $ Tuple' sl sr+ Done br -> Done . (`f` br) <$> finalL sl++ Done bl -> do+ Done . f bl <$>+ case resR of+ Partial sr -> finalR sr+ Done br -> return br++ initial = runBoth initialL initialR++ step (Tuple' sL sR) a = runBoth (stepL sL a) (stepR sR a)++ extract (Tuple' sL sR) = f <$> extractL sL <*> extractR sR++ final (Tuple' sL sR) = f <$> finalL sL <*> finalR sR++instance Monad m => Applicative (Scanl m a) where+ {-# INLINE pure #-}+ pure = const++ (<*>) = teeWith id++{-+-- XXX this does not make sense as a scan.+--+-- | Shortest alternative. Apply both folds in parallel but choose the result+-- from the one which consumed least input i.e. take the shortest succeeding+-- fold.+--+-- If both the folds finish at the same time or if the result is extracted+-- before any of the folds could finish then the left one is taken.+--+-- /Pre-release/+--+{-# INLINE shortest #-}+shortest :: Monad m => Scanl m x a -> Scanl m x b -> Scanl m x (Either a b)+shortest (Scanl stepL initialL extractL finalL) (Scanl stepR initialR _ finalR) =+ Scanl step initial extract final++ where++ {-# INLINE runBoth #-}+ runBoth actionL actionR = do+ resL <- actionL+ resR <- actionR+ case resL of+ Partial sL ->+ case resR of+ Partial sR -> return $ Partial $ Tuple' sL sR+ Done bR -> finalL sL >> return (Done (Right bR))+ Done bL -> do+ case resR of+ Partial sR -> void (finalR sR)+ Done _ -> return ()+ return (Done (Left bL))++ initial = runBoth initialL initialR++ step (Tuple' sL sR) a = runBoth (stepL sL a) (stepR sR a)++ extract (Tuple' sL _) = Left <$> extractL sL++ final (Tuple' sL sR) = Left <$> finalL sL <* finalR sR++{-# ANN type LongestState Fuse #-}+data LongestState sL sR+ = LongestBoth !sL !sR+ | LongestLeft !sL+ | LongestRight !sR++-- | Longest alternative. Apply both folds in parallel but choose the result+-- from the one which consumed more input i.e. take the longest succeeding+-- fold.+--+-- If both the folds finish at the same time or if the result is extracted+-- before any of the folds could finish then the left one is taken.+--+-- /Pre-release/+--+{-# INLINE longest #-}+longest :: Monad m => Scanl m x a -> Scanl m x b -> Scanl m x (Either a b)+longest+ (Scanl stepL initialL _ finalL)+ (Scanl stepR initialR _ finalR) =+ Scanl step initial extract final++ where++ {-# INLINE runBoth #-}+ runBoth actionL actionR = do+ resL <- actionL+ resR <- actionR+ return $+ case resL of+ Partial sL ->+ Partial $+ case resR of+ Partial sR -> LongestBoth sL sR+ Done _ -> LongestLeft sL+ Done bL -> bimap LongestRight (const (Left bL)) resR++ initial = runBoth initialL initialR++ step (LongestBoth sL sR) a = runBoth (stepL sL a) (stepR sR a)+ step (LongestLeft sL) a = bimap LongestLeft Left <$> stepL sL a+ step (LongestRight sR) a = bimap LongestRight Right <$> stepR sR a++ -- XXX Scan with this may not make sense as we cannot determine the longest+ -- until one of them have exhausted.+ extract _ = error $ "longest: scan is not allowed as longest cannot be "+ ++ "determined until one fold has exhausted."++ final (LongestLeft sL) = Left <$> finalL sL+ final (LongestRight sR) = Right <$> finalR sR+ final (LongestBoth sL sR) = Left <$> finalL sL <* finalR sR++data ConcatMapState m sa a b c+ = B !sa (sa -> m b)+ | forall s. C (s -> a -> m (Step s c)) !s (s -> m c) (s -> m c)++-- | Map a 'Fold' returning function on the result of a 'Fold' and run the+-- returned fold. This is akin to an n-ary version of 'splitWith' where the+-- next fold for splitting the input is decided dynamically using the previous+-- result. This operation can be used to express data dependencies+-- between fold operations.+--+-- Let's say the first element in the stream is a count of the following+-- elements that we have to add, then:+--+-- >>> import Data.Maybe (fromJust)+-- >>> count = fmap fromJust Scanl.one+-- >>> total n = Scanl.take n Scanl.sum+-- >>> Stream.fold (Scanl.concatMap total count) $ Stream.fromList [10,9..1]+-- 45+--+-- This does not fuse completely, see 'refold' for a fusible alternative.+--+-- /Time: O(n^2) where @n@ is the number of compositions./+--+-- See also: 'Streamly.Internal.Data.Stream.foldIterateM', 'refold'+--+{-# INLINE concatMap #-}+concatMap :: Monad m => (b -> Scanl m a c) -> Scanl m a b -> Scanl m a c+concatMap f (Fold stepa initiala _ finala) =+ Fold stepc initialc extractc finalc+ where+ initialc = do+ r <- initiala+ case r of+ Partial s -> return $ Partial (B s finala)+ Done b -> initInnerFold (f b)++ stepc (B s fin) a = do+ r <- stepa s a+ case r of+ Partial s1 -> return $ Partial (B s1 fin)+ Done b -> initInnerFold (f b)++ stepc (C stepInner s extractInner fin) a = do+ r <- stepInner s a+ return $ case r of+ Partial sc -> Partial (C stepInner sc extractInner fin)+ Done c -> Done c++ -- XXX Cannot use for scanning+ extractc _ = error "concatMap: cannot be used for scanning"++ initInnerFold (Scanl step i e fin) = do+ r <- i+ return $ case r of+ Partial s -> Partial (C step s e fin)+ Done c -> Done c++ initFinalize (Fold _ i _ fin) = do+ r <- i+ case r of+ Partial s -> fin s+ Done c -> return c++ finalc (B s fin) = do+ r <- fin s+ initFinalize (f r)+ finalc (C _ sInner _ fin) = fin sInner+-}++------------------------------------------------------------------------------+-- Mapping on input+------------------------------------------------------------------------------++-- | @lmap f scan@ maps the function @f@ on the input of the scan.+--+-- Definition:+--+-- >>> lmap = Scanl.lmapM return+--+-- Example:+--+-- >>> sumSquared = Scanl.lmap (\x -> x * x) Scanl.sum+-- >>> Stream.toList $ Stream.scanl sumSquared (Stream.enumerateFromTo 1 10)+-- [0,1,5,14,30,55,91,140,204,285,385]+--+{-# INLINE lmap #-}+lmap :: (a -> b) -> Scanl m b r -> Scanl m a r+lmap f (Scanl step begin done final) = Scanl step' begin done final+ where+ step' x a = step x (f a)++-- | @lmapM f scan@ maps the monadic function @f@ on the input of the scan.+--+{-# INLINE lmapM #-}+lmapM :: Monad m => (a -> m b) -> Scanl m b r -> Scanl m a r+lmapM f (Scanl step begin done final) = Scanl step' begin done final+ where+ step' x a = f a >>= step x++-- | Postscan the input of a 'Scanl' to change it in a stateful manner using+-- another 'Scanl'.+--+-- This is basically an append operation.+--+-- /Pre-release/+{-# INLINE postscanl #-}+postscanl :: Monad m => Scanl m a b -> Scanl m b c -> Scanl m a c+postscanl+ (Scanl stepL initialL extractL finalL)+ (Scanl stepR initialR extractR finalR) =+ Scanl step initial extract final++ where++ {-# INLINE runStep #-}+ runStep actionL sR = do+ rL <- actionL+ case rL of+ Done bL -> do+ rR <- stepR sR bL+ case rR of+ Partial sR1 -> Done <$> finalR sR1+ Done bR -> return $ Done bR+ Partial sL -> do+ !b <- extractL sL+ rR <- stepR sR b+ case rR of+ Partial sR1 -> return $ Partial (sL, sR1)+ Done bR -> finalL sL >> return (Done bR)++ initial = do+ rR <- initialR+ case rR of+ Partial sR -> do+ rL <- initialL+ case rL of+ Done _ -> Done <$> finalR sR+ Partial sL -> return $ Partial (sL, sR)+ Done b -> return $ Done b++ -- XXX should use Tuple'+ step (sL, sR) x = runStep (stepL sL x) sR++ extract = extractR . snd++ final (sL, sR) = finalL sL *> finalR sR++------------------------------------------------------------------------------+-- Filtering+------------------------------------------------------------------------------++-- | Modify a scan to receive a 'Maybe' input, the 'Just' values are unwrapped+-- and sent to the original scan, 'Nothing' values are discarded.+--+-- >>> catMaybes = Scanl.mapMaybe id+-- >>> catMaybes = Scanl.filter isJust . Scanl.lmap fromJust+--+{-# INLINE_NORMAL catMaybes #-}+catMaybes :: Monad m => Scanl m a b -> Scanl m (Maybe a) b+catMaybes (Scanl step initial extract final) =+ Scanl step1 initial extract final++ where++ step1 s a =+ case a of+ Nothing -> return $ Partial s+ Just x -> step s x++-- | Scan using a 'Maybe' returning scan, filter out 'Nothing' values.+--+-- >>> postscanlMaybe p f = Scanl.postscanl p (Scanl.catMaybes f)+--+-- /Pre-release/+{-# INLINE postscanlMaybe #-}+postscanlMaybe :: Monad m => Scanl m a (Maybe b) -> Scanl m b c -> Scanl m a c+postscanlMaybe f1 f2 = postscanl f1 (catMaybes f2)++-- | A scan for filtering elements based on a predicate.+--+{-# INLINE filtering #-}+filtering :: Monad m => (a -> Bool) -> Scanl m a (Maybe a)+filtering f = mkScanl step Nothing++ where++ step _ a = if f a then Just a else Nothing++-- | Include only those elements that pass a predicate.+--+-- >>> Stream.toList $ Stream.scanl (Scanl.filter (> 5) Scanl.sum) $ Stream.fromList [1..10]+-- [0,0,0,0,0,0,6,13,21,30,40]+--+-- >>> filter p = Scanl.postscanlMaybe (Scanl.filtering p)+-- >>> filter p = Scanl.filterM (return . p)+-- >>> filter p = Scanl.mapMaybe (\x -> if p x then Just x else Nothing)+--+{-# INLINE filter #-}+filter :: Monad m => (a -> Bool) -> Scanl m a r -> Scanl m a r+-- filter p = postscanlMaybe (filtering p)+filter f (Scanl step begin extract final) = Scanl step' begin extract final+ where+ step' x a = if f a then step x a else return $ Partial x++-- | Like 'filter' but with a monadic predicate.+--+-- >>> f p x = p x >>= \r -> return $ if r then Just x else Nothing+-- >>> filterM p = Scanl.mapMaybeM (f p)+--+{-# INLINE filterM #-}+filterM :: Monad m => (a -> m Bool) -> Scanl m a r -> Scanl m a r+filterM f (Scanl step begin extract final) = Scanl step' begin extract final+ where+ step' x a = do+ use <- f a+ if use then step x a else return $ Partial x++------------------------------------------------------------------------------+-- Either streams+------------------------------------------------------------------------------++-- | Discard 'Right's and unwrap 'Left's in an 'Either' stream.+--+-- /Pre-release/+--+{-# INLINE catLefts #-}+catLefts :: (Monad m) => Scanl m a c -> Scanl m (Either a b) c+catLefts = filter isLeft . lmap (fromLeft undefined)++-- | Discard 'Left's and unwrap 'Right's in an 'Either' stream.+--+-- /Pre-release/+--+{-# INLINE catRights #-}+catRights :: (Monad m) => Scanl m b c -> Scanl m (Either a b) c+catRights = filter isRight . lmap (fromRight undefined)++-- | Remove the either wrapper and flatten both lefts and as well as rights in+-- the output stream.+--+-- Definition:+--+-- >>> catEithers = Scanl.lmap (either id id)+--+-- /Pre-release/+--+{-# INLINE catEithers #-}+catEithers :: Scanl m a b -> Scanl m (Either a a) b+catEithers = lmap (either id id)++------------------------------------------------------------------------------+-- Parsing+------------------------------------------------------------------------------++-- Required to fuse "take" with "many" in "groupsOf", for ghc-9.x+{-# ANN type Tuple'Fused Fuse #-}+data Tuple'Fused a b = Tuple'Fused !a !b deriving Show++{-# INLINE taking #-}+taking :: Monad m => Int -> Scanl m a (Maybe a)+taking n = mkScant step initial extract++ where++ initial =+ if n <= 0+ then Done Nothing+ else Partial (Tuple'Fused n Nothing)++ step (Tuple'Fused i _) a =+ if i > 1+ then Partial (Tuple'Fused (i - 1) (Just a))+ else Done (Just a)++ extract (Tuple'Fused _ r) = r++{-# INLINE dropping #-}+dropping :: Monad m => Int -> Scanl m a (Maybe a)+dropping n = mkScant step initial extract++ where++ initial = Partial (Tuple'Fused n Nothing)++ step (Tuple'Fused i _) a =+ if i > 0+ then Partial (Tuple'Fused (i - 1) Nothing)+ else Partial (Tuple'Fused i (Just a))++ extract (Tuple'Fused _ r) = r++-- | Take at most @n@ input elements and scan them using the supplied scan. A+-- negative count is treated as 0.+--+-- >>> Stream.toList $ Stream.scanl (Scanl.take 2 Scanl.toList) $ Stream.fromList [1..10]+-- [[],[1],[1,2]]+--+{-# INLINE take #-}+take :: Monad m => Int -> Scanl m a b -> Scanl m a b+-- take n = postscanlMaybe (taking n)+take n (Scanl fstep finitial fextract ffinal) = Scanl step initial extract final++ where++ {-# INLINE next #-}+ next i res =+ case res of+ Partial s -> do+ let i1 = i + 1+ s1 = Tuple'Fused i1 s+ if i1 < n+ then return $ Partial s1+ else Done <$> ffinal s+ Done b -> return $ Done b++ initial = finitial >>= next (-1)++ step (Tuple'Fused i r) a = fstep r a >>= next i++ extract (Tuple'Fused _ r) = fextract r++ final (Tuple'Fused _ r) = ffinal r++-- Note: Keep this consistent with S.splitOn. In fact we should eliminate+-- S.splitOn in favor of the fold.+--+-- XXX Use Scanl.many instead once it is fixed.+-- > Stream.splitOnSuffix p f = Stream.foldMany (Scanl.takeEndBy_ p f)++-- | Like 'takeEndBy' but drops the element on which the predicate succeeds.+--+-- Example:+--+-- >>> input = Stream.fromList "hello\nthere\n"+-- >>> line = Scanl.takeEndBy_ (== '\n') Scanl.toList+-- >>> Stream.toList $ Stream.scanl line input+-- ["","h","he","hel","hell","hello","hello"]+--+{-# INLINE takeEndBy_ #-}+takeEndBy_ :: Monad m => (a -> Bool) -> Scanl m a b -> Scanl m a b+-- takeEndBy_ predicate = postscanlMaybe (takingEndBy_ predicate)+takeEndBy_ predicate (Scanl fstep finitial fextract ffinal) =+ Scanl step finitial fextract ffinal++ where++ step s a =+ if not (predicate a)+ then fstep s a+ else Done <$> ffinal s++-- Note:+-- > Stream.splitWithSuffix p f = Stream.foldMany (Scanl.takeEndBy p f)++-- | Take the input, stop when the predicate succeeds taking the succeeding+-- element as well.+--+-- Example:+--+-- >>> input = Stream.fromList "hello\nthere\n"+-- >>> line = Scanl.takeEndBy (== '\n') Scanl.toList+-- >>> Stream.toList $ Stream.scanl line input+-- ["","h","he","hel","hell","hello","hello\n"]+--+{-# INLINE takeEndBy #-}+takeEndBy :: Monad m => (a -> Bool) -> Scanl m a b -> Scanl m a b+-- takeEndBy predicate = postscanlMaybe (takingEndBy predicate)+takeEndBy predicate (Scanl fstep finitial fextract ffinal) =+ Scanl step finitial fextract ffinal++ where++ step s a = do+ res <- fstep s a+ if not (predicate a)+ then return res+ else do+ case res of+ Partial s1 -> Done <$> ffinal s1+ Done b -> return $ Done b++------------------------------------------------------------------------------+-- Nesting+------------------------------------------------------------------------------++-- Similar to the comonad "duplicate" operation.++{-+-- | 'duplicate' provides the ability to run a fold in parts. The duplicated+-- fold consumes the input and returns the same fold as output instead of+-- returning the final result, the returned fold can be run later to consume+-- more input.+--+-- 'duplicate' essentially appends a stream to the fold without finishing the+-- fold. Compare with 'snoc' which appends a singleton value to the fold.+--+-- /Pre-release/+{-# INLINE duplicate #-}+duplicate :: Monad m => Scanl m a b -> Scanl m a (Scanl m a b)+duplicate (Fold step1 initial1 extract1 final1) =+ Scanl step initial extract final++ where++ initial = second fromPure <$> initial1++ step s a = second fromPure <$> step1 s a++ -- Scanning may be problematic due to multiple finalizations.+ extract = error "duplicate: scanning may be problematic"++ final s = pure $ Fold step1 (pure $ Partial s) extract1 final1++-- If there were a finalize/flushing action in the stream type that would be+-- equivalent to running initialize in Scanl. But we do not have a flushing+-- action in streams.++-- | Evaluate the initialization effect of a fold. If we are building the fold+-- by chaining lazy actions in fold init this would reduce the actions to a+-- strict accumulator value.+--+-- /Pre-release/+{-# INLINE reduce #-}+reduce :: Monad m => Scanl m a b -> m (Scanl m a b)+reduce (Scanl step initial extract final) = do+ i <- initial+ return $ Scanl step (return i) extract final++-- This is the dual of Stream @cons@.++-- | Append an effect to the fold lazily, in other words run a single+-- step of the fold.+--+-- /Pre-release/+{-# INLINE snoclM #-}+snoclM :: Monad m => Scanl m a b -> m a -> Scanl m a b+snoclM (Scanl fstep finitial fextract ffinal) action =+ Scanl fstep initial fextract ffinal++ where++ initial = do+ res <- finitial+ case res of+ Partial fs -> action >>= fstep fs+ Done b -> return $ Done b++-- | Append a singleton value to the fold lazily, in other words run a single+-- step of the fold.+--+-- Definition:+--+-- >>> snocl f = Scanl.snoclM f . return+--+-- Example:+--+-- >>> import qualified Data.Foldable as Foldable+-- >>> Scanl.extractM $ Foldable.foldl Scanl.snocl Scanl.toList [1..3]+-- [1,2,3]+--+-- /Pre-release/+{-# INLINE snocl #-}+snocl :: Monad m => Scanl m a b -> a -> Scanl m a b+-- snocl f = snoclM f . return+snocl (Scanl fstep finitial fextract ffinal) a =+ Scanl fstep initial fextract ffinal++ where++ initial = do+ res <- finitial+ case res of+ Partial fs -> fstep fs a+ Done b -> return $ Done b++-- | Append a singleton value to the fold in other words run a single step of+-- the fold.+--+-- Definition:+--+-- >>> snocM f = Scanl.reduce . Scanl.snoclM f+--+-- /Pre-release/+{-# INLINE snocM #-}+snocM :: Monad m => Scanl m a b -> m a -> m (Scanl m a b)+snocM (Scanl step initial extract final) action = do+ res <- initial+ r <- case res of+ Partial fs -> action >>= step fs+ Done _ -> return res+ return $ Scanl step (return r) extract final++-- Definitions:+--+-- >>> snoc f = Scanl.reduce . Scanl.snocl f+-- >>> snoc f = Scanl.snocM f . return++-- | Append a singleton value to the fold, in other words run a single step of+-- the fold.+--+-- Example:+--+-- >>> import qualified Data.Foldable as Foldable+-- >>> Foldable.foldlM Scanl.snoc Scanl.toList [1..3] >>= Scanl.drive Stream.nil+-- [1,2,3]+--+-- /Pre-release/+{-# INLINE snoc #-}+snoc :: Monad m => Scanl m a b -> a -> m (Scanl m a b)+snoc (Scanl step initial extract final) a = do+ res <- initial+ r <- case res of+ Partial fs -> step fs a+ Done _ -> return res+ return $ Scanl step (return r) extract final++-- | Append a singleton value to the fold.+--+-- See examples under 'addStream'.+--+-- /Pre-release/+{-# INLINE addOne #-}+addOne :: Monad m => a -> Scanl m a b -> m (Scanl m a b)+addOne = flip snoc++-- Similar to the comonad "extract" operation.+-- XXX rename to extract. We can use "extr" for the fold extract function.++-- | Extract the accumulated result of the fold.+--+-- Definition:+--+-- >>> extractM = Scanl.drive Stream.nil+--+-- Example:+--+-- >>> Scanl.extractM Scanl.toList+-- []+--+-- /Pre-release/+{-# INLINE extractM #-}+extractM :: Monad m => Scanl m a b -> m b+extractM (Scanl _ initial extract _) = do+ res <- initial+ case res of+ Partial fs -> extract fs+ Done b -> return b++-- | Close a fold so that it does not accept any more input.+{-# INLINE close #-}+close :: Monad m => Scanl m a b -> Scanl m a b+close (Scanl _ initial1 _ final1) =+ Scanl undefined initial undefined undefined++ where++ initial = do+ res <- initial1+ case res of+ Partial s -> Done <$> final1 s+ Done b -> return $ Done b++-- Corresponds to the null check for streams.++-- | Check if the fold has terminated and can take no more input.+--+-- /Pre-release/+{-# INLINE isClosed #-}+isClosed :: Monad m => Scanl m a b -> m Bool+isClosed (Scanl _ initial _ _) = do+ res <- initial+ return $ case res of+ Partial _ -> False+ Done _ -> True+-}++------------------------------------------------------------------------------+-- Parsing+------------------------------------------------------------------------------++-- All the grouping transformation that we apply to a stream can also be+-- applied to a fold input stream. groupBy et al can be written as terminating+-- folds and then we can apply "many" to use those repeatedly on a stream.++-- XXX many should have the following signature:+-- many :: Monad m => Foldl m a b -> Scanl m b c -> Scanl m a (Maybe c)+-- Should return Nothing in the intermediate state and Just when the first fold+-- completes and is fed to the second fold.++{-+{-# ANN type ManyState Fuse #-}+data ManyState s1 s2+ = ManyFirst !s1 !s2+ | ManyLoop !s1 !s2++-- | Collect zero or more applications of a fold. @many first second@ applies+-- the @first@ fold repeatedly on the input stream and accumulates it's results+-- using the @second@ fold.+--+-- >>> two = Scanl.take 2 Scanl.toList+-- >>> twos = Scanl.many two Scanl.toList+-- >>> Stream.fold twos $ Stream.fromList [1..10]+-- [[1,2],[3,4],[5,6],[7,8],[9,10]]+--+-- Stops when @second@ fold stops.+--+-- See also: 'Data.Stream.concatMap', 'Data.Stream.foldMany'+--+{-# INLINE many #-}+many :: Monad m => Scanl m a b -> Scanl m b c -> Scanl m a c+many+ (Scanl sstep sinitial sextract sfinal)+ (Scanl cstep cinitial cextract cfinal) =+ Scanl step initial extract final++ where++ -- cs = collect state+ -- ss = split state+ -- cres = collect state result+ -- sres = split state result+ -- cb = collect done+ -- sb = split done++ -- Caution! There is mutual recursion here, inlining the right functions is+ -- important.++ {-# INLINE split #-}+ split f cs sres =+ case sres of+ Partial ss -> return $ Partial $ f ss cs+ Done sb -> cstep cs sb >>= collect++ collect cres =+ case cres of+ Partial cs -> sinitial >>= split ManyFirst cs+ Done cb -> return $ Done cb++ -- A fold may terminate even without accepting a single input. So we run+ -- the split fold's initial action even if no input is received. However,+ -- this means that if no input was ever received by "step" we discard the+ -- fold's initial result which could have generated an effect. However,+ -- note that if "sinitial" results in Done we do collect its output even+ -- though the fold may not have received any input. XXX Is this+ -- inconsistent?+ initial = cinitial >>= collect++ {-# INLINE step_ #-}+ step_ ss cs a = sstep ss a >>= split ManyLoop cs++ {-# INLINE step #-}+ step (ManyFirst ss cs) a = step_ ss cs a+ step (ManyLoop ss cs) a = step_ ss cs a++ -- Do not extract the split fold if no item was consumed.+ extract (ManyFirst _ cs) = cextract cs+ extract (ManyLoop ss cs) = do+ cres <- sextract ss >>= cstep cs+ case cres of+ Partial s -> cextract s+ Done b -> return b++ final (ManyFirst ss cs) = sfinal ss *> cfinal cs+ final (ManyLoop ss cs) = do+ cres <- sfinal ss >>= cstep cs+ case cres of+ Partial s -> cfinal s+ Done b -> return b++-- | Like many, but the "first" fold emits an output at the end even if no+-- input is received.+--+-- /Internal/+--+-- See also: 'Data.Stream.concatMap', 'Data.Stream.foldMany'+--+{-# INLINE manyPost #-}+manyPost :: Monad m => Scanl m a b -> Scanl m b c -> Scanl m a c+manyPost+ (Scanl sstep sinitial sextract sfinal)+ (Scanl cstep cinitial cextract cfinal) =+ Scanl step initial extract final++ where++ -- cs = collect state+ -- ss = split state+ -- cres = collect state result+ -- sres = split state result+ -- cb = collect done+ -- sb = split done++ -- Caution! There is mutual recursion here, inlining the right functions is+ -- important.++ {-# INLINE split #-}+ split cs sres =+ case sres of+ Partial ss1 -> return $ Partial $ Tuple' ss1 cs+ Done sb -> cstep cs sb >>= collect++ collect cres =+ case cres of+ Partial cs -> sinitial >>= split cs+ Done cb -> return $ Done cb++ initial = cinitial >>= collect++ {-# INLINE step #-}+ step (Tuple' ss cs) a = sstep ss a >>= split cs++ extract (Tuple' ss cs) = do+ cres <- sextract ss >>= cstep cs+ case cres of+ Partial s -> cextract s+ Done b -> return b++ final (Tuple' ss cs) = do+ cres <- sfinal ss >>= cstep cs+ case cres of+ Partial s -> cfinal s+ Done b -> return b++-- | @groupsOf n split collect@ repeatedly applies the @split@ fold to chunks+-- of @n@ items in the input stream and supplies the result to the @collect@+-- fold.+--+-- Definition:+--+-- >>> groupsOf n split = Scanl.many (Scanl.take n split)+--+-- Example:+--+-- >>> twos = Scanl.groupsOf 2 Scanl.toList Scanl.toList+-- >>> Stream.fold twos $ Stream.fromList [1..10]+-- [[1,2],[3,4],[5,6],[7,8],[9,10]]+--+-- Stops when @collect@ stops.+--+{-# INLINE groupsOf #-}+groupsOf :: Monad m => Int -> Scanl m a b -> Scanl m b c -> Scanl m a c+groupsOf n split = many (take n split)++------------------------------------------------------------------------------+-- Refold and Fold Combinators+------------------------------------------------------------------------------++-- | Like 'many' but uses a 'Refold' for collecting.+--+{-# INLINE refoldMany #-}+refoldMany :: Monad m => Scanl m a b -> Refold m x b c -> Refold m x a c+refoldMany+ (Scanl sstep sinitial sextract _sfinal)+ -- XXX We will need a "final" in refold as well+ (Refold cstep cinject cextract) =+ Refold step inject extract++ where++ -- cs = collect state+ -- ss = split state+ -- cres = collect state result+ -- sres = split state result+ -- cb = collect done+ -- sb = split done++ -- Caution! There is mutual recursion here, inlining the right functions is+ -- important.++ {-# INLINE split #-}+ split cs f sres =+ case sres of+ Partial ss -> return $ Partial $ Tuple' cs (f ss)+ Done sb -> cstep cs sb >>= collect++ collect cres =+ case cres of+ Partial cs -> sinitial >>= split cs Left+ Done cb -> return $ Done cb++ inject x = cinject x >>= collect++ {-# INLINE step_ #-}+ step_ ss cs a = sstep ss a >>= split cs Right++ {-# INLINE step #-}+ step (Tuple' cs (Left ss)) a = step_ ss cs a+ step (Tuple' cs (Right ss)) a = step_ ss cs a++ -- Do not extract the split fold if no item was consumed.+ extract (Tuple' cs (Left _)) = cextract cs+ extract (Tuple' cs (Right ss )) = do+ cres <- sextract ss >>= cstep cs+ case cres of+ Partial s -> cextract s+ Done b -> return b++{-# ANN type ConsumeManyState Fuse #-}+data ConsumeManyState x cs ss = ConsumeMany x cs (Either ss ss)++-- | Like 'many' but uses a 'Refold' for splitting.+--+-- /Internal/+{-# INLINE refoldMany1 #-}+refoldMany1 :: Monad m => Refold m x a b -> Scanl m b c -> Refold m x a c+refoldMany1+ (Refold sstep sinject sextract)+ (Scanl cstep cinitial cextract _cfinal) =+ Refold step inject extract++ where++ -- cs = collect state+ -- ss = split state+ -- cres = collect state result+ -- sres = split state result+ -- cb = collect done+ -- sb = split done++ -- Caution! There is mutual recursion here, inlining the right functions is+ -- important.++ {-# INLINE split #-}+ split x cs f sres =+ case sres of+ Partial ss -> return $ Partial $ ConsumeMany x cs (f ss)+ Done sb -> cstep cs sb >>= collect x++ collect x cres =+ case cres of+ Partial cs -> sinject x >>= split x cs Left+ Done cb -> return $ Done cb++ inject x = cinitial >>= collect x++ {-# INLINE step_ #-}+ step_ x ss cs a = sstep ss a >>= split x cs Right++ {-# INLINE step #-}+ step (ConsumeMany x cs (Left ss)) a = step_ x ss cs a+ step (ConsumeMany x cs (Right ss)) a = step_ x ss cs a++ -- Do not extract the split fold if no item was consumed.+ extract (ConsumeMany _ cs (Left _)) = cextract cs+ extract (ConsumeMany _ cs (Right ss )) = do+ cres <- sextract ss >>= cstep cs+ case cres of+ Partial s -> cextract s+ Done b -> return b++-- | Extract the output of a fold and refold it using a 'Refold'.+--+-- A fusible alternative to 'concatMap'.+--+-- /Internal/+{-# INLINE refold #-}+refold :: Monad m => Refold m b a c -> Scanl m a b -> Scanl m a c+refold (Refold step inject extract) f =+ Scanl step (extractM f >>= inject) extract extract+-}++------------------------------------------------------------------------------+-- morphInner+------------------------------------------------------------------------------++-- | Change the underlying monad of a scan. Also known as hoist.+--+-- /Pre-release/+morphInner :: (forall x. m x -> n x) -> Scanl m a b -> Scanl n a b+morphInner f (Scanl step initial extract final) =+ Scanl (\x a -> f $ step x a) (f initial) (f . extract) (f . final)++-- | Adapt a pure scan to any monad.+--+-- >>> generalizeInner = Scanl.morphInner (return . runIdentity)+--+-- /Pre-release/+generalizeInner :: Monad m => Scanl Identity a b -> Scanl m a b+generalizeInner = morphInner (return . runIdentity)
+ src/Streamly/Internal/Data/Scanl/Window.hs view
@@ -0,0 +1,513 @@+{-# LANGUAGE CPP #-}+-- |+-- Module : Streamly.Internal.Data.Scanl.Window+-- Copyright : (c) 2020 Composewell Technologies+-- License : Apache-2.0+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- Simple incremental statistical measures over a stream of data. All+-- operations use numerically stable floating point arithmetic.+--+-- Measurements can be performed over the entire input stream or on a sliding+-- window of fixed or variable size. Where possible, measures are computed+-- online without buffering the input stream.+--+-- Currently there is no overflow detection.+--+-- For more advanced statistical measures see the @streamly-statistics@+-- package.++-- XXX A window scan can be driven either using the RingArray.slidingWindow+-- combinator or by zipping nthLast scan and last scan.++module Streamly.Internal.Data.Scanl.Window+ (+ -- * Types+ Incr (..)++ -- * Running Incremental Scans+ -- | Scans of type @Scanl m (Incr a) b@ are incremental sliding-window+ -- scans. Names of such scans are prefixed with @incr@. An input of type+ -- @(Insert a)@ indicates that the input element @a@ is being inserted in+ -- the window without ejecting an old value, increasing the window size by+ -- 1. An input of type @(Replace a a)@ indicates that the first argument of+ -- Replace is being removed from the window and the second argument is being+ -- inserted in the window, the window size remains the same. The window+ -- size can only increase and never decrease.+ --+ -- You can compute the statistics over the entire stream using window scans+ -- by always supplying input of type @Insert a@.+ --+ -- The incremental scans are converted into scans over a window using the+ -- 'incrScan' operation which maintains a sliding window and supplies the+ -- new and/or exiting element of the window to the window scan in the form+ -- of an incremental operation. The names of window scans are prefixed with+ -- @window@.+ --+ , cumulativeScan+ , incrScan+ , incrScanWith++ -- * Incremental Scans+ , incrRollingMap -- XXX remove?+ , incrRollingMapM -- XXX remove?++ -- ** Sums+ , incrCount+ , incrSum+ , incrSumInt+ , incrPowerSum+ , incrPowerSumFrac++ -- ** Location+ , windowRange+ , windowMinimum+ , windowMaximum+ , incrMean+ )+where++import Control.Monad.IO.Class (MonadIO (liftIO))+import Data.Proxy (Proxy(..))+import Fusion.Plugin.Types (Fuse(..))+import Streamly.Internal.Data.RingArray (RingArray(..))+import Streamly.Internal.Data.Scanl.Type (Scanl(..), Step(..))+import Streamly.Internal.Data.Tuple.Strict+ (Tuple'(..), Tuple3Fused' (Tuple3Fused'))+import Streamly.Internal.Data.Unbox (Unbox(..))++import qualified Streamly.Internal.Data.MutArray.Type as MutArray+import qualified Streamly.Internal.Data.RingArray as RingArray+import qualified Streamly.Internal.Data.Scanl.Type as Scanl++import Prelude hiding (length, sum, minimum, maximum)++#include "ArrayMacros.h"+#include "DocTestDataScanl.hs"++-------------------------------------------------------------------------------+-- Incremental operations+-------------------------------------------------------------------------------++-- The delete operation could be quite useful e.g. if we are computing stats+-- over last one hour of trades. The window would be growing when trade+-- frequency is increasing, the window would remain constant when the trade+-- frequency is steady, but it would shrink when the trade frequency reduces.+-- If no trades are happening our clock would still be ticking and to maintain+-- a 1 hour window we would be ejecting the oldest elements from the window+-- even without any other elements entering the window. In fact, it is required+-- for time based windows.+--+-- Replace can be implemented using Insert and Delete.+--++-- | Represents incremental input for a scan. 'Insert' means a new element is+-- being added to the collection, 'Replace' means an old value in the+-- collection is being replaced with a new value.+data Incr a =+ Insert !a+ -- | Delete !a+ | Replace !a !a -- ^ Replace old new++instance Functor Incr where+ fmap f (Insert x) = Insert (f x)+ -- fmap f (Delete x) = Delete (f x)+ fmap f (Replace x y) = Replace (f x) (f y)++-------------------------------------------------------------------------------+-- Utilities+-------------------------------------------------------------------------------++{-# ANN type SlidingWindow Fuse #-}+data SlidingWindow a r s = SWArray !a !Int !s | SWRing !r !s+-- data SlidingWindow a s = SWArray !a !Int !s !Int | SWRing !a !Int !s++-- | Like 'incrScan' but also provides the ring array to the scan. The ring+-- array reflects the state of the ring after inserting the incoming element.+--+-- IMPORTANT NOTE: The ring is mutable, therefore, references to it should not+-- be stored and used later, the state would have changed by then. If you need+-- to store it then copy it to an array or another ring and store it.+{-# INLINE incrScanWith #-}+incrScanWith :: forall m a b. (MonadIO m, Unbox a)+ => Int -> Scanl m (Incr a, RingArray a) b -> Scanl m a b+incrScanWith n (Scanl step1 initial1 extract1 final1) =+ Scanl step initial extract final++ where++ initial = do+ if n <= 0+ then error "Window size must be > 0"+ else do+ r <- initial1+ arr <- liftIO $ MutArray.emptyOf n+ return $+ case r of+ Partial s -> Partial $ SWArray arr (0 :: Int) s+ Done b -> Done b++ step (SWArray arr i st) a = do+ -- XXX compare this with the slidingWindow impl+ arr1 <- liftIO $ MutArray.unsafeSnoc arr a+ r <- step1 st (Insert a, RingArray.unsafeCastMutArray arr1)+ return $ case r of+ Partial s ->+ let i1 = i + 1+ in if i1 < n+ then Partial $ SWArray arr1 i1 s+ else Partial $ SWRing (RingArray.unsafeCastMutArray arr1) s+ Done b -> Done b++ step (SWRing rb st) a = do+ (rb1, old) <- RingArray.replace rb a+ r <- step1 st (Replace old a, rb1)+ return $+ case r of+ Partial s -> Partial $ SWRing rb1 s+ Done b -> Done b++ extract (SWArray _ _ st) = extract1 st+ extract (SWRing _ st) = extract1 st++ final (SWArray _ _ st) = final1 st+ final (SWRing _ st) = final1 st++ -- Alternative implementation flattening the constructors+ -- Improves some benchmarks, worsens some others, need more investigation.+ {-+ initial = do+ if n <= 0+ then error "Window size must be > 0"+ else do+ r <- initial1+ arr :: MutArray.MutArray a <- liftIO $ MutArray.emptyOf n+ return $+ case r of+ Partial s -> Partial+ $ SWArray (MutArray.arrContents arr) 0 s n+ Done b -> Done b++ step (SWArray mba rh st i) a = do+ RingArray _ _ rh1 <- RingArray.insert_ (RingArray mba (n * SIZE_OF(a)) rh) a+ r <- step1 st (Insert a, RingArray mba ((n - i) * SIZE_OF(a)) rh1)+ return $+ case r of+ Partial s ->+ if i > 0+ then Partial $ SWArray mba rh1 s (i-1)+ else Partial $ SWRing mba rh1 s+ Done b -> Done b++ step (SWRing mba rh st) a = do+ (rb1@(RingArray _ _ rh1), old) <-+ RingArray.insert (RingArray mba (n * SIZE_OF(a)) rh) a+ r <- step1 st (Replace old a, rb1)+ return $+ case r of+ Partial s -> Partial $ SWRing mba rh1 s+ Done b -> Done b++ extract (SWArray _ _ st _) = extract1 st+ extract (SWRing _ _ st) = extract1 st++ final (SWArray _ _ st _) = final1 st+ final (SWRing _ _ st) = final1 st+ -}++-- | @incrScan collector@ is an incremental sliding window scan that does not+-- require all the intermediate elements in each step of the scan computation.+-- This maintains @n@ elements in the window, when a new element comes it+-- slides out the oldest element. The new element along with the old element+-- are supplied to the collector scan.+--+{-# INLINE incrScan #-}+incrScan :: forall m a b. (MonadIO m, Unbox a)+ => Int -> Scanl m (Incr a) b -> Scanl m a b+incrScan n f = incrScanWith n (Scanl.lmap fst f)++-- | Convert an incremental scan to a cumulative scan using the entire input+-- stream as a single window.+--+-- >>> cumulativeScan = Scanl.lmap Scanl.Insert+--+{-# INLINE cumulativeScan #-}+cumulativeScan :: Scanl m (Incr a) b -> Scanl m a b+cumulativeScan = Scanl.lmap Insert++-- | Apply an effectful function on the entering and the exiting element of the+-- window. The first argument of the mapped function is the exiting element and+-- the second argument is the entering element.+{-# INLINE incrRollingMapM #-}+incrRollingMapM :: Monad m =>+ (Maybe a -> a -> m (Maybe b)) -> Scanl m (Incr a) (Maybe b)+incrRollingMapM f = Scanl.mkScanlM f1 initial++ where++ initial = return Nothing++ f1 _ (Insert a) = f Nothing a+ -- f1 _ (Delete _) = return Nothing+ f1 _ (Replace old new) = f (Just old) new++-- | Apply a pure function on the latest and the oldest element of the window.+--+-- >>> incrRollingMap f = Scanl.incrRollingMapM (\x y -> return $ f x y)+--+{-# INLINE incrRollingMap #-}+incrRollingMap :: Monad m =>+ (Maybe a -> a -> Maybe b) -> Scanl m (Incr a) (Maybe b)+incrRollingMap f = Scanl.mkScanl f1 initial++ where++ initial = Nothing++ f1 _ (Insert a) = f Nothing a+ -- f1 _ (Delete _) = Nothing+ f1 _ (Replace old new) = f (Just old) new++-------------------------------------------------------------------------------+-- Sum+-------------------------------------------------------------------------------++-- XXX Overflow.++-- | The sum of all the elements in a rolling window. The input elements are+-- required to be integral numbers.+--+-- This was written in the hope that it would be a tiny bit faster than 'incrSum'+-- for 'Integral' values. But turns out that 'incrSum' is 2% faster than this even+-- for integral values!+--+-- /Internal/+--+{-# INLINE incrSumInt #-}+incrSumInt :: forall m a. (Monad m, Integral a) => Scanl m (Incr a) a+incrSumInt = Scanl step initial extract extract++ where++ initial = return $ Partial (0 :: a)++ step s (Insert a) = return $ Partial (s + a)+ -- step s (Delete a) = return $ Partial (s - a)+ step s (Replace old new) = return $ Partial (s + new - old)++ extract = return++-- XXX Overflow.++-- | Sum of all the elements in a rolling window:+--+-- \(S = \sum_{i=1}^n x_{i}\)+--+-- This is the first power sum.+--+-- >>> incrSum = Scanl.incrPowerSum 1+--+-- Uses Kahan-Babuska-Neumaier style summation for numerical stability of+-- floating precision arithmetic.+--+-- /Space/: \(\mathcal{O}(1)\)+--+-- /Time/: \(\mathcal{O}(n)\)+--+{-# INLINE incrSum #-}+incrSum :: forall m a. (Monad m, Num a) => Scanl m (Incr a) a+incrSum = Scanl step initial extract extract++ where++ initial =+ return+ $ Partial+ $ Tuple'+ (0 :: a) -- running sum+ (0 :: a) -- accumulated rounding error++ add total incr =+ let+ -- total is large and incr may be small, we may round incr here but+ -- we will accumulate the rounding error in err1 in the next step.+ total1 = total + incr+ -- Accumulate any rounding error in err1+ -- XXX In the Insert case we may lose err, therefore we+ -- should use ((total1 - total) - new) + err here.+ -- Or even in the Replace case if (new - old) is large we may lose+ -- err, so we should use ((total1 - total) + (old - new)) + err.+ err1 = (total1 - total) - incr+ in return $ Partial $ Tuple' total1 err1++ step (Tuple' total err) (Insert new) =+ -- XXX if new is large we may lose err+ let incr = new - err+ in add total incr+ {-+ step (Tuple' total err) (Delete new) =+ -- XXX if new is large we may lose err+ let incr = -new - err+ in add total incr+ -}+ step (Tuple' total err) (Replace old new) =+ -- XXX if (new - old) is large we may lose err+ let incr = (new - old) - err+ in add total incr++ extract (Tuple' total _) = return total++-- | The number of elements in the rolling window.+--+-- This is the \(0\)th power sum.+--+-- >>> incrCount = Scanl.incrPowerSum 0+--+{-# INLINE incrCount #-}+incrCount :: (Monad m, Num b) => Scanl m (Incr a) b+incrCount = Scanl.mkScanl step 0++ where++ step w (Insert _) = w + 1+ -- step w (Delete _) = w - 1+ step w (Replace _ _) = w++-- | Sum of the \(k\)th power of all the elements in a rolling window:+--+-- \(S_k = \sum_{i=1}^n x_{i}^k\)+--+-- >>> incrPowerSum k = Scanl.lmap (fmap (^ k)) Scanl.incrSum+--+-- /Space/: \(\mathcal{O}(1)\)+--+-- /Time/: \(\mathcal{O}(n)\)+{-# INLINE incrPowerSum #-}+incrPowerSum :: (Monad m, Num a) => Int -> Scanl m (Incr a) a+incrPowerSum k = Scanl.lmap (fmap (^ k)) incrSum++-- | Like 'incrPowerSum' but powers can be negative or fractional. This is+-- slower than 'incrPowerSum' for positive intergal powers.+--+-- >>> incrPowerSumFrac p = Scanl.lmap (fmap (** p)) Scanl.incrSum+--+{-# INLINE incrPowerSumFrac #-}+incrPowerSumFrac :: (Monad m, Floating a) => a -> Scanl m (Incr a) a+incrPowerSumFrac p = Scanl.lmap (fmap (** p)) incrSum++-------------------------------------------------------------------------------+-- Location+-------------------------------------------------------------------------------++{-# INLINE ringRange #-}+ringRange :: (MonadIO m, Unbox a, Ord a) => RingArray a -> m (Maybe (a, a))+-- Ideally this should perform the same as the implementation below, but it is+-- 2x worse, need to investigate why.+-- ringRange = RingArray.fold (Fold.fromScanl Scanl.range)+ringRange rb@RingArray{..} = do+ if ringSize == 0+ then return Nothing+ else do+ x <- liftIO $ peekAt 0 ringContents+ let accum (mn, mx) a = return (min mn a, max mx a)+ in fmap Just $ RingArray.foldlM' accum (x, x) rb++-- | Determine the maximum and minimum in a rolling window.+--+-- This implementation traverses the entire window buffer to compute the+-- range whenever we demand it. It performs better than the dequeue based+-- implementation in @streamly-statistics@ package when the window size is+-- small (< 30).+--+-- If you want to compute the range of the entire stream+-- 'Streamly.Data.Scanl.range' would be much faster.+--+-- /Space/: \(\mathcal{O}(n)\) where @n@ is the window size.+--+-- /Time/: \(\mathcal{O}(n*w)\) where \(w\) is the window size.+--+{-# INLINE windowRange #-}+windowRange :: forall m a. (MonadIO m, Unbox a, Ord a) =>+ Int -> Scanl m a (Maybe (a, a))+-- windowRange = RingArray.scanFoldRingsBy (Fold.fromScanl Scanl.range)++-- Ideally this should perform the same as the implementation below which is+-- just expanded form of this. Some inlining/exitify optimization makes this+-- perform much worse. Need to investigate and fix that.+-- windowRange = RingArray.scanCustomFoldRingsBy ringRange++windowRange n = Scanl step initial extract extract++ where++ initial =+ if n <= 0+ then error "ringsOf: window size must be > 0"+ else do+ arr :: MutArray.MutArray a <- liftIO $ MutArray.emptyOf n+ return $ Partial $ Tuple3Fused' (MutArray.arrContents arr) 0 0++ step (Tuple3Fused' mba rh i) a = do+ RingArray _ _ rh1 <- RingArray.replace_ (RingArray mba (n * SIZE_OF(a)) rh) a+ return $ Partial $ Tuple3Fused' mba rh1 (i + 1)++ -- XXX exitify optimization causes a problem here when modular scans are+ -- used. Sometimes inlining "extract" is helpful.+ -- {-# INLINE extract #-}+ extract (Tuple3Fused' mba rh i) =+ -- XXX If newest is lower than the current min than new is the min.+ -- XXX Otherwise if exiting one was equal to min only then we need to find+ -- new min+ let rs = min i n * SIZE_OF(a)+ rh1 = if i <= n then 0 else rh+ in ringRange $ RingArray mba rs rh1++-- | Find the minimum element in a rolling window.+--+-- See the performance related comments in 'windowRange'.+--+-- If you want to compute the minimum of the entire stream+-- 'Streamly.Data.Scanl.minimum' is much faster.+--+-- /Time/: \(\mathcal{O}(n*w)\) where \(w\) is the window size.+--+{-# INLINE windowMinimum #-}+windowMinimum :: (MonadIO m, Unbox a, Ord a) => Int -> Scanl m a (Maybe a)+windowMinimum n = fmap (fmap fst) $ windowRange n+-- windowMinimum = RingArray.scanFoldRingsBy (Fold.fromScanl Scanl.minimum)++-- | The maximum element in a rolling window.+--+-- See the performance related comments in 'windowRange'.+--+-- If you want to compute the maximum of the entire stream+-- 'Streamly.Data.Scanl.maximum' would be much faster.+--+-- /Time/: \(\mathcal{O}(n*w)\) where \(w\) is the window size.+--+{-# INLINE windowMaximum #-}+windowMaximum :: (MonadIO m, Unbox a, Ord a) => Int -> Scanl m a (Maybe a)+windowMaximum n = fmap (fmap snd) $ windowRange n+-- windowMaximum = RingArray.scanFoldRingsBy (Fold.fromScanl Scanl.maximum)++-- XXX Returns NaN on empty stream.+-- XXX remove teeWith for better fusion?++-- | Arithmetic mean of elements in a sliding window:+--+-- \(\mu = \frac{\sum_{i=1}^n x_{i}}{n}\)+--+-- This is also known as the Simple Moving Average (SMA) when used in the+-- sliding window and Cumulative Moving Avergae (CMA) when used on the entire+-- stream.+--+-- >>> incrMean = Scanl.teeWith (/) Scanl.incrSum Scanl.incrCount+--+-- /Space/: \(\mathcal{O}(1)\)+--+-- /Time/: \(\mathcal{O}(n)\)+{-# INLINE incrMean #-}+incrMean :: forall m a. (Monad m, Fractional a) => Scanl m (Incr a) a+incrMean = Scanl.teeWith (/) incrSum incrCount
+ src/Streamly/Internal/Data/Scanr.hs view
@@ -0,0 +1,401 @@+-- |+-- Module : Streamly.Internal.Data.Scanr+-- Copyright : (c) 2019 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- Right scans.+--+-- == Scanr vs Stream+--+-- A scan is a generalization of a stream. Like streams, a scan has an internal+-- state. Unlike a stream, a scan produces an output only on an input, the+-- output is a function of the scan state and the input. A scan produces at+-- most one output on one input, in other words it is driven solely by the+-- input, it cannot produce output on its own. A @Scanr m () a@ can represent a+-- @Stream m a@ by supplying the scan with () inputs.+--+-- == Scans vs pipes:+--+-- A scan is a simpler version of pipes. It can produce at most one output on+-- one input. Whereas a pipe may produce output even without consuming anything+-- or it can produce multiple outputs on a single input. Scans are simpler+-- abstractions to think about compared to pipes and easier for the compiler to+-- optimize and fuse.+--+-- == Compositions+--+-- Append: this is the easiest. The behavior is simple even in presence of+-- filtering (Skip) and termination (Stop). Skip translates to Skip, Stop+-- translates to Stop.+--+-- demux: we select one of n scans to run. Behaviour with Skip is straight+-- forward. Termination behavior has multiple options, stop when first one+-- stops, stop when the last one stops, or stop when a selected one stops.+--+-- zip: run all and zip the outputs. If one of them Skips we Skip the output.+-- If one of them stops we stop. It may be possible to collect the outputs as+-- Just/Nothing values.+--+-- Another option could be if a Scan terminates do we want to start it again or+-- not.++module Streamly.Internal.Data.Scanr+ (+ -- * Type+ Scanr (..)++ -- * Primitive Scans+ , identity+ , function+ , functionM+ , filter+ , filterM++ -- * Combinators+ , compose+ , teeWithMay+ , teeWith+ , tee++ -- * Scans+ , length+ , sum+ )+where++#include "inline.hs"+import Control.Arrow (Arrow(..))+import Control.Category (Category(..))+import Data.Maybe (isJust, fromJust)+import Fusion.Plugin.Types (Fuse(..))+import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))+import Streamly.Internal.Data.Stream.Step (Step (..))++import qualified Prelude++import Prelude hiding+ (filter, length, sum, zipWith, map, mapM, id, unzip, null)++-- $setup+-- >>> :m+-- >>> :set -XFlexibleContexts+-- >>> import Control.Category+--+-- >>> import qualified Streamly.Internal.Data.Fold as Fold+-- >>> import qualified Streamly.Internal.Data.Scanr as Scanr+-- >>> import qualified Streamly.Internal.Data.Stream as Stream++------------------------------------------------------------------------------+-- Scans+------------------------------------------------------------------------------++-- A core difference between the Scan type and the Fold type is that Scan can+-- stop without producing an output, this is required to represent an empty+-- stream. For this reason a Scan cannot be represented using a Fold because a+-- fold requires a value to be produced on Stop.++-- A core difference between a Scan and a Stream is that a scan produces an+-- output only on an input while a stream can produce output without consuming+-- an input.+--+-- XXX A scan may have buffered data which may have to be drained if the driver+-- has no more input to supply. So we need a finalizer which produces a+-- (possibly empty) stream.+--+-- XXX We should add finalizer (and Error constructor?) to it before we+-- release it.++-- | Represents a stateful transformation over an input stream of values of+-- type @a@ to outputs of type @b@ in 'Monad' @m@.+--+-- The constructor is @Scan consume initial@.+data Scanr m a b =+ forall s. Scanr+ (s -> a -> m (Step s b))+ s++------------------------------------------------------------------------------+-- Functor: Mapping on the output+------------------------------------------------------------------------------++-- | 'fmap' maps a pure function on a scan output.+--+-- >>> Stream.toList $ Stream.scanr (fmap (+1) Scanr.identity) $ Stream.fromList [1..5::Int]+-- [2,3,4,5,6]+--+instance Functor m => Functor (Scanr m a) where+ {-# INLINE_NORMAL fmap #-}+ fmap f (Scanr consume initial) = Scanr consume1 initial++ where++ {-# INLINE_LATE consume1 #-}+ consume1 s b = fmap (fmap f) (consume s b)++-------------------------------------------------------------------------------+-- Category+-------------------------------------------------------------------------------++-- XXX We can call this append, because corresponding operation in stream is+-- also append.++-- | Connect two scans in series. Attach the first scan on the output of the+-- second scan.+--+-- >>> import Control.Category+-- >>> Stream.toList $ Stream.scanr (Scanr.function (+1) >>> Scanr.function (+1)) $ Stream.fromList [1..5::Int]+-- [3,4,5,6,7]+--+{-# INLINE_NORMAL compose #-}+compose :: Monad m => Scanr m b c -> Scanr m a b -> Scanr m a c+compose+ (Scanr stepR initialR)+ (Scanr stepL initialL) = Scanr step (initialL, initialR)++ where++ -- XXX Use strict tuple?+ step (sL, sR) x = do+ rL <- stepL sL x+ case rL of+ Yield bL sL1 -> do+ rR <- stepR sR bL+ return+ $ case rR of+ Yield br sR1 -> Yield br (sL1, sR1)+ Skip sR1 -> Skip (sL1, sR1)+ Stop -> Stop+ Skip sL1 -> return $ Skip (sL1, sR)+ Stop -> return Stop++-- | A scan representing mapping of a monadic action.+--+-- >>> Stream.toList $ Stream.scanr (Scanr.functionM print) $ Stream.fromList [1..5::Int]+-- 1+-- 2+-- 3+-- 4+-- 5+-- [(),(),(),(),()]+--+{-# INLINE functionM #-}+functionM :: Monad m => (a -> m b) -> Scanr m a b+functionM f = Scanr (\() a -> fmap (`Yield` ()) (f a)) ()++-- | A scan representing mapping of a pure function.+--+-- >>> Stream.toList $ Stream.scanr (Scanr.function (+1)) $ Stream.fromList [1..5::Int]+-- [2,3,4,5,6]+--+{-# INLINE function #-}+function :: Monad m => (a -> b) -> Scanr m a b+function f = functionM (return Prelude.. f)++{- HLINT ignore "Redundant map" -}++-- | An identity scan producing the same output as input.+--+-- >>> identity = Scanr.function Prelude.id+--+-- >>> Stream.toList $ Stream.scanr (Scanr.identity) $ Stream.fromList [1..5::Int]+-- [1,2,3,4,5]+--+{-# INLINE identity #-}+identity :: Monad m => Scanr m a a+identity = function Prelude.id++instance Monad m => Category (Scanr m) where+ {-# INLINE id #-}+ id = identity++ {-# INLINE (.) #-}+ (.) = compose++-------------------------------------------------------------------------------+-- Applicative Zip+-------------------------------------------------------------------------------++{-# ANN type TeeWith Fuse #-}+data TeeWith sL sR = TeeWith !sL !sR++-- XXX zipWith?++-- | Connect two scans in parallel. Distribute the input across two scans and+-- zip their outputs. If the scan filters the output, 'Nothing' is emitted+-- otherwise 'Just' is emitted. The scan stops if any of the scans stop.+--+-- >>> Stream.toList $ Stream.scanr (Scanr.teeWithMay (,) Scanr.identity (Scanr.function (\x -> x * x))) $ Stream.fromList [1..5::Int]+-- [(Just 1,Just 1),(Just 2,Just 4),(Just 3,Just 9),(Just 4,Just 16),(Just 5,Just 25)]+--+{-# INLINE_NORMAL teeWithMay #-}+teeWithMay :: Monad m =>+ (Maybe b -> Maybe c -> d) -> Scanr m a b -> Scanr m a c -> Scanr m a d+teeWithMay f (Scanr stepL initialL) (Scanr stepR initialR) =+ Scanr step (TeeWith initialL initialR)++ where++ step (TeeWith sL sR) a = do+ resL <- stepL sL a+ resR <- stepR sR a+ return+ $ case resL of+ Yield bL sL1 ->+ case resR of+ Yield bR sR1 ->+ Yield+ (f (Just bL) (Just bR))+ (TeeWith sL1 sR1)+ Skip sR1 ->+ Yield+ (f (Just bL) Nothing)+ (TeeWith sL1 sR1)+ Stop -> Stop+ Skip sL1 ->+ case resR of+ Yield bR sR1 ->+ Yield+ (f Nothing (Just bR))+ (TeeWith sL1 sR1)+ Skip sR1 ->+ Yield+ (f Nothing Nothing)+ (TeeWith sL1 sR1)+ Stop -> Stop+ Stop -> Stop++-- | Produces an output only when both the scans produce an output. If any of+-- the scans skips the output then the composed scan also skips. Stops when any+-- of the scans stop.+--+-- >>> Stream.toList $ Stream.scanr (Scanr.teeWith (,) Scanr.identity (Scanr.function (\x -> x * x))) $ Stream.fromList [1..5::Int]+-- [(1,1),(2,4),(3,9),(4,16),(5,25)]+--+{-# INLINE_NORMAL teeWith #-}+teeWith :: Monad m =>+ (b -> c -> d) -> Scanr m a b -> Scanr m a c -> Scanr m a d+teeWith f s1 s2 =+ fmap fromJust+ $ compose (filter isJust)+ $ teeWithMay (\b c -> f <$> b <*> c) s1 s2++-- | Zips the outputs only when both scans produce outputs, discards otherwise.+instance Monad m => Applicative (Scanr m a) where+ {-# INLINE pure #-}+ pure b = Scanr (\_ _ -> pure $ Yield b ()) ()++ (<*>) = teeWith id++{-# INLINE_NORMAL tee #-}+tee :: Monad m => Scanr m a b -> Scanr m a c -> Scanr m a (b,c)+tee = teeWith (,)++-------------------------------------------------------------------------------+-- Arrow+-------------------------------------------------------------------------------++-- | Use the first scan for the first element of the tuple and second scan for+-- the second. Zip the outputs. Emits 'Nothing' if no output is generated by+-- the scan, otherwise emits 'Just'. Stops as soon as any one of the scans+-- stop.+--+{-# INLINE_NORMAL unzipMay #-}+unzipMay :: Monad m =>+ Scanr m a x -> Scanr m b y -> Scanr m (a, b) (Maybe x, Maybe y)+unzipMay (Scanr stepL initialL) (Scanr stepR initialR) =+ Scanr step (Tuple' initialL initialR)++ where++ step (Tuple' sL sR) (a, b) = do+ resL <- stepL sL a+ resR <- stepR sR b+ return+ $ case resL of+ Yield bL sL1 ->+ case resR of+ Yield bR sR1 ->+ Yield+ (Just bL, Just bR)+ (Tuple' sL1 sR1)+ Skip sR1 ->+ Yield+ (Just bL, Nothing)+ (Tuple' sL1 sR1)+ Stop -> Stop+ Skip sL1 ->+ case resR of+ Yield bR sR1 ->+ Yield+ (Nothing, Just bR)+ (Tuple' sL1 sR1)+ Skip sR1 ->+ Yield+ (Nothing, Nothing)+ (Tuple' sL1 sR1)+ Stop -> Stop+ Stop -> Stop++-- | Like 'unzipMay' but produces an output only when both the scans produce an+-- output. Other outputs are filtered out.+{-# INLINE_NORMAL unzip #-}+unzip :: Monad m => Scanr m a x -> Scanr m b y -> Scanr m (a, b) (x, y)+unzip s1 s2 = fmap (fromJust Prelude.. f) $ unzipMay s1 s2++ where++ f (mx, my) =+ case mx of+ Just x ->+ case my of+ Just y -> Just (x, y)+ Nothing -> Nothing+ Nothing -> Nothing++instance Monad m => Arrow (Scanr m) where+ {-# INLINE arr #-}+ arr = function++ {-# INLINE (***) #-}+ (***) = unzip++ {-# INLINE (&&&) #-}+ (&&&) = teeWith (,)++-------------------------------------------------------------------------------+-- Primitive scans+-------------------------------------------------------------------------------++-- | A filtering scan using a monadic predicate.+{-# INLINE filterM #-}+filterM :: Monad m => (a -> m Bool) -> Scanr m a a+filterM f = Scanr (\() a -> f a >>= g a) ()++ where++ {-# INLINE g #-}+ g a b =+ return+ $ if b+ then Yield a ()+ else Skip ()++-- | A filtering scan using a pure predicate.+--+-- >>> Stream.toList $ Stream.scanr (Scanr.filter odd) $ Stream.fromList [1..5::Int]+-- [1,3,5]+--+{-# INLINE filter #-}+filter :: Monad m => (a -> Bool) -> Scanr m a a+filter f = filterM (return Prelude.. f)++{-# INLINE length #-}+length :: Monad m => Scanr m a Int+length = Scanr (\acc _ -> pure $ let !n = acc + 1 in Yield n n) 0++{-# INLINE sum #-}+sum :: (Monad m, Num a) => Scanr m a a+sum = Scanr (\acc x -> pure $ let !n = acc + x in Yield n n) 0
+ src/Streamly/Internal/Data/Serialize/TH.hs view
@@ -0,0 +1,529 @@+{-# LANGUAGE TemplateHaskell #-}++-- |+-- Module : Streamly.Internal.Data.Serialize.TH+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC++-- XXX Instead of applying the product constructor in one go can we apply it+-- one at a time in case of too many arguments, compiler may not have to save+-- them in local vars.++module Streamly.Internal.Data.Serialize.TH+ (+ -- Deriving+ deriveSerialize+ , deriveSerializeWith++ -- Utilities+ , module Streamly.Internal.Data.Serialize.TH.Bottom+ -- ** Common+ , module Streamly.Internal.Data.Serialize.TH.Common+ -- ** RecHeader+ , module Streamly.Internal.Data.Serialize.TH.RecHeader+ ) where++--------------------------------------------------------------------------------+-- Imports+--------------------------------------------------------------------------------++import Data.Foldable (length, foldMap)+import Data.List (foldl')+import Data.Word (Word16, Word32, Word64, Word8)++import Language.Haskell.TH+import Language.Haskell.TH.Syntax+import Streamly.Internal.Data.Serialize.Type++import Streamly.Internal.Data.Unbox.TH+ ( DataCon(..)+ , DataType(..)+ , reifyDataType+ )++import qualified Streamly.Internal.Data.Serialize.TH.RecHeader as RecHeader++import Streamly.Internal.Data.Serialize.TH.Bottom+import Streamly.Internal.Data.Serialize.TH.Common+import Streamly.Internal.Data.Serialize.TH.RecHeader+import Prelude hiding (Foldable(..))++--------------------------------------------------------------------------------+-- Domain specific helpers+--------------------------------------------------------------------------------++exprGetSize :: Q Exp -> (Int, Type) -> Q Exp+exprGetSize acc (i, _) = [|addSizeTo $(acc) $(varE (mkFieldName i))|]++getTagSize :: Int -> Int+getTagSize numConstructors+ | numConstructors == 1 = 0+ | fromIntegral (maxBound :: Word8) >= numConstructors = 1+ | fromIntegral (maxBound :: Word16) >= numConstructors = 2+ | fromIntegral (maxBound :: Word32) >= numConstructors = 4+ | fromIntegral (maxBound :: Word64) >= numConstructors = 8+ | otherwise = error "Too many constructors"++getTagType :: Int -> Name+getTagType numConstructors+ | numConstructors == 1 = error "No tag for 1 constructor"+ | fromIntegral (maxBound :: Word8) >= numConstructors = ''Word8+ | fromIntegral (maxBound :: Word16) >= numConstructors = ''Word16+ | fromIntegral (maxBound :: Word32) >= numConstructors = ''Word32+ | fromIntegral (maxBound :: Word64) >= numConstructors = ''Word64+ | otherwise = error "Too many constructors"++--------------------------------------------------------------------------------+-- Size+--------------------------------------------------------------------------------++getNameBaseLen :: Name -> Word8+getNameBaseLen cname =+ let x = length (nameBase cname)+ in if x > 63+ then error "Max Constructor Len: 63 characters"+ else fromIntegral x++conEncLen :: Name -> Word8+conEncLen cname = getNameBaseLen cname + 1++--------------------------------------------------------------------------------+-- Size+--------------------------------------------------------------------------------++mkSizeOfExpr :: Bool -> Bool -> TypeOfType -> Q Exp+mkSizeOfExpr True False tyOfTy =+ case tyOfTy of+ UnitType cname ->+ lamE+ [varP _acc, wildP]+ [|$(varE _acc) + $(litIntegral (conEncLen cname))|]+ TheType con ->+ lamE+ [varP _acc, varP _x]+ (caseE (varE _x) [matchCons (varE _acc) con])+ MultiType constructors -> sizeOfHeadDt constructors++ where++ sizeOfFields acc fields =+ foldl' exprGetSize acc $ zip [0..] fields++ matchCons acc (SimpleDataCon cname fields) =+ let a = litIntegral (conEncLen cname)+ b = sizeOfFields acc (map snd fields)+ expr = [|$(a) + $(b)|]+ in matchConstructor cname (length fields) expr++ sizeOfHeadDt cons =+ let acc = [|$(varE _acc)|]+ in lamE+ [varP _acc, varP _x]+ (caseE (varE _x) (fmap (matchCons acc) cons))++mkSizeOfExpr False False tyOfTy =+ case tyOfTy of+ UnitType _ -> lamE [varP _acc, wildP] [|$(varE _acc) + 1|]+ TheType con ->+ lamE+ [varP _acc, varP _x]+ (caseE (varE _x) [matchCons (varE _acc) con])+ MultiType constructors -> sizeOfHeadDt constructors++ where++ tagSizeExp numConstructors =+ litE (IntegerL (fromIntegral (getTagSize numConstructors)))++ -- XXX fields of the same type can be folded together, will reduce the code+ -- size when there are many fields of the same type.+ -- XXX const size fields can be calculated statically.+ -- XXX This can result in large compilation times due to nesting when there+ -- are many constructors. We can create a list and sum the list at run time+ -- to avoid that depending on the number of constructors. Or using a let+ -- statement for each case may help?+ -- appE (varE 'sum) (listE (acc : map (exprGetSize (litE (IntegerL 0))) (zip [0..] fields)))+ sizeOfFields acc fields =+ foldl' exprGetSize acc $ zip [0..] fields++ matchCons acc (SimpleDataCon cname fields) =+ let expr = sizeOfFields acc (map snd fields)+ in matchConstructor cname (length fields) expr++ -- XXX We fix VarSize for simplicity. Should be changed later.+ sizeOfHeadDt cons =+ let numCons = length cons+ acc = [|$(varE _acc) + $(tagSizeExp numCons)|]+ in lamE+ [varP _acc, varP _x]+ (caseE (varE _x) (fmap (matchCons acc) cons))++mkSizeOfExpr False True (TheType con) = RecHeader.mkRecSizeOfExpr con++mkSizeOfExpr _ _ _ = errorUnimplemented++mkSizeDec :: SerializeConfig -> Type -> [DataCon] -> Q [Dec]+mkSizeDec (SerializeConfig {..}) headTy cons = do+ -- INLINE on sizeOf actually worsens some benchmarks, and improves none+ sizeOfMethod <-+ mkSizeOfExpr+ cfgConstructorTagAsString+ cfgRecordSyntaxWithHeader+ (typeOfType headTy cons)+ pure+ ( foldMap+ (\x -> [PragmaD (InlineP 'addSizeTo x FunLike AllPhases)])+ cfgInlineSize+ ++ [FunD 'addSizeTo [Clause [] (NormalB sizeOfMethod) []]]+ )++--------------------------------------------------------------------------------+-- Peek+--------------------------------------------------------------------------------++mkDeserializeExpr :: Bool -> Bool -> Type -> TypeOfType -> Q Exp+mkDeserializeExpr True False headTy tyOfTy =+ case tyOfTy of+ UnitType cname -> deserializeConsExpr [SimpleDataCon cname []]+ TheType con -> deserializeConsExpr [con]+ MultiType cons -> deserializeConsExpr cons++ where++ deserializeConsExpr cons = do+ conLen <- newName "conLen"+ off1 <- newName "off1"+ [|do ($(varP off1), $(varP conLen) :: Word8) <-+ deserializeAt+ $(varE _initialOffset)+ $(varE _arr)+ $(varE _endOffset)+ $(multiIfE (map (guardCon conLen off1) cons ++ [catchAll]))|]++ catchAll =+ normalGE+ [|True|]+ [|error+ ("Found invalid tag while peeking (" +++ $(lift (pprint headTy)) ++ ")")|]++ guardCon conLen off con@(SimpleDataCon cname _) = do+ let lenCname = getNameBaseLen cname+ tag = map c2w (nameBase cname)+ normalGE+ [|($(litIntegral lenCname) == $(varE conLen))+ && $(xorCmp tag off _arr)|]+ [|let $(varP (makeI 0)) = $(varE off) + $(litIntegral lenCname)+ in $(mkDeserializeExprOne 'deserializeAt con)|]++mkDeserializeExpr False False headTy tyOfTy =+ case tyOfTy of+ -- Unit constructor+ -- XXX Should we peek and check if the byte value is 0?+ UnitType cname ->+ [|pure ($(varE _initialOffset) + 1, $(conE cname))|]+ -- Product type+ TheType con ->+ letE+ [valD (varP (mkName "i0")) (normalB (varE _initialOffset)) []]+ (mkDeserializeExprOne 'deserializeAt con)+ -- Sum type+ MultiType cons -> do+ let lenCons = length cons+ tagType = getTagType lenCons+ doE+ [ bindS+ (tupP [varP (mkName "i0"), varP _tag])+ [|deserializeAt $(varE _initialOffset) $(varE _arr) $(varE _endOffset)|]+ , noBindS+ (caseE+ (sigE (varE _tag) (conT tagType))+ (fmap peekMatch (zip [0 ..] cons) ++ [peekErr]))+ ]+ where+ peekMatch (i, con) =+ match+ (litP (IntegerL i))+ (normalB (mkDeserializeExprOne 'deserializeAt con)) []+ peekErr =+ match+ wildP+ (normalB+ -- XXX Print the tag+ [|error+ ("Found invalid tag while peeking (" +++ $(lift (pprint headTy)) ++ ")")|])+ []++mkDeserializeExpr False True _ (TheType con@(SimpleDataCon _ fields)) = do+ deserializeWithKeys <- newName "deserializeWithKeys"+ updateFunc <- newName "updateFunc"+ updateFuncDec <- RecHeader.conUpdateFuncDec updateFunc fields+ deserializeWithKeysDec <-+ RecHeader.mkDeserializeKeysDec deserializeWithKeys updateFunc con+ letE+ (pure <$> (deserializeWithKeysDec ++ updateFuncDec))+ (RecHeader.mkRecDeserializeExpr+ _initialOffset+ _endOffset+ deserializeWithKeys+ con)++mkDeserializeExpr _ _ _ _ = errorUnimplemented++mkDeserializeDec :: SerializeConfig -> Type -> [DataCon] -> Q [Dec]+mkDeserializeDec (SerializeConfig {..}) headTy cons = do+ peekMethod <-+ mkDeserializeExpr+ cfgConstructorTagAsString+ cfgRecordSyntaxWithHeader+ headTy+ (typeOfType headTy cons)+ pure+ ( foldMap+ (\x -> [PragmaD (InlineP 'deserializeAt x FunLike AllPhases)])+ cfgInlineDeserialize+ +++ [ FunD+ 'deserializeAt+ [ Clause+ (if isUnitType cons && not cfgConstructorTagAsString+ then [VarP _initialOffset, WildP, WildP]+ else [VarP _initialOffset, VarP _arr, VarP _endOffset])+ (NormalB peekMethod)+ []+ ]+ ]+ )++--------------------------------------------------------------------------------+-- Poke+--------------------------------------------------------------------------------++mkSerializeExprTag :: Name -> Int -> Q Exp+mkSerializeExprTag tagType tagVal =+ [|serializeAt+ $(varE _initialOffset)+ $(varE _arr)+ $(sigE (litE (IntegerL (fromIntegral tagVal))) (conT tagType))|]++mkSerializeExpr :: Bool -> Bool -> TypeOfType -> Q Exp+mkSerializeExpr True False tyOfTy =+ case tyOfTy of+ -- Unit type+ UnitType cname ->+ caseE+ (varE _val)+ [serializeDataCon (SimpleDataCon cname [])]+ -- Product type+ (TheType con) ->+ caseE+ (varE _val)+ [serializeDataCon con]+ -- Sum type+ (MultiType cons) ->+ caseE+ (varE _val)+ (map serializeDataCon cons)++ where++ serializeDataCon (SimpleDataCon cname fields) = do+ let tagLen8 = getNameBaseLen cname+ conEnc = tagLen8 : map c2w (nameBase cname)+ matchConstructor+ cname+ (length fields)+ (doE [ bindS+ (varP (mkName "i0"))+ (serializeW8List _initialOffset _arr conEnc)+ , noBindS (mkSerializeExprFields 'serializeAt fields)+ ])++mkSerializeExpr False False tyOfTy =+ case tyOfTy of+ -- Unit type+ UnitType _ ->+ [|serializeAt $(varE _initialOffset) $(varE _arr) (0 :: Word8)|]+ -- Product type+ (TheType (SimpleDataCon cname fields)) ->+ letE+ [valD (varP (mkName "i0")) (normalB (varE _initialOffset)) []]+ (caseE+ (varE _val)+ [ matchConstructor+ cname+ (length fields)+ (mkSerializeExprFields 'serializeAt fields)+ ])+ -- Sum type+ (MultiType cons) -> do+ let lenCons = length cons+ tagType = getTagType lenCons+ caseE+ (varE _val)+ (fmap (\(tagVal, SimpleDataCon cname fields) ->+ matchConstructor+ cname+ (length fields)+ (doE [ bindS+ (varP (mkName "i0"))+ (mkSerializeExprTag tagType tagVal)+ , noBindS+ (mkSerializeExprFields+ 'serializeAt+ fields)+ ]))+ (zip [0 ..] cons))++mkSerializeExpr False True (TheType con) =+ RecHeader.mkRecSerializeExpr _initialOffset con++mkSerializeExpr _ _ _ = errorUnimplemented++mkSerializeDec :: SerializeConfig -> Type -> [DataCon] -> Q [Dec]+mkSerializeDec (SerializeConfig {..}) headTy cons = do+ pokeMethod <-+ mkSerializeExpr+ cfgConstructorTagAsString+ cfgRecordSyntaxWithHeader+ (typeOfType headTy cons)+ pure+ ( foldMap+ (\x -> [PragmaD (InlineP 'serializeAt x FunLike AllPhases)])+ cfgInlineSerialize+ +++ [FunD+ 'serializeAt+ [ Clause+ (if isUnitType cons && not cfgConstructorTagAsString+ then [VarP _initialOffset, VarP _arr, WildP]+ else [VarP _initialOffset, VarP _arr, VarP _val])+ (NormalB pokeMethod)+ []+ ]+ ]+ )++--------------------------------------------------------------------------------+-- Main+--------------------------------------------------------------------------------++-- | A general function to derive Serialize instances where you can control+-- which Constructors of the datatype to consider and what the Context for the+-- 'Serialize' instance would be.+--+-- Consider the datatype:+-- @+-- data CustomDataType a b+-- = CDTConstructor1+-- | CDTConstructor2 Bool+-- | CDTConstructor3 Bool b+-- deriving (Show, Eq)+-- @+--+-- Usage:+-- @+-- $(deriveSerializeInternal+-- serializeConfig+-- [AppT (ConT ''Serialize) (VarT (mkName "b"))]+-- (AppT+-- (AppT (ConT ''CustomDataType) (VarT (mkName "a")))+-- (VarT (mkName "b")))+-- [ DataCon 'CDTConstructor1 [] [] []+-- , DataCon 'CDTConstructor2 [] [] [(Nothing, (ConT ''Bool))]+-- , DataCon+-- 'CDTConstructor3+-- []+-- []+-- [(Nothing, (ConT ''Bool)), (Nothing, (VarT (mkName "b")))]+-- ])+-- @+deriveSerializeInternal ::+ SerializeConfig -> Type -> [DataCon] -> ([Dec] -> Q [Dec]) -> Q [Dec]+deriveSerializeInternal conf headTy cons next = do+ sizeDec <- mkSizeDec conf headTy cons+ peekDec <- mkDeserializeDec conf headTy cons+ pokeDec <- mkSerializeDec conf headTy cons+ let methods = concat [sizeDec, peekDec, pokeDec]+ next methods++-- | @deriveSerializeWith config-modifier instance-dec@ generates a template+-- Haskell splice consisting of a declaration of a 'Serialize' instance.+-- @instance-dec@ is a template Haskell declaration splice consisting of a+-- standard Haskell instance declaration without the type class methods (e.g.+-- @[d|instance Serialize a => Serialize (Maybe a)|]@).+--+-- The type class methods for the given instance are generated according to the+-- supplied @config-modifier@ parameter. See 'SerializeConfig' for default+-- configuration settings.+--+-- Usage:+--+-- @+-- \$(deriveSerializeWith+-- ( inlineSerializeAt (Just NoInline)+-- . inlineDeserializeAt (Just NoInline)+-- )+-- [d|instance Serialize a => Serialize (Maybe a)|])+-- @+deriveSerializeWith ::+ (SerializeConfig -> SerializeConfig) -> Q [Dec] -> Q [Dec]+deriveSerializeWith modifier mDecs = do+ dec <- mDecs+ case dec of+ [InstanceD mo preds headTyWC []] -> do+ let headTy = unwrap dec headTyWC+ dt <- reifyDataType (getMainTypeName dec headTy)+ let cons = dtCons dt+ deriveSerializeInternal+ (modifier serializeConfig) headTy cons (next mo preds headTyWC)+ _ -> errorMessage dec++ where++ next mo preds headTyWC methods = pure [InstanceD mo preds headTyWC methods]++ errorMessage dec =+ error $ unlines+ [ "Error: deriveSerializeWith:"+ , ""+ , ">> " ++ pprint dec+ , ""+ , "The supplied declaration is not a valid instance declaration."+ , "Provide a valid Haskell instance declaration without a body."+ , ""+ , "Examples:"+ , "instance Serialize (Proxy a)"+ , "instance Serialize a => Serialize (Identity a)"+ , "instance Serialize (TableT Identity)"+ ]++ unwrap _ (AppT (ConT _) r) = r+ unwrap dec _ = errorMessage dec++ getMainTypeName dec = go++ where++ go (ConT nm) = nm+ go (AppT l _) = go l+ go _ = errorMessage dec++-- | Given an 'Serialize' instance declaration splice without the methods (e.g.+-- @[d|instance Serialize a => Serialize (Maybe a)|]@), generate an instance+-- declaration including all the type class method implementations.+--+-- >>> deriveSerialize = deriveSerializeWith id+--+-- Usage:+--+-- @+-- \$(deriveSerialize+-- [d|instance Serialize a => Serialize (Maybe a)|])+-- @+deriveSerialize :: Q [Dec] -> Q [Dec]+deriveSerialize = deriveSerializeWith id
+ src/Streamly/Internal/Data/Serialize/TH/Bottom.hs view
@@ -0,0 +1,478 @@+{-# LANGUAGE TemplateHaskell #-}++-- |+-- Module : Streamly.Internal.Data.Serialize.TH.Bottom+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+module Streamly.Internal.Data.Serialize.TH.Bottom+ (+ -- ** Config+ SerializeConfig(..)+ , serializeConfig+ , inlineAddSizeTo+ , inlineSerializeAt+ , inlineDeserializeAt+ , encodeConstrNames+ , encodeRecordFields++ -- ** Other Utilities+ , TypeOfType(..)+ , typeOfType+ , SimpleDataCon(..)+ , simplifyDataCon+ , Field+ , mkFieldName+ , isUnitType+ , isRecordSyntax+ , c2w+ , wListToString+ , xorCmp+ , serializeW8List+ , litIntegral+ , litProxy+ , matchConstructor+ , openConstructor+ , makeI+ , makeN+ , makeA+ , int_w8+ , int_w32+ , w32_int+ , w8_int+ , _acc+ , _arr+ , _endOffset+ , _initialOffset+ , _x+ , _tag+ , _val+ , errorUnsupported+ , errorUnimplemented+ ) where++import Data.Maybe (isJust)+import Data.Char (chr, ord)+import Data.Foldable (length)+import Data.List (foldl')+import Data.Word (Word16, Word32, Word64, Word8)+import Data.Bits (Bits, (.|.), shiftL, zeroBits, xor)+import Streamly.Internal.System.IO (unsafeInlineIO)+import Streamly.Internal.Data.Unbox (Unbox)+import Data.Proxy (Proxy)++import Language.Haskell.TH+import Streamly.Internal.Data.Serialize.Type++import qualified Streamly.Internal.Data.Unbox as Unbox++import Streamly.Internal.Data.Unbox.TH (DataCon(..))+import Prelude hiding (Foldable(..))++--------------------------------------------------------------------------------+-- Config+--------------------------------------------------------------------------------++-- NOTE: 'Nothing' is not eqvivalant to 'Just Inlinable'. Ie. Having no inline+-- specific pragma and having an Inlinable pragma are different. Having an+-- Inlinable pragma makes GHC put the code in the interface file whereas having+-- no inline specific pragma let's GHC decide whether to put the code in+-- interface file or not.++-- | Configuration to control how the 'Serialize' instance is generated. The+-- configuration is opaque and is modified by composing config modifier+-- functions, for example:+--+-- >>> import Language.Haskell.TH (Inline(..))+-- >>> configOpts = (inlineDeserializeAt (Just NoInline)) . (inlineSerializeAt (Just Inlinable))+--+-- The default configuration settings are:+--+-- * 'inlineAddSizeTo' Nothing+-- * 'inlineSerializeAt' (Just Inline)+-- * 'inlineDeserializeAt' (Just Inline)+--+-- The following experimental options are also available:+--+-- * 'encodeConstrNames' False+-- * 'encodeRecordFields' False+--+data SerializeConfig =+ SerializeConfig+ { cfgInlineSize :: Maybe Inline+ , cfgInlineSerialize :: Maybe Inline+ , cfgInlineDeserialize :: Maybe Inline+ , cfgConstructorTagAsString :: Bool+ , cfgRecordSyntaxWithHeader :: Bool+ }++-- | How should we inline the 'addSizeTo' function? The default is 'Nothing'+-- which means left to the compiler. Forcing inline on @addSizeTo@ function+-- actually worsens some benchmarks and improves none.+inlineAddSizeTo :: Maybe Inline -> SerializeConfig -> SerializeConfig+inlineAddSizeTo v cfg = cfg {cfgInlineSize = v}++-- XXX Should we make the default Inlinable instead?++-- | How should we inline the 'serialize' function? The default 'Just Inline'.+-- However, aggressive inlining can bloat the code and increase in compilation+-- times when there are big functions and too many nesting levels so you can+-- change it accordingly. A 'Nothing' value leaves the decision to the+-- compiler.+inlineSerializeAt :: Maybe Inline -> SerializeConfig -> SerializeConfig+inlineSerializeAt v cfg = cfg {cfgInlineSerialize = v}++-- XXX Should we make the default Inlinable instead?++-- | How should we inline the 'deserialize' function? See guidelines in+-- 'inlineSerializeAt'.+inlineDeserializeAt :: Maybe Inline -> SerializeConfig -> SerializeConfig+inlineDeserializeAt v cfg = cfg {cfgInlineDeserialize = v}++-- | __Experimental__+--+-- In sum types, use Latin-1 encoded original constructor names rather than+-- binary values to identify constructors. This option is not applicable to+-- product types.+--+-- This option enables the following behavior:+--+-- * __Reordering__: Order of the fields can be changed without affecting+-- serialization.+-- * __Addition__: If a field is added in the new version, the old version of+-- the data type can still be deserialized by the new version. The new value+-- would never occur in the old one.+-- * __Deletion__: If a field is deleted in the new version, deserialization+-- of the old version will result in an error. TBD: We can possibly designate a+-- catch-all case to handle this scenario.+--+-- Note that if you change a type, change the semantics of a type, or delete a+-- field and add a new field with the same name, deserialization of old data+-- may result in silent unexpected behavior.+--+-- This option has to be the same on both encoding and decoding side.+--+-- The default is 'False'.+--+encodeConstrNames :: Bool -> SerializeConfig -> SerializeConfig+encodeConstrNames v cfg = cfg {cfgConstructorTagAsString = v}++-- XXX We can deserialize each field to Either, so if there is a+-- deserialization error in any field it can handled independently. Also, a+-- unique type/version identifier of the field (based on the versions of the+-- packages, full module name space + type identifier) can be serialized along+-- with the value for stricter compatibility, semantics checking. Or we can+-- store a type hash.++-- | __Experimental__+--+-- In explicit record types, use Latin-1 encoded record field names rather than+-- binary values to identify the record fields. Note that this option is not+-- applicable to sum types. Also, it does not work on a product type which is+-- not a record, because there are no field names to begin with.+--+-- This option enables the following behavior:+--+-- * __Reordering__: Order of the fields can be changed without affecting+-- serialization.+-- * __Addition__: If a 'Maybe' type field is added in the new version, the old+-- version of the data type can still be deserialized by the new version, the+-- field value in the older version is assumed to be 'Nothing'. If any other+-- type of field is added, deserialization of the older version results in an+-- error but only when that field is actually accessed in the deserialized+-- record.+-- * __Deletion__: If a field is deleted in the new version and it is+-- encountered in a previously serialized version then the field is discarded.+--+-- This option has to be the same on both encoding and decoding side.+--+-- There is a constant performance overhead proportional to the total length of+-- the record field names and the number of record fields.+--+-- The default is 'False'.+--+encodeRecordFields :: Bool -> SerializeConfig -> SerializeConfig+encodeRecordFields v cfg = cfg {cfgRecordSyntaxWithHeader = v}++serializeConfig :: SerializeConfig+serializeConfig =+ SerializeConfig+ { cfgInlineSize = Nothing+ , cfgInlineSerialize = Just Inline+ , cfgInlineDeserialize = Just Inline+ , cfgConstructorTagAsString = False+ , cfgRecordSyntaxWithHeader = False+ }++--------------------------------------------------------------------------------+-- Helpers+--------------------------------------------------------------------------------++type Field = (Maybe Name, Type)++_x :: Name+_x = mkName "x"++_acc :: Name+_acc = mkName "acc"++_arr :: Name+_arr = mkName "arr"++_tag :: Name+_tag = mkName "tag"++_initialOffset :: Name+_initialOffset = mkName "initialOffset"++_endOffset :: Name+_endOffset = mkName "endOffset"++_val :: Name+_val = mkName "val"++mkFieldName :: Int -> Name+mkFieldName i = mkName ("field" ++ show i)++makeI :: Int -> Name+makeI i = mkName $ "i" ++ show i++makeN :: Int -> Name+makeN i = mkName $ "n" ++ show i++makeA :: Int -> Name+makeA i = mkName $ "a" ++ show i++--------------------------------------------------------------------------------+-- Domain specific helpers+--------------------------------------------------------------------------------++openConstructor :: Name -> Int -> Q Pat+openConstructor cname numFields =+ conP cname (map (varP. mkFieldName) [0 .. (numFields - 1)])++matchConstructor :: Name -> Int -> Q Exp -> Q Match+matchConstructor cname numFields exp0 =+ match (openConstructor cname numFields) (normalB exp0) []++--------------------------------------------------------------------------------+-- Constructor types+--------------------------------------------------------------------------------++data SimpleDataCon =+ SimpleDataCon Name [Field]+ deriving (Eq)++simplifyDataCon :: DataCon -> SimpleDataCon+simplifyDataCon (DataCon cname _ _ fields) = SimpleDataCon cname fields++data TypeOfType+ = UnitType Name -- 1 constructor and 1 field+ | TheType SimpleDataCon -- 1 constructor and 1+ fields+ | MultiType [SimpleDataCon] -- 1+ constructors+ deriving (Eq)++typeOfType :: Type -> [DataCon] -> TypeOfType+typeOfType headTy [] =+ error+ ("Attempting to get size with no constructors (" +++ pprint headTy ++ ")")+typeOfType _ [DataCon cname _ _ []] = UnitType cname+typeOfType _ [con@DataCon{}] = TheType $ simplifyDataCon con+typeOfType _ cons = MultiType $ map simplifyDataCon cons++isUnitType :: [DataCon] -> Bool+isUnitType [DataCon _ _ _ []] = True+isUnitType _ = False++isRecordSyntax :: SimpleDataCon -> Bool+isRecordSyntax (SimpleDataCon _ fields) = all (isJust . fst) fields++--------------------------------------------------------------------------------+-- Type casting+--------------------------------------------------------------------------------++int_w8 :: Int -> Word8+int_w8 = fromIntegral++int_w32 :: Int -> Word32+int_w32 = fromIntegral++w8_w16 :: Word8 -> Word16+w8_w16 = fromIntegral++w8_w32 :: Word8 -> Word32+w8_w32 = fromIntegral++w8_w64 :: Word8 -> Word64+w8_w64 = fromIntegral++w8_int :: Word8 -> Int+w8_int = fromIntegral++w32_int :: Word32 -> Int+w32_int = fromIntegral++c2w :: Char -> Word8+c2w = fromIntegral . ord++wListToString :: [Word8] -> String+wListToString = fmap (chr . fromIntegral)++--------------------------------------------------------------------------------+-- Bit manipulation+--------------------------------------------------------------------------------++shiftAdd :: Bits a => (b -> a) -> [b] -> a+shiftAdd conv xs =+ foldl' (.|.) zeroBits $+ fmap (\(j, x) -> shiftL x (j * 8)) $ zip [0 ..] $ map conv xs++-- Note: This only works in little endian machines+-- TODO:+-- Instead of generating this via TH can't we write it directly in Haskell and+-- use that? Creating one comparison function for each deserialization may be+-- too much code and may not be necessary.+-- Benchmark both the implementations and check.+xorCmp :: [Word8] -> Name -> Name -> Q Exp+xorCmp tag off arr =+ case tagLen of+ x | x < 2 -> [|$(go8 0) == zeroBits|]+ x | x < 4 -> [|$(go16 0) == zeroBits|]+ x | x < 8 -> [|$(go32 0) == zeroBits|]+ _ -> [|$(go64 0) == zeroBits|]+ where+ tagLen = length tag+ go8 i | i >= tagLen = [|zeroBits|]+ go8 i = do+ let wIntegral = litIntegral i+ [|xor (unsafeInlineIO+ (Unbox.peekAt+ ($(varE off) + $(litIntegral i))+ $(varE arr)))+ ($(wIntegral) :: Word8) .|.+ $(go8 (i + 1))|]+ go16 i+ | i >= tagLen = [|zeroBits|]+ go16 i+ | tagLen - i < 2 = go16 (tagLen - 2)+ go16 i = do+ let wIntegral =+ litIntegral+ (shiftAdd w8_w16 [tag !! i, tag !! (i + 1)] :: Word16)+ [|xor (unsafeInlineIO+ (Unbox.peekAt+ ($(varE off) + $(litIntegral i))+ $(varE arr)))+ ($(wIntegral) :: Word16) .|.+ $(go16 (i + 2))|]+ go32 i+ | i >= tagLen = [|zeroBits|]+ go32 i+ | tagLen - i < 4 = go32 (tagLen - 4)+ go32 i = do+ let wIntegral =+ litIntegral+ (shiftAdd+ w8_w32+ [ tag !! i+ , tag !! (i + 1)+ , tag !! (i + 2)+ , tag !! (i + 3)+ ] :: Word32)+ [|xor (unsafeInlineIO+ (Unbox.peekAt+ ($(varE off) + $(litIntegral i))+ $(varE arr)))+ ($(wIntegral) :: Word32) .|.+ $(go32 (i + 4))|]+ go64 i+ | i >= tagLen = [|zeroBits|]+ go64 i+ | tagLen - i < 8 = go64 (tagLen - 8)+ go64 i = do+ let wIntegral =+ litIntegral+ (shiftAdd+ w8_w64+ [ tag !! i+ , tag !! (i + 1)+ , tag !! (i + 2)+ , tag !! (i + 3)+ , tag !! (i + 4)+ , tag !! (i + 5)+ , tag !! (i + 6)+ , tag !! (i + 7)+ ])+ [|xor (unsafeInlineIO+ (Unbox.peekAt+ ($(varE off) + $(litIntegral i))+ $(varE arr)))+ ($(wIntegral) :: Word64) .|.+ $(go64 (i + 8))|]++--------------------------------------------------------------------------------+-- Primitive serialization+--------------------------------------------------------------------------------++-- TODO:+-- Will this be too much of a code bloat?+-- Loop with the loop body unrolled?+-- Serialize this in batches similar to batch comparision in xorCmp?+serializeW8List :: Name -> Name -> [Word8] -> Q Exp+serializeW8List off arr w8List = do+ [|let $(varP (makeN 0)) = $(varE off)+ in $(doE (fmap makeBind [0 .. (lenW8List - 1)] +++ [noBindS [|pure $(varE (makeN lenW8List))|]]))|]++ where++ lenW8List = length w8List+ makeBind i =+ bindS+ (varP (makeN (i + 1)))+ [|$(varE 'serializeAt)+ $(varE (makeN i))+ $(varE arr)+ ($(litIntegral (w8List !! i)) :: Word8)|]++--------------------------------------------------------------------------------+-- TH Helpers+--------------------------------------------------------------------------------++litIntegral :: Integral a => a -> Q Exp+litIntegral = litE . IntegerL . fromIntegral++litProxy :: Unbox a => Proxy a -> Q Exp+litProxy = litE . IntegerL . fromIntegral . Unbox.sizeOf++--------------------------------------------------------------------------------+-- Error codes+--------------------------------------------------------------------------------++errorUnsupported :: String -> a+errorUnsupported err =+ error+ $ unlines+ [ "Unsupported"+ , "==========="+ , "This is improper use of the library."+ , "This case is unsupported."+ , "Please contact the developer if this case is of interest."+ , ""+ , "Message"+ , "-------"+ , err+ ]++errorUnimplemented :: a+errorUnimplemented =+ error+ $ unlines+ [ "Unimplemented"+ , "============="+ , "Please contact the developer if this case is of interest."+ ]
+ src/Streamly/Internal/Data/Serialize/TH/Common.hs view
@@ -0,0 +1,70 @@+{-# LANGUAGE TemplateHaskell #-}++-- |+-- Module : Streamly.Internal.Data.Serialize.TH.Common+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+module Streamly.Internal.Data.Serialize.TH.Common+ ( mkDeserializeExprOne+ , mkSerializeExprFields+ ) where++--------------------------------------------------------------------------------+-- Imports+--------------------------------------------------------------------------------++import Language.Haskell.TH+import Streamly.Internal.Data.Serialize.TH.Bottom++--------------------------------------------------------------------------------+-- Code+--------------------------------------------------------------------------------++mkDeserializeExprOne :: Name -> SimpleDataCon -> Q Exp+mkDeserializeExprOne peeker (SimpleDataCon cname fields) =+ case fields of+ -- Only tag is serialized for unit fields, no actual value+ [] -> [|pure ($(varE (mkName "i0")), $(conE cname))|]+ _ ->+ doE+ (concat+ [ fmap makeBind [0 .. (numFields - 1)]+ , [ noBindS+ (appE+ (varE 'pure)+ (tupE+ [ varE (makeI numFields)+ , appsE+ (conE cname :+ map (varE . makeA)+ [0 .. (numFields - 1)])+ ]))+ ]+ ])+ where+ numFields = length fields+ makeBind i =+ bindS+ (tupP [varP (makeI (i + 1)), varP (makeA i)])+ [|$(varE peeker) $(varE (makeI i)) $(varE _arr) $(varE _endOffset)|]++mkSerializeExprFields :: Name -> [Field] -> Q Exp+mkSerializeExprFields poker fields =+ case fields of+ -- Unit constructor, do nothing just tag is enough+ [] -> [|pure ($(varE (mkName "i0")))|]+ _ ->+ doE+ (fmap makeBind [0 .. (numFields - 1)] +++ [noBindS [|pure $(varE (makeI numFields))|]])+ where+ numFields = length fields+ makeBind i =+ bindS+ (varP (makeI (i + 1)))+ [|$(varE poker)+ $(varE (makeI i)) $(varE _arr) $(varE (mkFieldName i))|]
+ src/Streamly/Internal/Data/Serialize/TH/RecHeader.hs view
@@ -0,0 +1,413 @@+{-# LANGUAGE TemplateHaskell #-}++-- |+-- Module : Streamly.Internal.Data.Serialize.TH.RecHeader+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+module Streamly.Internal.Data.Serialize.TH.RecHeader+ ( mkRecSerializeExpr+ , mkRecDeserializeExpr+ , mkRecSizeOfExpr+ , conUpdateFuncDec+ , mkDeserializeKeysDec+ ) where++--------------------------------------------------------------------------------+-- Imports+--------------------------------------------------------------------------------++import Control.Monad (void)+import Data.Foldable (length, sum)+import Data.List (foldl')+import Data.Word (Word32, Word8)+import Data.Maybe (fromJust)+import Language.Haskell.TH+import Streamly.Internal.Data.Serialize.Type (Serialize(..))+import Data.Foldable (foldlM)+import Streamly.Internal.Data.MutByteArray.Type (MutByteArray)+import Data.Proxy (Proxy(..))++import qualified Streamly.Internal.Data.Unbox as Unbox++import Streamly.Internal.Data.Serialize.TH.Bottom+import Streamly.Internal.Data.Serialize.TH.Common+import Prelude hiding (Foldable(..))++--------------------------------------------------------------------------------+-- Notes+--------------------------------------------------------------------------------++-- Compatibility Algorithm+-- =======================+--+-- The algorithm is written without any low level implementation details. See+-- the code for any low level implementation details.+--+-- Serialization:+-- --------------+--+-- To serialize the data,+--+-- * Get the list of keys for the record as @keyList@.+-- * Serialize the @keyList@.+-- * Serialize the @fields@ one-by-one after serializing the @keyList@.+--+-- Deserialization:+-- ----------------+--+-- To deserialize the data to type @T@,+--+-- __Checking for type match__:+--+-- * Get the list of keys for type @T@ as @targetKeyList@.+-- * Get the list of keys encoded as @encodedKeyList@.+-- * If @targetKeyList == encodedKeyList@ see the __Type Match__ section else+-- see the __No Type Match__ section.+--+-- __Type Match__:+--+-- * Decode the fields one-by-one and construct the type @T@ in the end.+--+-- __No Type Match__:+--+-- * Decode the list of keys encoded into @encodedKeyList@.+-- * Get the list of keys for type @T@ as @targetKeyList@.+-- * Loop through @encodedKeyList@ and start deserializing the encoded data.+-- * If the key is present in @encodedKeyList@ and not in @targetKeyList@+-- then skip parsing the corresponding value.+-- * If the key is present in @targetKeyList@ and not in @encodedKeyList@+-- then set the value for that key as @Nothing@.+-- * If the key is present in both @encodedKeyList@ and in @targetKeyList@+-- parse the value.+-- * Construct @T@ after parsing all the data.++-- Developer Notes+-- ===============+--+-- * Record update syntax is not robust across language extensions and common+-- record plugins (like record-dot-processor, large-records, etc.).++--------------------------------------------------------------------------------+-- Compact lists+--------------------------------------------------------------------------------++-- Like haskell list but the maximum length of the list is 255+newtype CompactList a =+ CompactList+ { unCompactList :: [a]+ }++-- We use 'Word8' to encode the length, hence the maximim number of elements in+-- the list is 255.+instance forall a. Serialize a => Serialize (CompactList a) where++ -- {-# INLINE addSizeTo #-}+ addSizeTo acc (CompactList xs) =+ foldl' addSizeTo (acc + Unbox.sizeOf (Proxy :: Proxy Word8)) xs++ -- Inlining this causes large compilation times for tests+ {-# INLINABLE deserializeAt #-}+ deserializeAt off arr sz = do+ (off1, len8) <- deserializeAt off arr sz :: IO (Int, Word8)+ let len = w8_int len8+ peekList f o i | i >= 3 = do+ -- Unfold the loop three times+ (o1, x1) <- deserializeAt o arr sz+ (o2, x2) <- deserializeAt o1 arr sz+ (o3, x3) <- deserializeAt o2 arr sz+ peekList (f . (\xs -> x1:x2:x3:xs)) o3 (i - 3)+ peekList f o 0 = pure (o, f [])+ peekList f o i = do+ (o1, x) <- deserializeAt o arr sz+ peekList (f . (x:)) o1 (i - 1)+ (nextOff, lst) <- peekList id off1 len+ pure (nextOff, CompactList lst)++ -- Inlining this causes large compilation times for tests+ {-# INLINABLE serializeAt #-}+ serializeAt off arr (CompactList val) = do+ void $ serializeAt off arr (int_w8 (length val) :: Word8)+ let off1 = off + Unbox.sizeOf (Proxy :: Proxy Word8)+ let pokeList o [] = pure o+ pokeList o (x:xs) = do+ o1 <- serializeAt o arr x+ pokeList o1 xs+ pokeList off1 val++--------------------------------------------------------------------------------+-- Helpers+--------------------------------------------------------------------------------++fieldToNameBase :: Field -> String+fieldToNameBase = nameBase . fromJust . fst++isMaybeType :: Type -> Bool+isMaybeType (AppT (ConT m) _) = m == ''Maybe+isMaybeType _ = False++--------------------------------------------------------------------------------+-- Size+--------------------------------------------------------------------------------++-- We add 4 here because we use 'serializeWithSize' for serializing.+exprGetSize :: Q Exp -> (Int, Type) -> Q Exp+exprGetSize acc (i, _) =+ [|addSizeTo $(acc) $(varE (mkFieldName i)) + 4|]++sizeOfHeader :: SimpleDataCon -> Int+sizeOfHeader (SimpleDataCon _ fields) =+ sizeForFinalOff + sizeForHeaderLength + sizeForNumFields+ + sum (map ((+ sizeForFieldLen) . length . fieldToNameBase) fields)++ where++ sizeForFinalOff = 4+ sizeForHeaderLength = 4 -- Max header length is (255 * (255 + 1) + 1) and+ -- hence 2 bytes is enough to store it. But we still+ -- use 4 bytes as using 2 bytes introduces+ -- regression.+ sizeForNumFields = 1 -- At max 255 fields in the record constructor+ sizeForFieldLen = 1 -- At max 255 letters in the key++mkRecSizeOfExpr :: SimpleDataCon -> Q Exp+mkRecSizeOfExpr con = do+ n_acc <- newName "acc"+ n_x <- newName "x"+ lamE+ [varP n_acc, varP n_x]+ [|$(litIntegral hlen) ++ $(caseE (varE n_x) [matchCons (varE n_acc) con])|]++ where++ hlen = sizeOfHeader con+ sizeOfFields acc fields = foldl' exprGetSize acc $ zip [0 ..] fields+ matchCons acc (SimpleDataCon cname fields) =+ let expr = sizeOfFields acc (map snd fields)+ in matchConstructor cname (length fields) expr++--------------------------------------------------------------------------------+-- Header+--------------------------------------------------------------------------------++headerValue :: SimpleDataCon -> [Word8]+headerValue (SimpleDataCon _ fields) =+ int_w8 numFields : concatMap lengthPrependedFieldEncoding fields++ where++ -- Error out if the number of fields or the length of key is >= 256. We use+ -- Word8 for encoding the info and hence the max value is 255.+ numFields =+ let lenFields = length fields+ in if lenFields <= 255+ then lenFields+ else errorUnsupported+ "Number of fields in the record should be <= 255."+ lengthPrependedFieldEncoding field =+ let fEnc =+ let fEnc_ = map c2w (fieldToNameBase field)+ lenFEnc = length fEnc_+ in if lenFEnc <= 255+ then fEnc_+ else+ errorUnsupported+ "Length of any key should be <= 255."+ in int_w8 (length fEnc) : fEnc++--------------------------------------------------------------------------------+-- Peek+--------------------------------------------------------------------------------++-- Encoding the size is required if we want to skip the field without knowing+-- its type. We encode the size as 'Word32' hence there is a 4 bytes increase+-- in size.+{-# INLINE serializeWithSize #-}+serializeWithSize :: Serialize a => Int -> MutByteArray -> a -> IO Int+serializeWithSize off arr val = do+ off1 <- serializeAt (off + 4) arr val+ Unbox.pokeAt off arr (int_w32 (off1 - off - 4) :: Word32)+ pure off1++mkRecSerializeExpr :: Name -> SimpleDataCon -> Q Exp+mkRecSerializeExpr initialOffset con@(SimpleDataCon cname fields) = do+ afterHLen <- newName "afterHLen"+ -- Encoding the header length is required.+ -- We first compare the header length encoded and the current header+ -- length. Only if the header lengths match, we compare the headers.+ [|do $(varP afterHLen) <-+ serializeAt+ ($(varE initialOffset) + 4)+ $(varE _arr)+ ($(litIntegral hlen) :: Word32)+ $(varP (makeI 0)) <- $(serializeW8List afterHLen _arr hval)+ let $(openConstructor cname (length fields)) = $(varE _val)+ finalOff <- $(mkSerializeExprFields 'serializeWithSize fields)+ Unbox.pokeAt+ $(varE initialOffset)+ $(varE _arr)+ ((fromIntegral :: Int -> Word32)+ (finalOff - $(varE initialOffset)))+ pure finalOff|]++ where++ hval = headerValue con+ hlen = length hval++--------------------------------------------------------------------------------+-- Poke+--------------------------------------------------------------------------------++{-# INLINE deserializeWithSize #-}+deserializeWithSize ::+ Serialize a => Int -> MutByteArray -> Int -> IO (Int, a)+deserializeWithSize off = deserializeAt (off + 4)++conUpdateFuncDec :: Name -> [Field] -> Q [Dec]+conUpdateFuncDec funcName fields = do+ prevAcc <- newName "prevAcc"+ curOff <- newName "curOff"+ endOff <- newName "endOff"+ arr <- newName "arr"+ key <- newName "key"+ method <-+ caseE+ (varE key)+ (concat+ [ map (matchField arr endOff (prevAcc, curOff)) fnames+ , [ match+ wildP+ (normalB+ [|do (valOff, valLen :: Word32) <-+ deserializeAt+ $(varE curOff)+ $(varE arr)+ $(varE endOff)+ pure+ ( $(varE prevAcc)+ , valOff + w32_int valLen)|])+ []+ ]+ ])+ pure+ [ PragmaD (InlineP funcName NoInline FunLike AllPhases)+ , FunD+ funcName+ [ Clause+ [ VarP arr+ , VarP endOff+ , TupP [VarP prevAcc, VarP curOff]+ , VarP key+ ]+ (NormalB method)+ []+ ]+ ]++ where++ fnames = fmap (fromJust . fst) fields+ matchField :: Name -> Name -> (Name, Name) -> Name -> Q Match+ matchField arr endOff (acc, currOff) fname = do+ let fnameLit = StringL (nameBase fname)+ match+ (litP fnameLit)+ (normalB+ [|do (valOff, valLen :: Word32) <-+ deserializeAt+ $(varE currOff)+ $(varE arr)+ $(varE endOff)+ pure+ ( ($(litE fnameLit), $(varE currOff)) : $(varE acc)+ , valOff + w32_int valLen)|])+ []++mkDeserializeKeysDec :: Name -> Name -> SimpleDataCon -> Q [Dec]+mkDeserializeKeysDec funcName updateFunc (SimpleDataCon cname fields) = do+ hOff <- newName "hOff"+ finalOff <- newName "finalOff"+ arr <- newName "arr"+ endOff <- newName "endOff"+ kvEncoded <- newName "kvEncoded"+ finalRec <- newName "finalRec"+ let deserializeFieldExpr (Just name, ty) = do+ let nameLit = litE (StringL (nameBase name))+ [|case lookup $(nameLit) $(varE kvEncoded) of+ Nothing -> $(emptyTy name ty)+ Just off -> do+ val <- deserializeWithSize off $(varE arr) $(varE endOff)+ pure $ snd val|]+ deserializeFieldExpr _ =+ errorUnsupported "The datatype should use record syntax."+ method <-+ [|do (dataOff, hlist :: CompactList (CompactList Word8)) <-+ deserializeAt $(varE hOff) $(varE arr) $(varE endOff)+ let keys = wListToString . unCompactList <$> unCompactList hlist+ ($(varP kvEncoded), _) <-+ foldlM+ ($(varE updateFunc) $(varE arr) $(varE endOff))+ ([], dataOff)+ keys+ $(varP finalRec) <-+ $(foldl'+ (\acc i ->+ [|$(acc) <*>+ $(deserializeFieldExpr i)|])+ [|pure $(conE cname)|]+ fields)+ pure ($(varE finalOff), $(varE finalRec))|]+ pure+ [ PragmaD (InlineP funcName NoInline FunLike AllPhases)+ , FunD+ funcName+ [ Clause+ [ VarP hOff+ , VarP finalOff+ , VarP arr+ , VarP endOff+ ]+ (NormalB method)+ []+ ]+ ]++ where++ emptyTy k ty =+ if isMaybeType ty+ then [|pure Nothing|]+ else [|error $(litE (StringL (nameBase k ++ " is not found.")))|]+++mkRecDeserializeExpr :: Name -> Name -> Name -> SimpleDataCon -> Q Exp+mkRecDeserializeExpr initialOff endOff deserializeWithKeys con = do+ hOff <- newName "hOff"+ let sizeForFinalOff = 4 -- Word32+ sizeForHeaderLength = 4 -- Word32+ sizePreData = sizeForFinalOff + sizeForHeaderLength + hlen+ [|do (hlenOff, encLen :: Word32) <-+ deserializeAt $(varE initialOff) $(varE _arr) $(varE endOff)+ ($(varP hOff), hlen1 :: Word32) <-+ deserializeAt hlenOff $(varE _arr) $(varE endOff)+ if (hlen1 == $(litIntegral hlen)) && $(xorCmp hval hOff _arr)+ then do+ let $(varP (makeI 0)) =+ $(varE initialOff) ++ $(litIntegral sizePreData)+ $(mkDeserializeExprOne 'deserializeWithSize con)+ else $(varE deserializeWithKeys)+ $(varE hOff)+ ($(varE initialOff) + w32_int encLen)+ $(varE _arr)+ $(varE endOff)|]++ where++ hval = headerValue con+ hlen = length hval
+ src/Streamly/Internal/Data/Serialize/Type.hs view
@@ -0,0 +1,321 @@+{- HLINT ignore -}+-- |+-- Module : Streamly.Internal.Data.Serialize.Type+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3-3-Clause+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--++module Streamly.Internal.Data.Serialize.Type+ (+ Serialize(..)+ ) where++--------------------------------------------------------------------------------+-- Imports+--------------------------------------------------------------------------------++import Control.Monad (when)+import Data.List (foldl')+import Data.Proxy (Proxy (..))+import Streamly.Internal.Data.Unbox (Unbox)+import Streamly.Internal.Data.MutByteArray.Type (MutByteArray(..))+import Streamly.Internal.Data.Array.Type (Array(..))+import GHC.Int (Int16(..), Int32(..), Int64(..), Int8(..))+import GHC.Word (Word16(..), Word32(..), Word64(..), Word8(..))+import GHC.Stable (StablePtr(..))+import GHC.Fingerprint.Type (Fingerprint)++import qualified Streamly.Internal.Data.Array.Type as Array+import qualified Streamly.Internal.Data.MutArray.Type as MutArray+import qualified Streamly.Internal.Data.MutByteArray.Type as MBA+import qualified Streamly.Internal.Data.Unbox as Unbox++import GHC.Exts+import Prelude hiding (Foldable(..))++--------------------------------------------------------------------------------+-- Developer Note+--------------------------------------------------------------------------------++-- IMPORTANT+-- =========+--+-- Don't ever serialize the absolute offsets in the encoding. Serialize length+-- instead. Absolute offsets are NOT stable.+--+-- They will only work if the start offset of the Array when encoding and+-- decoding is the same. This is almost never the case.++--------------------------------------------------------------------------------+-- Types+--------------------------------------------------------------------------------++-- | The 'Serialize' type class provides operations for serialization and+-- deserialization of general Haskell data types to and from their byte stream+-- representation.+--+-- Unlike 'Unbox', 'Serialize' uses variable length encoding, therefore, it can+-- serialize recursive and variable length data types like lists, or variable+-- length sum types where the length of the value may vary depending on a+-- particular constructor. For variable length data types the length is encoded+-- along with the data.+--+-- The 'deserializeAt' operation reads bytes from the mutable byte array and+-- builds a Haskell data type from these bytes, the number of bytes it reads+-- depends on the type and the encoded value it is reading. 'serializeAt'+-- operation converts a Haskell data type to its binary representation which+-- must consist of as many bytes as added by the @addSizeTo@ operation for that+-- value and then stores these bytes into the mutable byte array. The+-- programmer is expected to use the @addSizeTo@ operation and allocate an+-- array of sufficient length before calling 'serializeAt'.+--+-- IMPORTANT: The serialized data's byte ordering remains the same as the host+-- machine's byte order. Therefore, it can not be deserialized from host+-- machines with a different byte ordering.+--+-- Instances can be derived via Template Haskell, or written manually.+--+-- Here is an example, for deriving an instance of this type class using+-- template Haskell:+--+-- >>> :{+-- data Object = Object+-- { _obj1 :: [Int]+-- , _obj2 :: Int+-- }+-- :}+--+-- @+-- import Streamly.Data.MutByteArray (deriveSerialize)+-- \$(deriveSerialize [d|instance Serialize Object|])+-- @+--+-- See 'Streamly.Data.MutByteArray.deriveSerialize' and+-- 'Streamly.Data.MutByteArray.deriveSerializeWith' for more information on+-- deriving using Template Haskell.+--+-- Here is an example of a manual instance.+--+-- >>> import Streamly.Data.MutByteArray (Serialize(..))+--+-- >>> :{+-- instance Serialize Object where+-- addSizeTo acc obj = addSizeTo (addSizeTo acc (_obj1 obj)) (_obj2 obj)+-- deserializeAt i arr len = do+-- -- Check the array bounds before reading+-- (i1, x0) <- deserializeAt i arr len+-- (i2, x1) <- deserializeAt i1 arr len+-- pure (i2, Object x0 x1)+-- serializeAt i arr (Object x0 x1) = do+-- i1 <- serializeAt i arr x0+-- i2 <- serializeAt i1 arr x1+-- pure i2+-- :}+--+class Serialize a where+ -- XXX Use (a -> Sum Int) instead, remove the Size type++ -- A left fold step to fold a generic structure to its serializable size.+ -- It is of the form @Int -> a -> Int@ because you can have tail-recursive+ -- traversal of the structures.++ -- | @addSizeTo accum value@ returns @accum@ incremented by the size of the+ -- serialized representation of @value@ in bytes. Size cannot be zero. It+ -- should be at least 1 byte.+ addSizeTo :: Int -> a -> Int++ -- We can implement the following functions without returning the `Int`+ -- offset but that may require traversing the Haskell structure again to get+ -- the size. Therefore, this is a performance optimization.++ -- | @deserializeAt byte-offset array arrayLen@ deserializes a value from+ -- the given byte-offset in the array. Returns a tuple consisting of the+ -- next byte-offset and the deserialized value.+ --+ -- The arrayLen passed is the entire length of the input buffer. It is to+ -- be used to check if we would overflow the input buffer when+ -- deserializing.+ --+ -- Throws an exception if the operation would exceed the supplied arrayLen.+ deserializeAt :: Int -> MutByteArray -> Int -> IO (Int, a)++ -- | @serializeAt byte-offset array value@ writes the serialized+ -- representation of the @value@ in the array at the given byte-offset.+ -- Returns the next byte-offset.+ --+ -- This is an unsafe operation, the programmer must ensure that the array+ -- has enough space available to serialize the value as determined by the+ -- @addSizeTo@ operation.+ serializeAt :: Int -> MutByteArray -> a -> IO Int++--------------------------------------------------------------------------------+-- Instances+--------------------------------------------------------------------------------++-- _size is the length from array start to the last accessed byte.+#ifdef DEBUG+{-# INLINE checkBounds #-}+checkBounds :: String -> Int -> MutByteArray -> IO ()+checkBounds _label _size _arr = do+ sz <- MBA.length _arr+ if (_size > sz)+ then error+ $ _label+ ++ ": accessing array at offset = "+ ++ show (_size - 1)+ ++ " max valid offset = " ++ show (sz - 1)+ else return ()+#endif++-- Note: Instead of passing around the size parameter, we can use+-- (MBA.length arr) for checking the array bound, but that turns+-- out to be more expensive.+--+-- Another way to optimize this is to avoid the check for fixed size+-- structures. For fixed size structures we can do a check at the top level and+-- then use checkless deserialization using the Unbox type class. That will+-- require ConstSize and VarSize constructors in size. The programmer can+-- bundle all const size fields in a newtype to make serialization faster. This+-- can speed up the computation of size when serializing and checking size when+-- deserialing.+--+-- For variable size non-recursive structures a separate size validation method+-- could be used to validate the size before deserializing. "validate" can also+-- be used to collpase multiple chunks of arrays coming from network into a+-- single array for deserializing. But that can also be done by framing the+-- serialized value with a size header.+--+{-# INLINE deserializeChecked #-}+deserializeChecked :: forall a. Unbox a => Int -> MutByteArray -> Int -> IO (Int, a)+deserializeChecked off arr sz =+ let next = off + Unbox.sizeOf (Proxy :: Proxy a)+ in do+ -- Keep likely path in the straight branch.+ if next <= sz+ then Unbox.peekAt off arr >>= \val -> pure (next, val)+ else error+ $ "deserializeAt: accessing array at offset = "+ ++ show (next - 1)+ ++ " max valid offset = " ++ show (sz - 1)++{-# INLINE serializeUnsafe #-}+serializeUnsafe :: forall a. Unbox a => Int -> MutByteArray -> a -> IO Int+serializeUnsafe off arr val =+ let next = off + Unbox.sizeOf (Proxy :: Proxy a)+ in do+#ifdef DEBUG+ checkBounds "serializeAt" next arr+#endif+ Unbox.pokeAt off arr val+ pure next++#define DERIVE_SERIALIZE_FROM_UNBOX(_type) \+instance Serialize _type where \+; {-# INLINE addSizeTo #-} \+; addSizeTo acc _ = acc + Unbox.sizeOf (Proxy :: Proxy _type) \+; {-# INLINE deserializeAt #-} \+; deserializeAt off arr end = deserializeChecked off arr end :: IO (Int, _type) \+; {-# INLINE serializeAt #-} \+; serializeAt = \+ serializeUnsafe :: Int -> MutByteArray -> _type -> IO Int++DERIVE_SERIALIZE_FROM_UNBOX(())+DERIVE_SERIALIZE_FROM_UNBOX(Bool)+DERIVE_SERIALIZE_FROM_UNBOX(Char)+DERIVE_SERIALIZE_FROM_UNBOX(Int8)+DERIVE_SERIALIZE_FROM_UNBOX(Int16)+DERIVE_SERIALIZE_FROM_UNBOX(Int32)+DERIVE_SERIALIZE_FROM_UNBOX(Int)+DERIVE_SERIALIZE_FROM_UNBOX(Int64)+DERIVE_SERIALIZE_FROM_UNBOX(Word)+DERIVE_SERIALIZE_FROM_UNBOX(Word8)+DERIVE_SERIALIZE_FROM_UNBOX(Word16)+DERIVE_SERIALIZE_FROM_UNBOX(Word32)+DERIVE_SERIALIZE_FROM_UNBOX(Word64)+DERIVE_SERIALIZE_FROM_UNBOX(Double)+DERIVE_SERIALIZE_FROM_UNBOX(Float)+DERIVE_SERIALIZE_FROM_UNBOX((StablePtr a))+DERIVE_SERIALIZE_FROM_UNBOX((Ptr a))+DERIVE_SERIALIZE_FROM_UNBOX((FunPtr a))+DERIVE_SERIALIZE_FROM_UNBOX(Fingerprint)++instance forall a. Serialize a => Serialize [a] where++ -- {-# INLINE addSizeTo #-}+ addSizeTo acc xs =+ foldl' addSizeTo (acc + Unbox.sizeOf (Proxy :: Proxy Int)) xs++ -- Inlining this causes large compilation times for tests+ {-# INLINABLE deserializeAt #-}+ deserializeAt off arr sz = do+ (off1, len64) <- deserializeAt off arr sz :: IO (Int, Int64)+ let len = (fromIntegral :: Int64 -> Int) len64+ peekList f o i | i >= 3 = do+ -- Unfold the loop three times+ (o1, x1) <- deserializeAt o arr sz+ (o2, x2) <- deserializeAt o1 arr sz+ (o3, x3) <- deserializeAt o2 arr sz+ peekList (f . (\xs -> x1:x2:x3:xs)) o3 (i - 3)+ peekList f o 0 = pure (o, f [])+ peekList f o i = do+ (o1, x) <- deserializeAt o arr sz+ peekList (f . (x:)) o1 (i - 1)+ peekList id off1 len++ -- Inlining this causes large compilation times for tests+ {-# INLINABLE serializeAt #-}+ serializeAt off arr val = do+ let off1 = off + Unbox.sizeOf (Proxy :: Proxy Int64)+ let pokeList acc o [] =+ Unbox.pokeAt off arr (acc :: Int64) >> pure o+ pokeList acc o (x:xs) = do+ o1 <- serializeAt o arr x+ pokeList (acc + 1) o1 xs+ pokeList 0 off1 val++instance+#ifdef DEVBUILD+ Unbox a =>+#endif+ Serialize (Array a) where+ {-# INLINE addSizeTo #-}+ addSizeTo i (Array {..}) = i + (arrEnd - arrStart) + 8++ {-# INLINE deserializeAt #-}+ deserializeAt off arr len = do+ (off1, byteLen) <- deserializeAt off arr len :: IO (Int, Int)+ let off2 = off1 + byteLen+ when (off2 > len) $+ error+ $ "deserializeAt: accessing array at offset = "+ ++ show (off2 - 1)+ ++ " max valid offset = " ++ show (len - 1)+ -- XXX Use MutByteArray.cloneSliceUnsafe+ let slice = MutArray.MutArray arr off1 off2 off2+ newArr <- MutArray.clone slice+ pure (off2, Array.unsafeFreeze newArr)++ {-# INLINE serializeAt #-}+ serializeAt off arr (Array {..}) = do+ let arrLen = arrEnd - arrStart+ off1 <- serializeAt off arr arrLen+ MBA.unsafePutSlice arrContents arrStart arr off1 arrLen+ pure (off1 + arrLen)++instance (Serialize a, Serialize b) => Serialize (a, b) where++ {-# INLINE addSizeTo #-}+ addSizeTo acc (a, b) = addSizeTo (addSizeTo acc a) b++ {-# INLINE serializeAt #-}+ serializeAt off arr (a, b) = do+ off1 <- serializeAt off arr a+ serializeAt off1 arr b++ {-# INLINE deserializeAt #-}+ deserializeAt off arr end = do+ (off1, a) <- deserializeAt off arr end+ (off2, b) <- deserializeAt off1 arr end+ pure (off2, (a, b))
src/Streamly/Internal/Data/Stream.hs view
@@ -1,14 +1,41 @@ -- | -- Module : Streamly.Internal.Data.Stream--- Copyright : (c) 2019 Composewell Technologies+-- Copyright : (c) 2018 Composewell Technologies -- License : BSD-3-Clause -- Maintainer : streamly@composewell.com -- Stability : experimental -- Portability : GHC --+-- Direct style re-implementation of CPS stream in+-- "Streamly.Internal.Data.StreamK". GHC is able to INLINE and fuse direct+-- style better, providing better performance than CPS implementation.+--+-- @+-- import qualified Streamly.Internal.Data.Stream as Stream+-- @+ module Streamly.Internal.Data.Stream- ( module Streamly.Internal.Data.Stream.StreamD+ (+ module Streamly.Internal.Data.Stream.Type+ , module Streamly.Internal.Data.Stream.Generate+ , module Streamly.Internal.Data.Stream.Eliminate+ , module Streamly.Internal.Data.Stream.Exception+ , module Streamly.Internal.Data.Stream.Lift+ , module Streamly.Internal.Data.Stream.Transformer+ , module Streamly.Internal.Data.Stream.Nesting+ , module Streamly.Internal.Data.Stream.Transform+ , module Streamly.Internal.Data.Stream.Top+ , module Streamly.Internal.Data.Stream.Container ) where -import Streamly.Internal.Data.Stream.StreamD+import Streamly.Internal.Data.Stream.Type+import Streamly.Internal.Data.Stream.Generate+import Streamly.Internal.Data.Stream.Eliminate+import Streamly.Internal.Data.Stream.Exception+import Streamly.Internal.Data.Stream.Lift+import Streamly.Internal.Data.Stream.Transformer+import Streamly.Internal.Data.Stream.Nesting+import Streamly.Internal.Data.Stream.Transform+import Streamly.Internal.Data.Stream.Top+import Streamly.Internal.Data.Stream.Container
− src/Streamly/Internal/Data/Stream/Bottom.hs
@@ -1,670 +0,0 @@--- |--- Module : Streamly.Internal.Data.Stream.Bottom--- Copyright : (c) 2017 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ Bottom level Stream module that can be used by all other upper level--- Stream modules.--module Streamly.Internal.Data.Stream.Bottom- (- -- * Generation- fromPure- , fromEffect- , fromList- , timesWith- , absTimesWith- , relTimesWith-- -- * Folds- , fold- , foldBreak- , foldBreak2- , foldEither- , foldEither2- , foldConcat-- -- * Builders- , foldAdd- , foldAddLazy-- -- * Scans- , smapM- -- $smapM_Notes- , postscan- , catMaybes- , scanMaybe-- , take- , takeWhile- , takeEndBy- , drop- , findIndices-- -- * Merge- , intersperseM-- -- * Fold and Unfold- , reverse- , reverse'-- -- * Expand- , concatEffect- , concatEffect2- , concatMapM- , concatMap-- -- * Reduce- , foldManyPost-- -- * Zipping- , zipWithM- , zipWith- )-where--#include "inline.hs"--import Control.Monad.IO.Class (MonadIO(..))-import GHC.Types (SPEC(..))-import Streamly.Internal.Data.Fold.Type (Fold (..))-import Streamly.Internal.Data.Time.Units (AbsTime, RelTime64, addToAbsTime64)-import Streamly.Internal.Data.Unboxed (Unbox)-import Streamly.Internal.Data.Producer.Type (Producer(..))-import Streamly.Internal.System.IO (defaultChunkSize)-import Streamly.Internal.Data.SVar.Type (defState)--import qualified Streamly.Internal.Data.Array.Type as A-import qualified Streamly.Internal.Data.Fold as Fold-import qualified Streamly.Internal.Data.Stream.StreamK as K-import qualified Streamly.Internal.Data.Stream.StreamD as D--import Prelude hiding (take, takeWhile, drop, reverse, concatMap, map, zipWith)--import Streamly.Internal.Data.Stream.Type------- $setup--- >>> :m--- >>> import Control.Monad (join, (>=>), (<=<))--- >>> import Data.Function (fix, (&))--- >>> import Data.Functor.Identity (Identity)--- >>> import Data.Maybe (fromJust, isJust)--- >>> import Prelude hiding (take, takeWhile, drop, reverse)--- >>> import Streamly.Data.Array (Array)--- >>> import Streamly.Data.Fold (Fold)--- >>> import Streamly.Data.Stream (Stream)--- >>> import System.IO.Unsafe (unsafePerformIO)--- >>> import qualified Streamly.Data.Array as Array--- >>> import qualified Streamly.Data.MutArray as MArray--- >>> import qualified Streamly.Data.Fold as Fold--- >>> import qualified Streamly.Data.Parser as Parser--- >>> import qualified Streamly.Data.Unfold as Unfold--- >>> import qualified Streamly.Internal.Data.Fold as Fold (toStream)--- >>> import Streamly.Internal.Data.Stream as Stream----------------------------------------------------------------------------------- Generation - Time related----------------------------------------------------------------------------------- | @timesWith g@ returns a stream of time value tuples. The first component--- of the tuple is an absolute time reference (epoch) denoting the start of the--- stream and the second component is a time relative to the reference.------ The argument @g@ specifies the granularity of the relative time in seconds.--- A lower granularity clock gives higher precision but is more expensive in--- terms of CPU usage. Any granularity lower than 1 ms is treated as 1 ms.------ >>> import Control.Concurrent (threadDelay)--- >>> f = Fold.drainMapM (\x -> print x >> threadDelay 1000000)--- >>> Stream.fold f $ Stream.take 3 $ Stream.timesWith 0.01--- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))--- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))--- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))------ Note: This API is not safe on 32-bit machines.------ /Pre-release/----{-# INLINE timesWith #-}-timesWith :: MonadIO m => Double -> Stream m (AbsTime, RelTime64)-timesWith g = fromStreamD $ D.timesWith g---- | @absTimesWith 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. Any granularity lower than 1 ms is treated--- as 1 ms.------ >>> f = Fold.drainMapM print--- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.absTimesWith 0.01--- AbsTime (TimeSpec {sec = ..., nsec = ...})--- AbsTime (TimeSpec {sec = ..., nsec = ...})--- AbsTime (TimeSpec {sec = ..., nsec = ...})------ Note: This API is not safe on 32-bit machines.------ /Pre-release/----{-# INLINE absTimesWith #-}-absTimesWith :: MonadIO m => Double -> Stream m AbsTime-absTimesWith = fmap (uncurry addToAbsTime64) . timesWith---- | @relTimesWith g@ returns a stream of relative time values starting from 0,--- using a clock of granularity @g@ specified in seconds. A low granularity--- clock is more expensive in terms of CPU usage. Any granularity lower than 1--- ms is treated as 1 ms.------ >>> f = Fold.drainMapM print--- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.relTimesWith 0.01--- RelTime64 (NanoSecond64 ...)--- RelTime64 (NanoSecond64 ...)--- RelTime64 (NanoSecond64 ...)------ Note: This API is not safe on 32-bit machines.------ /Pre-release/----{-# INLINE relTimesWith #-}-relTimesWith :: MonadIO m => Double -> Stream m RelTime64-relTimesWith = fmap snd . timesWith----------------------------------------------------------------------------------- Elimination - Running a Fold----------------------------------------------------------------------------------- | Append a stream to a fold lazily to build an accumulator incrementally.------ Example, to continue folding a list of streams on the same sum fold:------ >>> streams = [Stream.fromList [1..5], Stream.fromList [6..10]]--- >>> f = Prelude.foldl Stream.foldAddLazy Fold.sum streams--- >>> Stream.fold f Stream.nil--- 55----{-# INLINE foldAddLazy #-}-foldAddLazy :: Monad m => Fold m a b -> Stream m a -> Fold m a b-foldAddLazy f s = D.foldAddLazy f $ toStreamD s---- >>> foldAdd f = Stream.foldAddLazy f >=> Fold.reduce---- |--- >>> foldAdd = flip Fold.addStream----foldAdd :: Monad m => Fold m a b -> Stream m a -> m (Fold m a b)-foldAdd f = fold (Fold.duplicate f)---- >>> fold f = Fold.extractM . Stream.foldAddLazy f--- >>> fold f = Stream.fold Fold.one . Stream.foldManyPost f--- >>> fold f = Fold.extractM <=< Stream.foldAdd f---- | Fold a stream using the supplied left 'Fold' and reducing the resulting--- expression strictly at each step. The behavior is similar to 'foldl''. A--- 'Fold' can terminate early without consuming the full stream. See the--- documentation of individual 'Fold's for termination behavior.------ Definitions:------ >>> fold f = fmap fst . Stream.foldBreak f--- >>> fold f = Stream.parse (Parser.fromFold f)------ Example:------ >>> Stream.fold Fold.sum (Stream.enumerateFromTo 1 100)--- 5050----{-# INLINE fold #-}-fold :: Monad m => Fold m a b -> Stream m a -> m b-fold fl strm = D.fold fl $ D.fromStreamK $ toStreamK strm---- Alternative name foldSome, but may be confused vs foldMany.---- | Like 'fold' but also returns the remaining stream. The resulting stream--- would be 'Stream.nil' if the stream finished before the fold.------ /CPS/----{-# INLINE foldBreak #-}-foldBreak :: Monad m => Fold m a b -> Stream m a -> m (b, Stream m a)-foldBreak fl strm = fmap f $ K.foldBreak fl (toStreamK strm)-- where-- f (b, str) = (b, fromStreamK str)---- XXX The quadratic slowdown in recursive use is because recursive function--- cannot be inlined and StreamD/StreamK conversions pile up and cannot be--- eliminated by rewrite rules.---- | Like 'foldBreak' but fuses.------ /Note:/ Unlike 'foldBreak', recursive application on the resulting stream--- would lead to quadratic slowdown. If you need recursion with fusion (within--- one iteration of recursion) use StreamD.foldBreak directly.------ /Internal/-{-# INLINE foldBreak2 #-}-foldBreak2 :: Monad m => Fold m a b -> Stream m a -> m (b, Stream m a)-foldBreak2 fl strm = fmap f $ D.foldBreak fl $ toStreamD strm-- where-- f (b, str) = (b, fromStreamD str)---- | Fold resulting in either breaking the stream or continuation of the fold.--- Instead of supplying the input stream in one go we can run the fold multiple--- times, each time supplying the next segment of the input stream. If the fold--- has not yet finished it returns a fold that can be run again otherwise it--- returns the fold result and the residual stream.------ /Internal/-{-# INLINE foldEither #-}-foldEither :: Monad m =>- Fold m a b -> Stream m a -> m (Either (Fold m a b) (b, Stream m a))-foldEither fl strm = fmap (fmap f) $ K.foldEither fl $ toStreamK strm-- where-- f (b, str) = (b, fromStreamK str)---- | Like 'foldEither' but fuses. However, recursive application on resulting--- stream would lead to quadratic slowdown.------ /Internal/-{-# INLINE foldEither2 #-}-foldEither2 :: Monad m =>- Fold m a b -> Stream m a -> m (Either (Fold m a b) (b, Stream m a))-foldEither2 fl strm = fmap (fmap f) $ D.foldEither fl $ toStreamD strm-- where-- f (b, str) = (b, fromStreamD str)---- XXX Array folds can be implemented using this.--- foldContainers? Specialized to foldArrays.---- | Generate streams from individual elements of a stream and fold the--- concatenation of those streams using the supplied fold. Return the result of--- the fold and residual stream.------ For example, this can be used to efficiently fold an Array Word8 stream--- using Word8 folds.------ The outer stream forces CPS to allow scalable appends and the inner stream--- forces direct style for stream fusion.------ /Internal/-{-# INLINE foldConcat #-}-foldConcat :: Monad m =>- Producer m a b -> Fold m b c -> Stream m a -> m (c, Stream m a)-foldConcat- (Producer pstep pinject pextract)- (Fold fstep begin done)- stream = do-- res <- begin- case res of- Fold.Partial fs -> go fs streamK- Fold.Done fb -> return (fb, fromStreamK streamK)-- where-- streamK = toStreamK stream-- go !acc m1 = do- let stop = do- r <- done acc- return (r, fromStreamK K.nil)- single a = do- st <- pinject a- res <- go1 SPEC acc st- case res of- Left fs -> do- r <- done fs- return (r, fromStreamK K.nil)- Right (b, s) -> do- x <- pextract s- return (b, fromStreamK (K.fromPure x))- yieldk a r = do- st <- pinject a- res <- go1 SPEC acc st- case res of- Left fs -> go fs r- Right (b, s) -> do- x <- pextract s- return (b, fromStreamK (x `K.cons` r))- in K.foldStream defState yieldk single stop m1-- {-# INLINE go1 #-}- go1 !_ !fs st = do- r <- pstep st- case r of- D.Yield x s -> do- res <- fstep fs x- case res of- Fold.Done b -> return $ Right (b, s)- Fold.Partial fs1 -> go1 SPEC fs1 s- D.Skip s -> go1 SPEC fs s- D.Stop -> return $ Left fs----------------------------------------------------------------------------------- Transformation---------------------------------------------------------------------------------{---- |--- >>> map = fmap------ Same as 'fmap'.------ >>> Stream.fold Fold.toList $ fmap (+1) $ Stream.fromList [1,2,3]--- [2,3,4]----{-# INLINE map #-}-map :: Monad m => (a -> b) -> Stream m a -> Stream m b-map f = fromStreamD . D.map f . toStreamD--}---- | Postscan a stream using the given monadic fold.------ The following example extracts the input stream up to a point where the--- running average of elements is no more than 10:------ >>> import Data.Maybe (fromJust)--- >>> let avg = Fold.teeWith (/) Fold.sum (fmap fromIntegral Fold.length)--- >>> s = Stream.enumerateFromTo 1.0 100.0--- >>> :{--- Stream.fold Fold.toList--- $ fmap (fromJust . fst)--- $ Stream.takeWhile (\(_,x) -> x <= 10)--- $ Stream.postscan (Fold.tee Fold.latest avg) s--- :}--- [1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0,17.0,18.0,19.0]----{-# INLINE postscan #-}-postscan :: Monad m => Fold m a b -> Stream m a -> Stream m b-postscan fld = fromStreamD . D.postscan fld . toStreamD---- $smapM_Notes------ The stateful step function can be simplified to @(s -> a -> m b)@ to provide--- a read-only environment. However, that would just be 'mapM'.------ The initial action could be @m (s, Maybe b)@, and we can also add a final--- action @s -> m (Maybe b)@. This can be used to get pre/post scan like--- functionality and also to flush the state in the end like scanlMAfter'.--- We can also use it along with a fusible version of bracket to get--- scanlMAfter' like functionality. See issue #677.------ This can be further generalized to a type similar to Fold/Parser, giving it--- filtering and parsing capability as well (this is in fact equivalent to--- parseMany):------ smapM :: (s -> a -> m (Step s b)) -> m s -> Stream m a -> Stream m b------- | A stateful 'mapM', equivalent to a left scan, more like mapAccumL.--- Hopefully, this is a better alternative to @scan@. Separation of state from--- the output makes it easier to think in terms of a shared state, and also--- makes it easier to keep the state fully strict and the output lazy.------ See also: 'postscan'------ /Pre-release/----{-# INLINE smapM #-}-smapM :: Monad m =>- (s -> a -> m (s, b))- -> m s- -> Stream m a- -> Stream m b-smapM step initial stream =- -- XXX implement this directly instead of using postscan- let f = Fold.foldlM'- (\(s, _) a -> step s a)- (fmap (,undefined) initial)- in fmap snd $ postscan f stream---- | In a stream of 'Maybe's, discard 'Nothing's and unwrap 'Just's.------ >>> catMaybes = Stream.mapMaybe id--- >>> catMaybes = fmap fromJust . Stream.filter isJust------ /Pre-release/----{-# INLINE catMaybes #-}-catMaybes :: Monad m => Stream m (Maybe a) -> Stream m a--- catMaybes = fmap fromJust . filter isJust-catMaybes = fromStreamD . D.catMaybes . toStreamD---- | Use a filtering fold on a stream.------ >>> scanMaybe f = Stream.catMaybes . Stream.postscan f----{-# INLINE scanMaybe #-}-scanMaybe :: Monad m => Fold m a (Maybe b) -> Stream m a -> Stream m b-scanMaybe p = catMaybes . postscan p----------------------------------------------------------------------------------- Transformation - Trimming----------------------------------------------------------------------------------- | Take first 'n' elements from the stream and discard the rest.----{-# INLINE take #-}-take :: Monad m => Int -> Stream m a -> Stream m a--- take n = scanMaybe (Fold.taking n)-take n m = fromStreamD $ D.take n $ toStreamD m---- | End the stream as soon as the predicate fails on an element.----{-# INLINE takeWhile #-}-takeWhile :: Monad m => (a -> Bool) -> Stream m a -> Stream m a--- takeWhile p = scanMaybe (Fold.takingEndBy_ (not . p))-takeWhile p m = fromStreamD $ D.takeWhile p $ toStreamD m--{-# INLINE takeEndBy #-}-takeEndBy :: Monad m => (a -> Bool) -> Stream m a -> Stream m a--- takeEndBy p = scanMaybe (Fold.takingEndBy p)-takeEndBy p m = fromStreamD $ D.takeEndBy p $ toStreamD m---- | Discard first 'n' elements from the stream and take the rest.----{-# INLINE drop #-}-drop :: Monad m => Int -> Stream m a -> Stream m a--- drop n = scanMaybe (Fold.dropping n)-drop n m = fromStreamD $ D.drop n $ toStreamD m----------------------------------------------------------------------------------- Searching----------------------------------------------------------------------------------- | Find all the indices where the element in the stream satisfies the given--- predicate.------ >>> findIndices p = Stream.scanMaybe (Fold.findIndices p)----{-# INLINE findIndices #-}-findIndices :: Monad m => (a -> Bool) -> Stream m a -> Stream m Int--- findIndices p = scanMaybe (Fold.findIndices p)-findIndices p m = fromStreamD $ D.findIndices p (toStreamD m)----------------------------------------------------------------------------------- Transformation by Inserting----------------------------------------------------------------------------------- intersperseM = intersperseMWith 1---- | Insert an effect and its output before consuming an element of a stream--- except the first one.------ >>> input = Stream.fromList "hello"--- >>> Stream.fold Fold.toList $ Stream.trace putChar $ Stream.intersperseM (putChar '.' >> return ',') input--- h.,e.,l.,l.,o"h,e,l,l,o"------ Be careful about the order of effects. In the above example we used trace--- after the intersperse, if we use it before the intersperse the output would--- be he.l.l.o."h,e,l,l,o".------ >>> Stream.fold Fold.toList $ Stream.intersperseM (putChar '.' >> return ',') $ Stream.trace putChar input--- he.l.l.o."h,e,l,l,o"----{-# INLINE intersperseM #-}-intersperseM :: Monad m => m a -> Stream m a -> Stream m a-intersperseM m = fromStreamD . D.intersperseM m . toStreamD----------------------------------------------------------------------------------- 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.------ | Returns the elements of the stream in reverse order. The stream must be--- finite. Note that this necessarily buffers the entire stream in memory.------ >>> reverse = Stream.foldlT (flip Stream.cons) Stream.nil----{-# INLINE reverse #-}-reverse :: Stream m a -> Stream m a-reverse s = fromStreamK $ K.reverse $ toStreamK s---- | Like 'reverse' but several times faster, requires a 'Storable' instance.------ /O(n) space/------ /Pre-release/-{-# INLINE reverse' #-}-reverse' :: (MonadIO m, Unbox a) => Stream m a -> Stream m a--- reverse' s = fromStreamD $ D.reverse' $ toStreamD s-reverse' =- fromStreamD- . A.flattenArraysRev -- unfoldMany A.readRev- . D.fromStreamK- . K.reverse- . D.toStreamK- . A.chunksOf defaultChunkSize- . toStreamD----------------------------------------------------------------------------------- Combine streams and flatten----------------------------------------------------------------------------------- | 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.------ See 'unfoldMany' for a fusible alternative.----{-# INLINE concatMapM #-}-concatMapM :: Monad m => (a -> m (Stream m b)) -> Stream m a -> Stream m b-concatMapM f m = fromStreamD $ D.concatMapM (fmap toStreamD . f) (toStreamD m)---- | Map a stream producing function on each element of the stream and then--- flatten the results into a single stream.------ >>> concatMap f = Stream.concatMapM (return . f)--- >>> concatMap f = Stream.concatMapWith Stream.append f--- >>> concatMap f = Stream.concat . fmap f--- >>> concatMap f = Stream.unfoldMany (Unfold.lmap f Unfold.fromStream)------ See 'unfoldMany' for a fusible alternative.----{-# INLINE concatMap #-}-concatMap ::Monad m => (a -> Stream m b) -> Stream m a -> Stream m b-concatMap f m = fromStreamD $ D.concatMap (toStreamD . f) (toStreamD m)---- >>> concatEffect = Stream.concat . lift -- requires (MonadTrans t)--- >>> concatEffect = join . lift -- requires (MonadTrans t, Monad (Stream m))---- | Given a stream value in the underlying monad, lift and join the underlying--- monad with the stream monad.------ >>> concatEffect = Stream.concat . Stream.fromEffect------ See also: 'concat', 'sequence'------ See 'concatEffect2' for a fusible alternative.------ /CPS/----{-# INLINE concatEffect #-}-concatEffect :: Monad m => m (Stream m a) -> Stream m a-concatEffect generator =- fromStreamK $ K.concatEffect $ fmap toStreamK generator--{-# INLINE concatEffect2 #-}-concatEffect2 :: Monad m => m (Stream m a) -> Stream m a--- concatEffect generator = concatMapM (\() -> generator) (fromPure ())-concatEffect2 generator =- fromStreamD $ D.concatEffect $ fmap toStreamD generator---- XXX Need a more intuitive name, and need to reconcile the names--- foldMany/fold/parse/parseMany/parseManyPost etc.---- | Like 'foldMany' but evaluates the fold before the stream, and yields its--- output even if the stream is empty, therefore, always results in a non-empty--- output even on an empty stream (default result of the fold).------ Example, empty stream:------ >>> f = Fold.take 2 Fold.sum--- >>> fmany = Stream.fold Fold.toList . Stream.foldManyPost f--- >>> fmany $ Stream.fromList []--- [0]------ Example, last fold empty:------ >>> fmany $ Stream.fromList [1..4]--- [3,7,0]------ Example, last fold non-empty:------ >>> fmany $ Stream.fromList [1..5]--- [3,7,5]------ Note that using a closed fold e.g. @Fold.take 0@, would result in an--- infinite stream without consuming the input.------ /Pre-release/----{-# INLINE foldManyPost #-}-foldManyPost- :: Monad m- => Fold m a b- -> Stream m a- -> Stream m b-foldManyPost f m = fromStreamD $ D.foldManyPost f (toStreamD m)----------------------------------------------------------------------------------- Zipping----------------------------------------------------------------------------------- | Like 'zipWith' but using a monadic zipping function.----{-# INLINE zipWithM #-}-zipWithM :: Monad m =>- (a -> b -> m c) -> Stream m a -> Stream m b -> Stream m c-zipWithM f m1 m2 = fromStreamK $ K.zipWithM f (toStreamK m1) (toStreamK m2)---- | Stream @a@ is evaluated first, followed by stream @b@, the resulting--- elements @a@ and @b@ are then zipped using the supplied zip function and the--- result @c@ is yielded to the consumer.------ If stream @a@ or stream @b@ ends, the zipped stream ends. If stream @b@ ends--- first, the element @a@ from previous evaluation of stream @a@ is discarded.------ >>> s1 = Stream.fromList [1,2,3]--- >>> s2 = Stream.fromList [4,5,6]--- >>> Stream.fold Fold.toList $ Stream.zipWith (+) s1 s2--- [5,7,9]----{-# INLINE zipWith #-}-zipWith :: Monad m => (a -> b -> c) -> Stream m a -> Stream m b -> Stream m c-zipWith f m1 m2 = fromStreamK $ K.zipWith f (toStreamK m1) (toStreamK m2)
− src/Streamly/Internal/Data/Stream/Chunked.hs
@@ -1,1215 +0,0 @@--- |--- Module : Streamly.Internal.Data.Stream.Chunked--- Copyright : (c) 2019 Composewell Technologies--- License : BSD3-3-Clause--- Maintainer : streamly@composewell.com--- Stability : pre-release--- Portability : GHC------ Combinators to efficiently manipulate streams of immutable arrays.----module Streamly.Internal.Data.Stream.Chunked- (- -- * Creation- chunksOf-- -- * Flattening to elements- , concat- , concatRev- , interpose- , interposeSuffix- , intercalateSuffix- , unlines-- -- * Elimination- -- ** Element Folds- -- The byte level foldBreak can work as efficiently as the chunk level. We- -- can flatten the stream to byte stream and use that. But if we want the- -- remaining stream to be a chunk stream then this could be handy. But it- -- could also be implemented using parseBreak.- , foldBreak -- StreamK.foldBreakChunks- , foldBreakD- -- The byte level parseBreak cannot work efficiently. Because the stream- -- will have to be a StreamK for backtracking, StreamK at byte level would- -- not be efficient.- , parseBreak -- StreamK.parseBreakChunks- -- , parseBreakD- -- , foldManyChunks- -- , parseManyChunks-- -- ** Array Folds- -- XXX Use Parser.Chunked instead, need only chunkedParseBreak,- -- foldBreak can be implemented using parseBreak. Use StreamK.- , runArrayFold- , runArrayFoldBreak- -- , parseArr- , runArrayParserDBreak -- StreamK.chunkedParseBreak- , runArrayFoldMany -- StreamK.chunkedParseMany-- , toArray-- -- * Compaction- -- We can use something like foldManyChunks, parseManyChunks with a take- -- fold.- , lpackArraysChunksOf -- Fold.compactChunks- , compact -- rechunk, compactChunks-- -- * Splitting- -- We can use something like foldManyChunks, parseManyChunks with an- -- appropriate splitting fold.- , splitOn -- Stream.rechunkOn- , splitOnSuffix -- Stream.rechunkOnSuffix- )-where--#include "ArrayMacros.h"-#include "inline.hs"--import Data.Bifunctor (second)-import Control.Exception (assert)-import Control.Monad.IO.Class (MonadIO(..))-import Data.Proxy (Proxy(..))-import Data.Word (Word8)-import Streamly.Internal.Data.Unboxed (Unbox, peekWith, sizeOf)-import Fusion.Plugin.Types (Fuse(..))-import GHC.Exts (SpecConstrAnnotation(..))-import GHC.Types (SPEC(..))-import Prelude hiding (null, last, (!!), read, concat, unlines)--import Streamly.Data.Fold (Fold)-import Streamly.Internal.Data.Array.Type (Array(..))-import Streamly.Internal.Data.Array.Mut.Type (MutArray)-import Streamly.Internal.Data.Fold.Chunked (ChunkFold(..))-import Streamly.Internal.Data.Parser (ParseError(..))-import Streamly.Internal.Data.Stream.StreamD (Stream)-import Streamly.Internal.Data.Stream.StreamK (StreamK, fromStream, toStream)-import Streamly.Internal.Data.SVar.Type (adaptState, defState)-import Streamly.Internal.Data.Array.Mut.Type- (allocBytesToElemCount)-import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))--import qualified Streamly.Data.Fold as FL-import qualified Streamly.Internal.Data.Array as A-import qualified Streamly.Internal.Data.Array as Array-import qualified Streamly.Internal.Data.Array.Type as A-import qualified Streamly.Internal.Data.Array.Mut.Type as MA-import qualified Streamly.Internal.Data.Array.Mut.Stream as AS-import qualified Streamly.Internal.Data.Fold.Type as FL (Fold(..), Step(..))-import qualified Streamly.Internal.Data.Parser as PR-import qualified Streamly.Internal.Data.Parser.ParserD as PRD- (Parser(..), Initial(..))-import qualified Streamly.Internal.Data.Stream.StreamD as D-import qualified Streamly.Internal.Data.Stream.StreamK as K---- XXX Since these are immutable arrays MonadIO constraint can be removed from--- most places.------------------------------------------------------------------------------------ Generation------------------------------------------------------------------------------------ | @chunksOf n stream@ groups the elements in the input stream into arrays of--- @n@ elements each.------ > chunksOf n = Stream.groupsOf n (Array.writeN n)------ /Pre-release/-{-# INLINE chunksOf #-}-chunksOf :: (MonadIO m, Unbox a)- => Int -> Stream m a -> Stream m (Array a)-chunksOf = A.chunksOf------------------------------------------------------------------------------------ Append------------------------------------------------------------------------------------ XXX efficiently compare two streams of arrays. Two streams can have chunks--- of different sizes, we can handle that in the stream comparison abstraction.--- This could be useful e.g. to fast compare whether two files differ.---- | Convert a stream of arrays into a stream of their elements.------ Same as the following:------ > concat = Stream.unfoldMany Array.read------ @since 0.7.0-{-# INLINE concat #-}-concat :: (Monad m, Unbox a) => Stream m (Array a) -> Stream m a--- concat m = fromStreamD $ A.flattenArrays (toStreamD m)--- concat m = fromStreamD $ D.concatMap A.toStreamD (toStreamD m)-concat = D.unfoldMany A.reader---- | Convert a stream of arrays into a stream of their elements reversing the--- contents of each array before flattening.------ > concatRev = Stream.unfoldMany Array.readerRev------ @since 0.7.0-{-# INLINE concatRev #-}-concatRev :: (Monad m, Unbox a) => Stream m (Array a) -> Stream m a--- concatRev m = fromStreamD $ A.flattenArraysRev (toStreamD m)-concatRev = D.unfoldMany A.readerRev------------------------------------------------------------------------------------ Intersperse and append------------------------------------------------------------------------------------ | Flatten a stream of arrays after inserting the given element between--- arrays.------ /Pre-release/-{-# INLINE interpose #-}-interpose :: (Monad m, Unbox a) => a -> Stream m (Array a) -> Stream m a-interpose x = D.interpose x A.reader--{-# INLINE intercalateSuffix #-}-intercalateSuffix :: (Monad m, Unbox a)- => Array a -> Stream m (Array a) -> Stream m a-intercalateSuffix = D.intercalateSuffix A.reader---- | Flatten a stream of arrays appending the given element after each--- array.------ @since 0.7.0-{-# INLINE interposeSuffix #-}-interposeSuffix :: (Monad m, Unbox a)- => a -> Stream m (Array a) -> Stream m a--- interposeSuffix x = fromStreamD . A.unlines x . toStreamD-interposeSuffix x = D.interposeSuffix x A.reader--data FlattenState s =- OuterLoop s- | InnerLoop s !MA.MutableByteArray !Int !Int---- XXX This is a special case of interposeSuffix, can be removed.--- XXX Remove monadIO constraint-{-# INLINE_NORMAL unlines #-}-unlines :: forall m a. (MonadIO m, Unbox a)- => a -> D.Stream m (Array a) -> D.Stream m a-unlines sep (D.Stream step state) = D.Stream step' (OuterLoop state)- where- {-# INLINE_LATE step' #-}- step' gst (OuterLoop st) = do- r <- step (adaptState gst) st- return $ case r of- D.Yield Array{..} s ->- D.Skip (InnerLoop s arrContents arrStart arrEnd)- D.Skip s -> D.Skip (OuterLoop s)- D.Stop -> D.Stop-- step' _ (InnerLoop st _ p end) | p == end =- return $ D.Yield sep $ OuterLoop st-- step' _ (InnerLoop st contents p end) = do- x <- liftIO $ peekWith contents p- return $ D.Yield x (InnerLoop st contents (INDEX_NEXT(p,a)) end)------------------------------------------------------------------------------------ Compact------------------------------------------------------------------------------------ XXX These would not be needed once we implement compactLEFold, see--- module Streamly.Internal.Data.Array.Mut.Stream------ XXX Note that this thaws immutable arrays for appending, that may be--- problematic if multiple users do the same thing, however, immutable arrays--- would usually have no capacity to append, therefore, a copy will be forced--- anyway. Confirm this. We can forcefully trim the array capacity before thaw--- to ensure this.-{-# INLINE_NORMAL packArraysChunksOf #-}-packArraysChunksOf :: (MonadIO m, Unbox a)- => Int -> D.Stream m (Array a) -> D.Stream m (Array a)-packArraysChunksOf n str =- D.map A.unsafeFreeze $ AS.packArraysChunksOf n $ D.map A.unsafeThaw str---- XXX instead of writing two different versions of this operation, we should--- write it as a pipe.------ XXX Confirm that immutable arrays won't be modified.-{-# INLINE_NORMAL lpackArraysChunksOf #-}-lpackArraysChunksOf :: (MonadIO m, Unbox a)- => Int -> Fold m (Array a) () -> Fold m (Array a) ()-lpackArraysChunksOf n fld =- FL.lmap A.unsafeThaw $ AS.lpackArraysChunksOf n (FL.lmap A.unsafeFreeze fld)---- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a--- maximum specified size in bytes.------ @since 0.7.0-{-# INLINE compact #-}-compact :: (MonadIO m, Unbox a)- => Int -> Stream m (Array a) -> Stream m (Array a)-compact = packArraysChunksOf------------------------------------------------------------------------------------ Split----------------------------------------------------------------------------------data SplitState s arr- = Initial s- | Buffering s arr- | Splitting s arr- | Yielding arr (SplitState s arr)- | Finishing---- | Split a stream of arrays on a given separator byte, dropping the separator--- and coalescing all the arrays between two separators into a single array.------ @since 0.7.0-{-# INLINE_NORMAL _splitOn #-}-_splitOn- :: MonadIO m- => Word8- -> D.Stream m (Array Word8)- -> D.Stream m (Array Word8)-_splitOn byte (D.Stream step state) = D.Stream step' (Initial state)-- where-- {-# INLINE_LATE step' #-}- step' gst (Initial st) = do- r <- step gst st- case r of- D.Yield arr s -> do- (arr1, marr2) <- A.breakOn byte arr- return $ case marr2 of- Nothing -> D.Skip (Buffering s arr1)- Just arr2 -> D.Skip (Yielding arr1 (Splitting s arr2))- D.Skip s -> return $ D.Skip (Initial s)- D.Stop -> return D.Stop-- step' gst (Buffering st buf) = do- r <- step gst st- case r of- D.Yield arr s -> do- (arr1, marr2) <- A.breakOn byte arr- buf' <- A.splice buf arr1- return $ case marr2 of- Nothing -> D.Skip (Buffering s buf')- Just x -> D.Skip (Yielding buf' (Splitting s x))- D.Skip s -> return $ D.Skip (Buffering s buf)- D.Stop -> return $- if A.byteLength buf == 0- then D.Stop- else D.Skip (Yielding buf Finishing)-- step' _ (Splitting st buf) = do- (arr1, marr2) <- A.breakOn byte buf- return $ case marr2 of- Nothing -> D.Skip $ Buffering st arr1- Just arr2 -> D.Skip $ Yielding arr1 (Splitting st arr2)-- step' _ (Yielding arr next) = return $ D.Yield arr next- step' _ Finishing = return D.Stop---- XXX Remove MonadIO constraint.--- | Split a stream of arrays on a given separator byte, dropping the separator--- and coalescing all the arrays between two separators into a single array.------ @since 0.7.0-{-# INLINE splitOn #-}-splitOn- :: (MonadIO m)- => Word8- -> Stream m (Array Word8)- -> Stream m (Array Word8)-splitOn byte = D.splitInnerBy (A.breakOn byte) A.splice--{-# INLINE splitOnSuffix #-}-splitOnSuffix- :: (MonadIO m)- => Word8- -> Stream m (Array Word8)- -> Stream m (Array Word8)--- splitOn byte s = fromStreamD $ A.splitOn byte $ toStreamD s-splitOnSuffix byte = D.splitInnerBySuffix (A.breakOn byte) A.splice------------------------------------------------------------------------------------ Elimination - Running folds----------------------------------------------------------------------------------{-# INLINE_NORMAL foldBreakD #-}-foldBreakD :: forall m a b. (MonadIO m, Unbox a) =>- Fold m a b -> D.Stream m (Array a) -> m (b, D.Stream m (Array a))-foldBreakD (FL.Fold fstep initial extract) stream@(D.Stream step state) = do- res <- initial- case res of- FL.Partial fs -> go SPEC state fs- FL.Done fb -> return $! (fb, stream)-- where-- {-# INLINE go #-}- go !_ st !fs = do- r <- step defState st- case r of- D.Yield (Array contents start end) s ->- let fp = Tuple' end contents- in goArray SPEC s fp start fs- D.Skip s -> go SPEC s fs- D.Stop -> do- b <- extract fs- return (b, D.nil)-- goArray !_ s (Tuple' end _) !cur !fs- | cur == end = do- go SPEC s fs- goArray !_ st fp@(Tuple' end contents) !cur !fs = do- x <- liftIO $ peekWith contents cur- res <- fstep fs x- let next = INDEX_NEXT(cur,a)- case res of- FL.Done b -> do- let arr = Array contents next end- return $! (b, D.cons arr (D.Stream step st))- FL.Partial fs1 -> goArray SPEC st fp next fs1--{-# INLINE_NORMAL foldBreakK #-}-foldBreakK :: forall m a b. (MonadIO m, Unbox a) =>- Fold m a b -> K.StreamK m (Array a) -> m (b, K.StreamK m (Array a))-foldBreakK (FL.Fold fstep initial extract) stream = do- res <- initial- case res of- FL.Partial fs -> go fs stream- FL.Done fb -> return (fb, stream)-- where-- {-# INLINE go #-}- go !fs st = do- let stop = (, K.nil) <$> extract fs- single a = yieldk a K.nil- yieldk (Array contents start end) r =- let fp = Tuple' end contents- in goArray fs r fp start- in K.foldStream defState yieldk single stop st-- goArray !fs st (Tuple' end _) !cur- | cur == end = do- go fs st- goArray !fs st fp@(Tuple' end contents) !cur = do- x <- liftIO $ peekWith contents cur- res <- fstep fs x- let next = INDEX_NEXT(cur,a)- case res of- FL.Done b -> do- let arr = Array contents next end- return $! (b, K.cons arr st)- FL.Partial fs1 -> goArray fs1 st fp next---- | Fold an array stream using the supplied 'Fold'. Returns the fold result--- and the unconsumed stream.------ > foldBreak f = runArrayFoldBreak (ChunkFold.fromFold f)------ /Internal/----{-# INLINE_NORMAL foldBreak #-}-foldBreak ::- (MonadIO m, Unbox a)- => Fold m a b- -> StreamK m (A.Array a)- -> m (b, StreamK m (A.Array a))--- foldBreak f s = fmap fromStreamD <$> foldBreakD f (toStreamD s)-foldBreak = foldBreakK--- If foldBreak performs better than runArrayFoldBreak we can use a rewrite--- rule to rewrite runArrayFoldBreak to fold.--- foldBreak f = runArrayFoldBreak (ChunkFold.fromFold f)------------------------------------------------------------------------------------ Fold to a single Array------------------------------------------------------------------------------------ When we have to take an array partially, take the last part of the array.-{-# INLINE takeArrayListRev #-}-takeArrayListRev :: forall a. Unbox a => Int -> [Array a] -> [Array a]-takeArrayListRev = go-- where-- go _ [] = []- go n _ | n <= 0 = []- go n (x:xs) =- let len = Array.length x- in if n > len- then x : go (n - len) xs- else if n == len- then [x]- else let !(Array contents _ end) = x- !start = end - (n * SIZE_OF(a))- in [Array contents start end]---- When we have to take an array partially, take the last part of the array in--- the first split.-{-# INLINE splitAtArrayListRev #-}-splitAtArrayListRev ::- forall a. Unbox a => Int -> [Array a] -> ([Array a],[Array a])-splitAtArrayListRev n ls- | n <= 0 = ([], ls)- | otherwise = go n ls- where- go :: Int -> [Array a] -> ([Array a], [Array a])- go _ [] = ([], [])- go m (x:xs) =- let len = Array.length x- (xs', xs'') = go (m - len) xs- in if m > len- then (x:xs', xs'')- else if m == len- then ([x],xs)- else let !(Array contents start end) = x- end1 = end - (m * SIZE_OF(a))- arr2 = Array contents start end1- arr1 = Array contents end1 end- in ([arr1], arr2:xs)------------------------------------------------------------------------------------ Fold to a single Array------------------------------------------------------------------------------------ XXX Both of these implementations of splicing seem to perform equally well.--- We need to perform benchmarks over a range of sizes though.---- CAUTION! length must more than equal to lengths of all the arrays in the--- stream.-{-# INLINE spliceArraysLenUnsafe #-}-spliceArraysLenUnsafe :: (MonadIO m, Unbox a)- => Int -> Stream m (MutArray a) -> m (MutArray a)-spliceArraysLenUnsafe len buffered = do- arr <- liftIO $ MA.newPinned len- D.foldlM' MA.spliceUnsafe (return arr) buffered--{-# INLINE _spliceArrays #-}-_spliceArrays :: (MonadIO m, Unbox a)- => Stream m (Array a) -> m (Array a)-_spliceArrays s = do- buffered <- D.foldr K.cons K.nil s- len <- K.fold FL.sum (fmap Array.length buffered)- arr <- liftIO $ MA.newPinned len- final <- D.foldlM' writeArr (return arr) (toStream buffered)- return $ A.unsafeFreeze final-- where-- writeArr dst arr = MA.spliceUnsafe dst (A.unsafeThaw arr)--{-# INLINE _spliceArraysBuffered #-}-_spliceArraysBuffered :: (MonadIO m, Unbox a)- => Stream m (Array a) -> m (Array a)-_spliceArraysBuffered s = do- buffered <- D.foldr K.cons K.nil s- len <- K.fold FL.sum (fmap Array.length buffered)- A.unsafeFreeze <$>- spliceArraysLenUnsafe len (fmap A.unsafeThaw (toStream buffered))--{-# INLINE spliceArraysRealloced #-}-spliceArraysRealloced :: forall m a. (MonadIO m, Unbox a)- => Stream m (Array a) -> m (Array a)-spliceArraysRealloced s = do- let n = allocBytesToElemCount (undefined :: a) (4 * 1024)- idst = liftIO $ MA.newPinned n-- arr <- D.foldlM' MA.spliceExp idst (fmap A.unsafeThaw s)- liftIO $ A.unsafeFreeze <$> MA.rightSize arr---- XXX This should just be "fold A.write"------ | Given a stream of arrays, splice them all together to generate a single--- array. The stream must be /finite/.------ @since 0.7.0-{-# INLINE toArray #-}-toArray :: (MonadIO m, Unbox a) => Stream m (Array a) -> m (Array a)-toArray = spliceArraysRealloced--- spliceArrays = _spliceArraysBuffered---- exponentially increasing sizes of the chunks upto the max limit.--- XXX this will be easier to implement with parsers/terminating folds--- With this we should be able to reduce the number of chunks/allocations.--- The reallocation/copy based toArray can also be implemented using this.----{--{-# INLINE toArraysInRange #-}-toArraysInRange :: (MonadIO m, Unbox a)- => Int -> Int -> Fold m (Array a) b -> Fold m a b-toArraysInRange low high (Fold step initial extract) =--}--{---- | Fold the input to a pure buffered stream (List) of arrays.-{-# INLINE _toArraysOf #-}-_toArraysOf :: (MonadIO m, Unbox a)- => Int -> Fold m a (Stream Identity (Array a))-_toArraysOf n = FL.groupsOf n (A.writeNF n) FL.toStream--}------------------------------------------------------------------------------------ Elimination - running element parsers------------------------------------------------------------------------------------ GHC parser does not accept {-# ANN type [] NoSpecConstr #-}, so we need--- to make a newtype.-{-# ANN type List NoSpecConstr #-}-newtype List a = List {getList :: [a]}--{---- This can be generalized to any type provided it can be unfolded to a stream--- and it can be combined using a semigroup operation.------ XXX This should be written using CPS (as parseK) if we want it to scale wrt--- to the number of times it can be called on the same stream.-{-# INLINE_NORMAL parseBreakD #-}-parseBreakD ::- forall m a b. (MonadIO m, MonadThrow m, Unbox a)- => PRD.Parser a m b- -> D.Stream m (Array.Array a)- -> m (b, D.Stream m (Array.Array a))-parseBreakD- (PRD.Parser pstep initial extract) stream@(D.Stream step state) = do-- res <- initial- case res of- PRD.IPartial s -> go SPEC state (List []) s- PRD.IDone b -> return (b, stream)- PRD.IError err -> throwM $ ParseError err-- where-- -- "backBuf" contains last few items in the stream that we may have to- -- backtrack to.- --- -- XXX currently we are using a dumb list based approach for backtracking- -- buffer. This can be replaced by a sliding/ring buffer using Data.Array.- -- That will allow us more efficient random back and forth movement.- go !_ st backBuf !pst = do- r <- step defState st- case r of- D.Yield (Array contents start end) s ->- gobuf SPEC s backBuf- (Tuple' end contents) start pst- D.Skip s -> go SPEC s backBuf pst- D.Stop -> do- b <- extract pst- return (b, D.nil)-- -- Use strictness on "cur" to keep it unboxed- gobuf !_ s backBuf (Tuple' end _) !cur !pst- | cur == end = do- go SPEC s backBuf pst- gobuf !_ s backBuf fp@(Tuple' end contents) !cur !pst = do- x <- liftIO $ peekWith contents cur- pRes <- pstep pst x- let next = INDEX_NEXT(cur,a)- case pRes of- PR.Partial 0 pst1 ->- gobuf SPEC s (List []) fp next pst1- PR.Partial n pst1 -> do- assert (n <= Prelude.length (x:getList backBuf)) (return ())- let src0 = Prelude.take n (x:getList backBuf)- arr0 = A.fromListN n (Prelude.reverse src0)- arr1 = Array contents next end- src = arr0 <> arr1- let !(Array cont1 start end1) = src- fp1 = Tuple' end1 cont1- gobuf SPEC s (List []) fp1 start pst1- PR.Continue 0 pst1 ->- gobuf SPEC s (List (x:getList backBuf)) fp next pst1- PR.Continue n pst1 -> do- assert (n <= Prelude.length (x:getList backBuf)) (return ())- let (src0, buf1) = splitAt n (x:getList backBuf)- arr0 = A.fromListN n (Prelude.reverse src0)- arr1 = Array contents next end- src = arr0 <> arr1- let !(Array cont1 start end1) = src- fp1 = Tuple' end1 cont1- gobuf SPEC s (List buf1) fp1 start pst1- PR.Done 0 b -> do- let arr = Array contents next end- return (b, D.cons arr (D.Stream step s))- PR.Done n b -> do- assert (n <= Prelude.length (x:getList backBuf)) (return ())- let src0 = Prelude.take n (x:getList backBuf)- -- XXX create the array in reverse instead- arr0 = A.fromListN n (Prelude.reverse src0)- arr1 = Array contents next end- -- XXX Use StreamK to avoid adding arbitrary layers of- -- constructors every time.- str = D.cons arr0 (D.cons arr1 (D.Stream step s))- return (b, str)- PR.Error err -> throwM $ ParseError err--}--{-# INLINE_NORMAL parseBreakK #-}-parseBreakK ::- forall m a b. (MonadIO m, Unbox a)- => PRD.Parser a m b- -> K.StreamK m (Array.Array a)- -> m (Either ParseError b, K.StreamK m (Array.Array a))-parseBreakK (PRD.Parser pstep initial extract) stream = do- res <- initial- case res of- PRD.IPartial s -> go s stream []- PRD.IDone b -> return (Right b, stream)- PRD.IError err -> return (Left (ParseError err), stream)-- where-- -- "backBuf" contains last few items in the stream that we may have to- -- backtrack to.- --- -- XXX currently we are using a dumb list based approach for backtracking- -- buffer. This can be replaced by a sliding/ring buffer using Data.Array.- -- That will allow us more efficient random back and forth movement.- go !pst st backBuf = do- let stop = goStop pst backBuf -- (, K.nil) <$> extract pst- single a = yieldk a K.nil- yieldk arr r = goArray pst backBuf r arr- in K.foldStream defState yieldk single stop st-- -- Use strictness on "cur" to keep it unboxed- goArray !pst backBuf st (Array _ cur end) | cur == end = go pst st backBuf- goArray !pst backBuf st (Array contents cur end) = do- x <- liftIO $ peekWith contents cur- pRes <- pstep pst x- let next = INDEX_NEXT(cur,a)- case pRes of- PR.Partial 0 s ->- goArray s [] st (Array contents next end)- PR.Partial n s -> do- assert (n <= Prelude.length (x:backBuf)) (return ())- let src0 = Prelude.take n (x:backBuf)- arr0 = A.fromListN n (Prelude.reverse src0)- arr1 = Array contents next end- src = arr0 <> arr1- goArray s [] st src- PR.Continue 0 s ->- goArray s (x:backBuf) st (Array contents next end)- PR.Continue n s -> do- assert (n <= Prelude.length (x:backBuf)) (return ())- let (src0, buf1) = splitAt n (x:backBuf)- arr0 = A.fromListN n (Prelude.reverse src0)- arr1 = Array contents next end- src = arr0 <> arr1- goArray s buf1 st src- PR.Done 0 b -> do- let arr = Array contents next end- return (Right b, K.cons arr st)- PR.Done n b -> do- assert (n <= Prelude.length (x:backBuf)) (return ())- let src0 = Prelude.take n (x:backBuf)- -- XXX Use fromListRevN once implemented- -- arr0 = A.fromListRevN n src0- arr0 = A.fromListN n (Prelude.reverse src0)- arr1 = Array contents next end- str = K.cons arr0 (K.cons arr1 st)- return (Right b, str)- PR.Error err -> do- let str = K.cons (Array contents cur end) stream- return (Left (ParseError err), str)-- -- This is a simplified goArray- goExtract !pst backBuf (Array _ cur end)- | cur == end = goStop pst backBuf- goExtract !pst backBuf (Array contents cur end) = do- x <- liftIO $ peekWith contents cur- pRes <- pstep pst x- let next = INDEX_NEXT(cur,a)- case pRes of- PR.Partial 0 s ->- goExtract s [] (Array contents next end)- PR.Partial n s -> do- assert (n <= Prelude.length (x:backBuf)) (return ())- let src0 = Prelude.take n (x:backBuf)- arr0 = A.fromListN n (Prelude.reverse src0)- arr1 = Array contents next end- src = arr0 <> arr1- goExtract s [] src- PR.Continue 0 s ->- goExtract s backBuf (Array contents next end)- PR.Continue n s -> do- assert (n <= Prelude.length (x:backBuf)) (return ())- let (src0, buf1) = splitAt n (x:backBuf)- arr0 = A.fromListN n (Prelude.reverse src0)- arr1 = Array contents next end- src = arr0 <> arr1- goExtract s buf1 src- PR.Done 0 b -> do- let arr = Array contents next end- return (Right b, K.fromPure arr)- PR.Done n b -> do- assert (n <= Prelude.length backBuf) (return ())- let src0 = Prelude.take n backBuf- -- XXX Use fromListRevN once implemented- -- arr0 = A.fromListRevN n src0- arr0 = A.fromListN n (Prelude.reverse src0)- arr1 = Array contents next end- str = K.cons arr0 (K.fromPure arr1)- return (Right b, str)- PR.Error err -> do- let str = K.fromPure (Array contents cur end)- return (Left (ParseError err), str)-- -- This is a simplified goExtract- {-# INLINE goStop #-}- goStop !pst backBuf = do- pRes <- extract pst- case pRes of- PR.Partial _ _ -> error "Bug: parseBreak: Partial in extract"- PR.Continue 0 s ->- goStop s backBuf- PR.Continue n s -> do- assert (n <= Prelude.length backBuf) (return ())- let (src0, buf1) = splitAt n backBuf- arr = A.fromListN n (Prelude.reverse src0)- goExtract s buf1 arr- PR.Done 0 b ->- return (Right b, K.nil)- PR.Done n b -> do- assert (n <= Prelude.length backBuf) (return ())- let src0 = Prelude.take n backBuf- -- XXX Use fromListRevN once implemented- -- arr0 = A.fromListRevN n src0- arr0 = A.fromListN n (Prelude.reverse src0)- return (Right b, K.fromPure arr0)- PR.Error err ->- return (Left (ParseError err), K.nil)---- | Parse an array stream using the supplied 'Parser'. Returns the parse--- result and the unconsumed stream. Throws 'ParseError' if the parse fails.------ /Internal/----{-# INLINE_NORMAL parseBreak #-}-parseBreak ::- (MonadIO m, Unbox a)- => PR.Parser a m b- -> StreamK m (A.Array a)- -> m (Either ParseError b, StreamK m (A.Array a))-{--parseBreak p s =- fmap fromStreamD <$> parseBreakD (PRD.fromParserK p) (toStreamD s)--}-parseBreak = parseBreakK------------------------------------------------------------------------------------ Elimination - Running Array Folds and parsers------------------------------------------------------------------------------------ | Note that this is not the same as using a @Parser (Array a) m b@ with the--- regular "Streamly.Internal.Data.IsStream.parse" function. The regular parse--- would consume the input arrays as single unit. This parser parses in the way--- as described in the ChunkFold module. The input arrays are treated as @n@--- element units and can be consumed partially. The remaining elements are--- inserted in the source stream as an array.----{-# INLINE_NORMAL runArrayParserDBreak #-}-runArrayParserDBreak ::- forall m a b. (MonadIO m, Unbox a)- => PRD.Parser (Array a) m b- -> D.Stream m (Array.Array a)- -> m (Either ParseError b, D.Stream m (Array.Array a))-runArrayParserDBreak- (PRD.Parser pstep initial extract)- stream@(D.Stream step state) = do-- res <- initial- case res of- PRD.IPartial s -> go SPEC state (List []) s- PRD.IDone b -> return (Right b, stream)- PRD.IError err -> return (Left (ParseError err), stream)-- where-- -- "backBuf" contains last few items in the stream that we may have to- -- backtrack to.- --- -- XXX currently we are using a dumb list based approach for backtracking- -- buffer. This can be replaced by a sliding/ring buffer using Data.Array.- -- That will allow us more efficient random back and forth movement.- go _ st backBuf !pst = do- r <- step defState st- case r of- D.Yield x s -> gobuf SPEC [x] s backBuf pst- D.Skip s -> go SPEC s backBuf pst- D.Stop -> goStop backBuf pst-- gobuf !_ [] s backBuf !pst = go SPEC s backBuf pst- gobuf !_ (x:xs) s backBuf !pst = do- pRes <- pstep pst x- case pRes of- PR.Partial 0 pst1 ->- gobuf SPEC xs s (List []) pst1- PR.Partial n pst1 -> do- assert- (n <= sum (map Array.length (x:getList backBuf)))- (return ())- let src0 = takeArrayListRev n (x:getList backBuf)- src = Prelude.reverse src0 ++ xs- gobuf SPEC src s (List []) pst1- PR.Continue 0 pst1 ->- gobuf SPEC xs s (List (x:getList backBuf)) pst1- PR.Continue n pst1 -> do- assert- (n <= sum (map Array.length (x:getList backBuf)))- (return ())- let (src0, buf1) = splitAtArrayListRev n (x:getList backBuf)- src = Prelude.reverse src0 ++ xs- gobuf SPEC src s (List buf1) pst1- PR.Done 0 b -> do- let str = D.append (D.fromList xs) (D.Stream step s)- return (Right b, str)- PR.Done n b -> do- assert- (n <= sum (map Array.length (x:getList backBuf)))- (return ())- let src0 = takeArrayListRev n (x:getList backBuf)- src = Prelude.reverse src0 ++ xs- return (Right b, D.append (D.fromList src) (D.Stream step s))- PR.Error err -> do- let strm = D.append (D.fromList (x:xs)) (D.Stream step s)- return (Left (ParseError err), strm)-- -- This is a simplified gobuf- goExtract _ [] backBuf !pst = goStop backBuf pst- goExtract _ (x:xs) backBuf !pst = do- pRes <- pstep pst x- case pRes of- PR.Partial 0 pst1 ->- goExtract SPEC xs (List []) pst1- PR.Partial n pst1 -> do- assert- (n <= sum (map Array.length (x:getList backBuf)))- (return ())- let src0 = takeArrayListRev n (x:getList backBuf)- src = Prelude.reverse src0 ++ xs- goExtract SPEC src (List []) pst1- PR.Continue 0 pst1 ->- goExtract SPEC xs (List (x:getList backBuf)) pst1- PR.Continue n pst1 -> do- assert- (n <= sum (map Array.length (x:getList backBuf)))- (return ())- let (src0, buf1) = splitAtArrayListRev n (x:getList backBuf)- src = Prelude.reverse src0 ++ xs- goExtract SPEC src (List buf1) pst1- PR.Done 0 b ->- return (Right b, D.fromList xs)- PR.Done n b -> do- assert- (n <= sum (map Array.length (x:getList backBuf)))- (return ())- let src0 = takeArrayListRev n (x:getList backBuf)- src = Prelude.reverse src0 ++ xs- return (Right b, D.fromList src)- PR.Error err ->- return (Left (ParseError err), D.fromList (x:xs))-- -- This is a simplified goExtract- {-# INLINE goStop #-}- goStop backBuf pst = do- pRes <- extract pst- case pRes of- PR.Partial _ _ -> error "Bug: runArrayParserDBreak: Partial in extract"- PR.Continue 0 pst1 ->- goStop backBuf pst1- PR.Continue n pst1 -> do- assert- (n <= sum (map Array.length (getList backBuf)))- (return ())- let (src0, buf1) = splitAtArrayListRev n (getList backBuf)- src = Prelude.reverse src0- goExtract SPEC src (List buf1) pst1- PR.Done 0 b -> return (Right b, D.nil)- PR.Done n b -> do- assert- (n <= sum (map Array.length (getList backBuf)))- (return ())- let src0 = takeArrayListRev n (getList backBuf)- src = Prelude.reverse src0- return (Right b, D.fromList src)- PR.Error err ->- return (Left (ParseError err), D.nil)--{---- | Parse an array stream using the supplied 'Parser'. Returns the parse--- result and the unconsumed stream. Throws 'ParseError' if the parse fails.------ /Internal/----{-# INLINE parseArr #-}-parseArr ::- (MonadIO m, MonadThrow m, Unbox a)- => ASF.Parser a m b- -> Stream m (A.Array a)- -> m (b, Stream m (A.Array a))-parseArr p s = fmap fromStreamD <$> parseBreakD p (toStreamD s)--}---- | Fold an array stream using the supplied array stream 'Fold'.------ /Pre-release/----{-# INLINE runArrayFold #-}-runArrayFold :: (MonadIO m, Unbox a) =>- ChunkFold m a b -> StreamK m (A.Array a) -> m (Either ParseError b)-runArrayFold (ChunkFold p) s = fst <$> runArrayParserDBreak p (toStream s)---- | Like 'fold' but also returns the remaining stream.------ /Pre-release/----{-# INLINE runArrayFoldBreak #-}-runArrayFoldBreak :: (MonadIO m, Unbox a) =>- ChunkFold m a b -> StreamK m (A.Array a) -> m (Either ParseError b, StreamK m (A.Array a))-runArrayFoldBreak (ChunkFold p) s =- second fromStream <$> runArrayParserDBreak p (toStream s)--{-# ANN type ParseChunksState Fuse #-}-data ParseChunksState x inpBuf st pst =- ParseChunksInit inpBuf st- | ParseChunksInitBuf inpBuf- | ParseChunksInitLeftOver inpBuf- | ParseChunksStream st inpBuf !pst- | ParseChunksStop inpBuf !pst- | ParseChunksBuf inpBuf st inpBuf !pst- | ParseChunksExtract inpBuf inpBuf !pst- | ParseChunksYield x (ParseChunksState x inpBuf st pst)--{-# INLINE_NORMAL runArrayFoldManyD #-}-runArrayFoldManyD- :: (Monad m, Unbox a)- => ChunkFold m a b- -> D.Stream m (Array a)- -> D.Stream m (Either ParseError b)-runArrayFoldManyD- (ChunkFold (PRD.Parser pstep initial extract)) (D.Stream step state) =-- D.Stream stepOuter (ParseChunksInit [] state)-- where-- {-# INLINE_LATE stepOuter #-}- -- Buffer is empty, get the first element from the stream, initialize the- -- fold and then go to stream processing loop.- stepOuter gst (ParseChunksInit [] st) = do- r <- step (adaptState gst) st- case r of- D.Yield x s -> do- res <- initial- case res of- PRD.IPartial ps ->- return $ D.Skip $ ParseChunksBuf [x] s [] ps- PRD.IDone pb -> do- let next = ParseChunksInit [x] s- return $ D.Skip $ ParseChunksYield (Right pb) next- PRD.IError err -> do- let next = ParseChunksInitLeftOver []- return- $ D.Skip- $ ParseChunksYield (Left (ParseError err)) next- D.Skip s -> return $ D.Skip $ ParseChunksInit [] s- D.Stop -> return D.Stop-- -- Buffer is not empty, go to buffered processing loop- stepOuter _ (ParseChunksInit src st) = do- res <- initial- case res of- PRD.IPartial ps ->- return $ D.Skip $ ParseChunksBuf src st [] ps- PRD.IDone pb ->- let next = ParseChunksInit src st- in return $ D.Skip $ ParseChunksYield (Right pb) next- PRD.IError err -> do- let next = ParseChunksInitLeftOver []- return- $ D.Skip- $ ParseChunksYield (Left (ParseError err)) next-- -- This is a simplified ParseChunksInit- stepOuter _ (ParseChunksInitBuf src) = do- res <- initial- case res of- PRD.IPartial ps ->- return $ D.Skip $ ParseChunksExtract src [] ps- PRD.IDone pb ->- let next = ParseChunksInitBuf src- in return $ D.Skip $ ParseChunksYield (Right pb) next- PRD.IError err -> do- let next = ParseChunksInitLeftOver []- return- $ D.Skip- $ ParseChunksYield (Left (ParseError err)) next-- -- XXX we just discard any leftover input at the end- stepOuter _ (ParseChunksInitLeftOver _) = return D.Stop-- -- Buffer is empty, process elements from the stream- stepOuter gst (ParseChunksStream st backBuf pst) = do- r <- step (adaptState gst) st- case r of- D.Yield x s -> do- pRes <- pstep pst x- case pRes of- PR.Partial 0 pst1 ->- return $ D.Skip $ ParseChunksStream s [] pst1- PR.Partial n pst1 -> do- assert- (n <= sum (map Array.length (x:backBuf)))- (return ())- let src0 = takeArrayListRev n (x:backBuf)- src = Prelude.reverse src0- return $ D.Skip $ ParseChunksBuf src s [] pst1- PR.Continue 0 pst1 ->- return $ D.Skip $ ParseChunksStream s (x:backBuf) pst1- PR.Continue n pst1 -> do- assert- (n <= sum (map Array.length (x:backBuf)))- (return ())- let (src0, buf1) = splitAtArrayListRev n (x:backBuf)- src = Prelude.reverse src0- return $ D.Skip $ ParseChunksBuf src s buf1 pst1- PR.Done 0 b -> do- return $ D.Skip $- ParseChunksYield (Right b) (ParseChunksInit [] s)- PR.Done n b -> do- assert- (n <= sum (map Array.length (x:backBuf)))- (return ())- let src0 = takeArrayListRev n (x:backBuf)- src = Prelude.reverse src0- next = ParseChunksInit src s- return- $ D.Skip- $ ParseChunksYield (Right b) next- PR.Error err -> do- let next = ParseChunksInitLeftOver []- return- $ D.Skip- $ ParseChunksYield (Left (ParseError err)) next-- D.Skip s -> return $ D.Skip $ ParseChunksStream s backBuf pst- D.Stop -> return $ D.Skip $ ParseChunksStop backBuf pst-- -- go back to stream processing mode- stepOuter _ (ParseChunksBuf [] s buf pst) =- return $ D.Skip $ ParseChunksStream s buf pst-- -- buffered processing loop- stepOuter _ (ParseChunksBuf (x:xs) s backBuf pst) = do- pRes <- pstep pst x- case pRes of- PR.Partial 0 pst1 ->- return $ D.Skip $ ParseChunksBuf xs s [] pst1- PR.Partial n pst1 -> do- assert (n <= sum (map Array.length (x:backBuf))) (return ())- let src0 = takeArrayListRev n (x:backBuf)- src = Prelude.reverse src0 ++ xs- return $ D.Skip $ ParseChunksBuf src s [] pst1- PR.Continue 0 pst1 ->- return $ D.Skip $ ParseChunksBuf xs s (x:backBuf) pst1- PR.Continue n pst1 -> do- assert (n <= sum (map Array.length (x:backBuf))) (return ())- let (src0, buf1) = splitAtArrayListRev n (x:backBuf)- src = Prelude.reverse src0 ++ xs- return $ D.Skip $ ParseChunksBuf src s buf1 pst1- PR.Done 0 b ->- return- $ D.Skip- $ ParseChunksYield (Right b) (ParseChunksInit xs s)- PR.Done n b -> do- assert (n <= sum (map Array.length (x:backBuf))) (return ())- let src0 = takeArrayListRev n (x:backBuf)- src = Prelude.reverse src0 ++ xs- return- $ D.Skip- $ ParseChunksYield (Right b) (ParseChunksInit src s)- PR.Error err -> do- let next = ParseChunksInitLeftOver []- return- $ D.Skip- $ ParseChunksYield (Left (ParseError err)) next-- -- This is a simplified ParseChunksBuf- stepOuter _ (ParseChunksExtract [] buf pst) =- return $ D.Skip $ ParseChunksStop buf pst-- stepOuter _ (ParseChunksExtract (x:xs) backBuf pst) = do- pRes <- pstep pst x- case pRes of- PR.Partial 0 pst1 ->- return $ D.Skip $ ParseChunksExtract xs [] pst1- PR.Partial n pst1 -> do- assert (n <= sum (map Array.length (x:backBuf))) (return ())- let src0 = takeArrayListRev n (x:backBuf)- src = Prelude.reverse src0 ++ xs- return $ D.Skip $ ParseChunksExtract src [] pst1- PR.Continue 0 pst1 ->- return $ D.Skip $ ParseChunksExtract xs (x:backBuf) pst1- PR.Continue n pst1 -> do- assert (n <= sum (map Array.length (x:backBuf))) (return ())- let (src0, buf1) = splitAtArrayListRev n (x:backBuf)- src = Prelude.reverse src0 ++ xs- return $ D.Skip $ ParseChunksExtract src buf1 pst1- PR.Done 0 b ->- return- $ D.Skip- $ ParseChunksYield (Right b) (ParseChunksInitBuf xs)- PR.Done n b -> do- assert (n <= sum (map Array.length (x:backBuf))) (return ())- let src0 = takeArrayListRev n (x:backBuf)- src = Prelude.reverse src0 ++ xs- return- $ D.Skip- $ ParseChunksYield (Right b) (ParseChunksInitBuf src)- PR.Error err -> do- let next = ParseChunksInitLeftOver []- return- $ D.Skip- $ ParseChunksYield (Left (ParseError err)) next--- -- This is a simplified ParseChunksExtract- stepOuter _ (ParseChunksStop backBuf pst) = do- pRes <- extract pst- case pRes of- PR.Partial _ _ -> error "runArrayFoldManyD: Partial in extract"- PR.Continue 0 pst1 ->- return $ D.Skip $ ParseChunksStop backBuf pst1- PR.Continue n pst1 -> do- assert (n <= sum (map Array.length backBuf)) (return ())- let (src0, buf1) = splitAtArrayListRev n backBuf- src = Prelude.reverse src0- return $ D.Skip $ ParseChunksExtract src buf1 pst1- PR.Done 0 b ->- return- $ D.Skip- $ ParseChunksYield (Right b) (ParseChunksInitLeftOver [])- PR.Done n b -> do- assert (n <= sum (map Array.length backBuf)) (return ())- let src0 = takeArrayListRev n backBuf- src = Prelude.reverse src0- return- $ D.Skip- $ ParseChunksYield (Right b) (ParseChunksInitBuf src)- PR.Error err -> do- let next = ParseChunksInitLeftOver []- return- $ D.Skip- $ ParseChunksYield (Left (ParseError err)) next-- stepOuter _ (ParseChunksYield a next) = return $ D.Yield a next---- | Apply an 'ChunkFold' repeatedly on an array stream and emit the--- fold outputs in the output stream.------ See "Streamly.Data.Stream.foldMany" for more details.------ /Pre-release/-{-# INLINE runArrayFoldMany #-}-runArrayFoldMany- :: (Monad m, Unbox a)- => ChunkFold m a b- -> StreamK m (Array a)- -> StreamK m (Either ParseError b)-runArrayFoldMany p m = fromStream $ runArrayFoldManyD p (toStream m)
− src/Streamly/Internal/Data/Stream/Common.hs
@@ -1,105 +0,0 @@-{-# OPTIONS_GHC -Wno-orphans #-}---- |--- Module : Streamly.Internal.Data.Stream.Common--- Copyright : (c) 2017 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ Low level functions using StreamK as the intermediate stream type. These--- functions are used in other stream modules to implement their instances.----module Streamly.Internal.Data.Stream.Common- (- -- * Conversion operations- fromList- , toList-- -- * Fold operations- , foldr- , foldl'- , fold-- -- * Zip style operations- , eqBy- , cmpBy- )-where--#include "inline.hs"--import Streamly.Internal.Data.Fold.Type (Fold (..))--import qualified Streamly.Internal.Data.Stream.StreamK.Type as K-import qualified Streamly.Internal.Data.Stream.StreamD.Type as D--import Prelude hiding (foldr, repeat)----------------------------------------------------------------------------------- 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.----{-# INLINE_EARLY fromList #-}-fromList :: Monad m => [a] -> K.StreamK m a-fromList = D.toStreamK . D.fromList-{-# RULES "fromList fallback to StreamK" [1]- forall a. D.toStreamK (D.fromList a) = K.fromFoldable a #-}---- | Convert a stream into a list in the underlying monad.----{-# INLINE toList #-}-toList :: Monad m => K.StreamK m a -> m [a]-toList m = D.toList $ D.fromStreamK m----------------------------------------------------------------------------------- Folds---------------------------------------------------------------------------------{-# INLINE foldrM #-}-foldrM :: Monad m => (a -> m b -> m b) -> m b -> K.StreamK m a -> m b-foldrM step acc m = D.foldrM step acc $ D.fromStreamK m--{-# INLINE foldr #-}-foldr :: Monad m => (a -> b -> b) -> b -> K.StreamK m a -> m b-foldr f z = foldrM (\a b -> f a <$> b) (return z)---- | Strict left associative fold.----{-# INLINE foldl' #-}-foldl' ::- Monad m => (b -> a -> b) -> b -> K.StreamK m a -> m b-foldl' step begin m = D.foldl' step begin $ D.fromStreamK m---{-# INLINE fold #-}-fold :: Monad m => Fold m a b -> K.StreamK m a -> m b-fold fld m = D.fold fld $ D.fromStreamK m----------------------------------------------------------------------------------- Comparison----------------------------------------------------------------------------------- | Compare two streams for equality----{-# INLINE eqBy #-}-eqBy :: Monad m =>- (a -> b -> Bool) -> K.StreamK m a -> K.StreamK m b -> m Bool-eqBy f m1 m2 = D.eqBy f (D.fromStreamK m1) (D.fromStreamK m2)---- | Compare two streams----{-# INLINE cmpBy #-}-cmpBy- :: Monad m- => (a -> b -> Ordering) -> K.StreamK m a -> K.StreamK m b -> m Ordering-cmpBy f m1 m2 = D.cmpBy f (D.fromStreamK m1) (D.fromStreamK m2)
+ src/Streamly/Internal/Data/Stream/Container.hs view
@@ -0,0 +1,308 @@+{-# LANGUAGE CPP #-}+-- |+-- Module : Streamly.Internal.Data.Stream.Container+-- Copyright : (c) 2019 Composewell Technologies+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- Stream operations that require transformers or containers like Set or Map.++module Streamly.Internal.Data.Stream.Container+ (+ -- * Deduplication+ ordNub++ -- * Joins+ , leftJoin+ , outerJoin++ -- * Ord Joins+ , innerOrdJoin+ , leftOrdJoin+ , outerOrdJoin+ )+where++#include "inline.hs"++import Control.Monad.IO.Class (MonadIO)+import Control.Monad.Trans.State.Strict (get, put)+import Data.Function ((&))+import Data.Maybe (isJust)+import Streamly.Internal.Data.Stream.Step (Step(..))+import Streamly.Internal.Data.Stream.Type (Stream(..), Nested(..))++import qualified Data.Map.Strict as Map+import qualified Data.Set as Set+import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Internal.Data.Array.Generic as Array+import qualified Streamly.Internal.Data.MutArray.Type as MA+import qualified Streamly.Internal.Data.Stream.Type as Stream+import qualified Streamly.Internal.Data.Stream.Generate as Stream+import qualified Streamly.Internal.Data.Stream.Transform as Stream+import qualified Streamly.Internal.Data.Stream.Transformer as Stream++#include "DocTestDataStream.hs"++-- | @nub@ specialized to 'Ord' types for better performance. Returns a+-- subsequence of the stream removing any duplicate elements.+--+-- The memory used is proportional to the number of unique elements in the+-- stream. One way to limit the memory is to use @take@ on the resulting+-- stream to limit the unique elements in the stream.+{-# INLINE_NORMAL ordNub #-}+ordNub :: (Monad m, Ord a) => Stream m a -> Stream m a+ordNub (Stream step1 state1) = Stream step (Set.empty, state1)++ where++ step gst (set, st) = do+ r <- step1 gst st+ return+ $ case r of+ Yield x s ->+ if Set.member x set+ then Skip (set, s)+ else Yield x (Set.insert x set, s)+ Skip s -> Skip (set, s)+ Stop -> Stop++-- XXX Generate error if a duplicate insertion is attempted?+toMap :: (Monad m, Ord k) => Stream m (k, v) -> m (Map.Map k v)+toMap =+ let f = Fold.foldl' (\kv (k, b) -> Map.insert k b kv) Map.empty+ in Stream.fold f++-- If the second stream is too big it can be partitioned based on hashes and+-- then we can process one parition at a time.+--+-- XXX An IntMap may be faster when the keys are Int.+-- XXX Use hashmap instead of map?++-- | 'innerJoin' specialized to 'Ord' types for better performance.+--+-- If the input streams have duplicate keys, the behavior is undefined.+--+-- For space efficiency use the smaller stream as the second stream.+--+-- Space: O(n)+--+-- Time: O(m + n)+--+-- /Pre-release/+{-# INLINE innerOrdJoin #-}+innerOrdJoin :: (Monad m, Ord k) =>+ Stream m (k, a) -> Stream m (k, b) -> Stream m (k, a, b)+innerOrdJoin s1 s2 =+ Stream.concatEffect $ do+ km <- toMap s2+ pure $ Stream.mapMaybe (joinAB km) s1++ where++ joinAB kvm (k, a) =+ case k `Map.lookup` kvm of+ Just b -> Just (k, a, b)+ Nothing -> Nothing++-- XXX We can do this concurrently.+-- XXX Check performance of StreamD vs StreamK+-- XXX If the second stream is sorted and passed as an Array or a seek capable+-- stream then we could use binary search if we have an Ord instance or+-- Ordering returning function. The time complexity would then become (m x log+-- n).++-- | Like 'innerJoin' but emits @(a, Just b)@ whenever a and b are equal, for+-- those @a@'s that are not equal to any @b@ emits @(a, Nothing)@.+--+-- This is a generalization of 'innerJoin' to include all elements from the+-- left stream and not just those which have an equal in the right stream. This+-- is not a commutative operation, the order of the stream arguments matters.+--+-- All the caveats mentioned in 'innerJoin' apply here as well. Right join is+-- not provided because it is just a flipped left join:+--+-- >>> rightJoin eq = flip (Stream.leftJoin eq)+--+-- Space: O(n) assuming the second stream is cached in memory.+--+-- Time: O(m x n)+--+-- /Unimplemented/+{-# INLINE leftJoin #-}+leftJoin :: Monad m =>+ (a -> b -> Bool) -> Stream m a -> Stream m b -> Stream m (a, Maybe b)+leftJoin eq s1 s2 = Stream.evalStateT (return False) $ unNested $ do+ a <- Nested (Stream.liftInner s1)+ -- XXX should we use StreamD monad here?+ -- XXX Is there a better way to perform some action at the end of a loop+ -- iteration?+ Nested (Stream.fromEffect $ put False)+ let final = Stream.concatEffect $ do+ r <- get+ if r+ then pure Stream.nil+ else pure (Stream.fromPure Nothing)+ b <- Nested (fmap Just (Stream.liftInner s2) `Stream.append` final)+ case b of+ Just b1 ->+ if a `eq` b1+ then do+ Nested (Stream.fromEffect $ put True)+ return (a, Just b1)+ else Nested Stream.nil+ Nothing -> return (a, Nothing)++-- | 'leftJoin' specialized to 'Ord' types for better performance.+--+-- Space: O(n)+--+-- Time: O(m + n)+--+-- /Pre-release/+{-# INLINE leftOrdJoin #-}+leftOrdJoin :: (Ord k, Monad m) =>+ Stream m (k, a) -> Stream m (k, b) -> Stream m (k, a, Maybe b)+leftOrdJoin s1 s2 =+ Stream.concatEffect $ do+ km <- toMap s2+ return $ fmap (joinAB km) s1++ where++ joinAB km (k, a) =+ case k `Map.lookup` km of+ Just b -> (k, a, Just b)+ Nothing -> (k, a, Nothing)++-- XXX We can do this concurrently.+-- XXX Check performance of StreamD vs StreamK cross operation.++-- | Like 'leftJoin' but emits a @(Just a, Just b)@. Like 'leftJoin', for those+-- @a@'s that are not equal to any @b@ emit @(Just a, Nothing)@, but+-- additionally, for those @b@'s that are not equal to any @a@ emit @(Nothing,+-- Just b)@.+--+-- This is a generalization of left join to include all the elements from the+-- right stream as well, in other words it is a combination of left and right+-- joins. This is a commutative operation. The order of stream arguments can be+-- changed without affecting results, except for the ordering of elements in+-- the resulting tuple.+--+-- For space efficiency use the smaller stream as the second stream.+--+-- Space: O(n)+--+-- Time: O(m x n)+--+-- /Pre-release/+{-# INLINE outerJoin #-}+outerJoin :: MonadIO m =>+ (a -> b -> Bool)+ -> Stream m a+ -> Stream m b+ -> Stream m (Maybe a, Maybe b)+outerJoin eq s1 s2 =+ Stream.concatEffect $ do+ inputArr <- Array.fromStream s2+ let len = Array.length inputArr+ foundArr <-+ Stream.fold+ (MA.createOf len)+ (Stream.fromList (Prelude.replicate len False))+ return $ go inputArr foundArr `Stream.append` leftOver inputArr foundArr++ where++ leftOver inputArr foundArr =+ let stream1 = Array.read inputArr+ stream2 = Stream.unfold MA.reader foundArr+ in Stream.filter+ isJust+ ( Stream.zipWith (\x y ->+ if y+ then Nothing+ else Just (Nothing, Just x)+ ) stream1 stream2+ ) & Stream.catMaybes++ evalState = Stream.evalStateT (return False) . unNested++ go inputArr foundArr = evalState $ do+ a <- Nested (Stream.liftInner s1)+ -- XXX should we use StreamD monad here?+ -- XXX Is there a better way to perform some action at the end of a loop+ -- iteration?+ Nested (Stream.fromEffect $ put False)+ let final = Stream.concatEffect $ do+ r <- get+ if r+ then pure Stream.nil+ else pure (Stream.fromPure Nothing)+ (i, b) <-+ let stream = Array.read inputArr+ in Nested+ (Stream.indexed $ fmap Just (Stream.liftInner stream) `Stream.append` final)++ case b of+ Just b1 ->+ if a `eq` b1+ then do+ Nested (Stream.fromEffect $ put True)+ MA.putIndex i foundArr True+ return (Just a, Just b1)+ else Nested Stream.nil+ Nothing -> return (Just a, Nothing)++-- Put the b's that have been paired, in another hash or mutate the hash to set+-- a flag. At the end go through @Stream m b@ and find those that are not in that+-- hash to return (Nothing, b).++-- | 'outerJoin' specialized to 'Ord' types for better performance.+--+-- Space: O(m + n)+--+-- Time: O(m + n)+--+-- /Pre-release/+{-# INLINE outerOrdJoin #-}+outerOrdJoin ::+ (Ord k, MonadIO m) =>+ Stream m (k, a) -> Stream m (k, b) -> Stream m (k, Maybe a, Maybe b)+outerOrdJoin s1 s2 =+ Stream.concatEffect $ do+ km1 <- kvFold s1+ km2 <- kvFold s2++ -- XXX Not sure if toList/fromList would fuse optimally. We may have to+ -- create a fused Map.toStream function.+ let res1 = fmap (joinAB km2)+ $ Stream.fromList $ Map.toList km1+ where+ joinAB km (k, a) =+ case k `Map.lookup` km of+ Just b -> (k, Just a, Just b)+ Nothing -> (k, Just a, Nothing)++ -- XXX We can take advantage of the lookups in the first pass above to+ -- reduce the number of lookups in this pass. If we keep mutable cells+ -- in the second Map, we can flag it in the first pass and not do any+ -- lookup in the second pass if it is flagged.+ let res2 = Stream.mapMaybe (joinAB km1)+ $ Stream.fromList $ Map.toList km2+ where+ joinAB km (k, b) =+ case k `Map.lookup` km of+ Just _ -> Nothing+ Nothing -> Just (k, Nothing, Just b)++ return $ Stream.append res1 res2++ where++ -- XXX Generate error if a duplicate insertion is attempted?+ kvFold =+ let f = Fold.foldl' (\kv (k, b) -> Map.insert k b kv) Map.empty+ in Stream.fold f
− src/Streamly/Internal/Data/Stream/Cross.hs
@@ -1,143 +0,0 @@-{-# LANGUAGE UndecidableInstances #-}---- |--- Module : Streamly.Internal.Data.Stream.Cross--- Copyright : (c) 2017 Composewell Technologies------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC----module Streamly.Internal.Data.Stream.Cross- (- CrossStream (..)- )-where--import Control.Monad.Catch (MonadThrow, throwM)-import Control.Monad.Trans.Class (MonadTrans(lift))-import Control.Applicative (liftA2)-import Control.Monad.IO.Class (MonadIO(..))-import Data.Functor.Identity (Identity(..))-import GHC.Exts (IsList(..), IsString(..))-import Streamly.Internal.Data.Stream.Type (Stream)--import qualified Streamly.Internal.Data.Stream.Type as Stream-import qualified Streamly.Internal.Data.Stream.StreamK.Type as K---- $setup--- >>> import Streamly.Internal.Data.Stream.Cross (CrossStream(..))--- >>> import qualified Streamly.Data.Fold as Fold--- >>> import qualified Streamly.Data.Stream as Stream----------------------------------------------------------------------------------- Stream with a cross product style monad instance----------------------------------------------------------------------------------- | A newtype wrapper for the 'Stream' type with a cross product style monad--- instance.------ Semigroup instance appends two streams.------ A 'Monad' bind behaves like a @for@ loop:------ >>> :{--- Stream.fold Fold.toList $ unCrossStream $ do--- x <- CrossStream (Stream.fromList [1,2]) -- foreach x in stream--- return x--- :}--- [1,2]------ Nested monad binds behave like nested @for@ loops:------ >>> :{--- Stream.fold Fold.toList $ unCrossStream $ do--- x <- CrossStream (Stream.fromList [1,2]) -- foreach x in stream--- y <- CrossStream (Stream.fromList [3,4]) -- foreach y in stream--- return (x, y)--- :}--- [(1,3),(1,4),(2,3),(2,4)]----newtype CrossStream m a = CrossStream {unCrossStream :: Stream m a}- deriving (Functor, Semigroup, Monoid, Foldable)---- Pure (Identity monad) stream instances-deriving instance Traversable (CrossStream Identity)-deriving instance IsList (CrossStream Identity a)-deriving instance (a ~ Char) => IsString (CrossStream Identity a)-deriving instance Eq a => Eq (CrossStream Identity a)-deriving instance Ord a => Ord (CrossStream Identity a)-deriving instance Show a => Show (CrossStream Identity a)-deriving instance Read a => Read (CrossStream Identity a)----------------------------------------------------------------------------------- Applicative----------------------------------------------------------------------------------- Note: we need to define all the typeclass operations because we want to--- INLINE them.-instance Monad m => Applicative (CrossStream m) where- {-# INLINE pure #-}- pure x = CrossStream (Stream.fromPure x)-- {-# INLINE (<*>) #-}- (CrossStream s1) <*> (CrossStream s2) =- CrossStream (Stream.crossApply s1 s2)- -- (<*>) = K.crossApply-- {-# INLINE liftA2 #-}- liftA2 f x = (<*>) (fmap f x)-- {-# INLINE (*>) #-}- (CrossStream s1) *> (CrossStream s2) =- CrossStream (Stream.crossApplySnd s1 s2)- -- (*>) = K.crossApplySnd-- {-# INLINE (<*) #-}- (CrossStream s1) <* (CrossStream s2) =- CrossStream (Stream.crossApplyFst s1 s2)- -- (<*) = K.crossApplyFst----------------------------------------------------------------------------------- Monad---------------------------------------------------------------------------------instance Monad m => Monad (CrossStream m) where- return = pure-- -- Benchmarks better with StreamD bind and pure:- -- toList, filterAllout, *>, *<, >> (~2x)- --- -- pure = Stream . D.fromStreamD . D.fromPure- -- m >>= f = D.fromStreamD $ D.concatMap (D.toStreamD . f) (D.toStreamD m)-- -- Benchmarks better with CPS bind and pure:- -- Prime sieve (25x)- -- n binds, breakAfterSome, filterAllIn, state transformer (~2x)- --- {-# INLINE (>>=) #-}- (>>=) (CrossStream m) f =- CrossStream- (Stream.fromStreamK- $ K.bindWith- K.append- (Stream.toStreamK m)- (Stream.toStreamK . unCrossStream . f))-- {-# INLINE (>>) #-}- (>>) = (*>)----------------------------------------------------------------------------------- Transformers---------------------------------------------------------------------------------instance (MonadIO m) => MonadIO (CrossStream m) where- liftIO x = CrossStream (Stream.fromEffect $ liftIO x)--instance MonadTrans CrossStream where- {-# INLINE lift #-}- lift x = CrossStream (Stream.fromEffect x)--instance (MonadThrow m) => MonadThrow (CrossStream m) where- throwM = lift . throwM
src/Streamly/Internal/Data/Stream/Eliminate.hs view
@@ -1,233 +1,138 @@+{-# LANGUAGE CPP #-} -- | -- Module : Streamly.Internal.Data.Stream.Eliminate--- Copyright : (c) 2017 Composewell Technologies+-- Copyright : (c) 2018 Composewell Technologies+-- (c) Roman Leshchinskiy 2008-2010 -- License : BSD-3-Clause -- Maintainer : streamly@composewell.com -- Stability : experimental -- Portability : GHC------ This module contains functions ending in the shape:------ @--- Stream m a -> m b--- @------ We call them stream folding functions, they reduce a stream @Stream m a@ to--- a monadic value @m b@. +-- A few functions in this module have been adapted from the vector package+-- (c) Roman Leshchinskiy.+-- module Streamly.Internal.Data.Stream.Eliminate (- -- * Running Examples- -- $setup-- -- * Running a 'Fold'- -- See "Streamly.Internal.Data.Fold".- fold- , foldBreak- , foldBreak2- , foldEither- , foldEither2- , foldConcat-- -- * Builders- , foldAdd- , foldAddLazy-- -- * Running a 'Parser'- -- "Streamly.Internal.Data.Parser".- , parse- --, parseK- , parseD- --, parseBreak- , parseBreakD+ -- * Running a Parser+ parse+ , parsePos+ , parseBreak+ , parseBreakPos - -- * Stream Deconstruction- -- | foldr and foldl do not provide the remaining stream. 'uncons' is more- -- general, as it can be used to implement those as well. It allows to use- -- the stream one element at a time, and we have the remaining stream all- -- the time.+ -- * Deconstruction , uncons- , init -- * Right Folds- , foldrM- , foldr+ , foldr1 - -- * Left Folds- -- Lazy left folds are useful only for reversing the stream- , foldlS+ -- * Specific Fold Functions+ , mapM_ -- Map and Fold+ , null+ , init+ , tail+ , last+ , elem+ , notElem+ , all+ , any+ , maximum+ , maximumBy+ , minimum+ , minimumBy+ , lookup+ , findM+ , find+ , (!!)+ , the - -- * Multi-Stream folds- -- Full equivalence- , eqBy- , cmpBy+ -- * To containers+ , toListRev - -- finding subsequences+ -- * Multi-Stream Folds+ -- | These should probably be expressed using parsers. , isPrefixOf , isInfixOf , isSuffixOf+ , isSuffixOfUnbox , isSubsequenceOf-- -- trimming sequences , stripPrefix- -- , stripInfix , stripSuffix+ , stripSuffixUnbox++ -- * Deprecated+ , parseD+ , parseBreakD ) where #include "inline.hs"+#include "deprecation.h" import Control.Monad.IO.Class (MonadIO(..))-import Foreign.Storable (Storable)-import Streamly.Internal.Data.Parser (Parser (..), ParseError (..))-import Streamly.Internal.Data.Unboxed (Unbox)-import Streamly.Internal.Data.Stream.Type (Stream)+import GHC.Types (SPEC(..))+import Streamly.Internal.Data.Parser (ParseError(..), ParseErrorPos(..))+import Streamly.Internal.Data.SVar.Type (adaptState, defState)+import Streamly.Internal.Data.Unbox (Unbox) +import Streamly.Internal.Data.Maybe.Strict (Maybe'(..))+ import qualified Streamly.Internal.Data.Array.Type as Array import qualified Streamly.Internal.Data.Fold as Fold-import qualified Streamly.Internal.Data.Parser.ParserD as PRD-import qualified Streamly.Internal.Data.Parser.ParserK.Type as PRK-import qualified Streamly.Internal.Data.Stream.StreamD as D-import qualified Streamly.Internal.Data.Stream.StreamK.Type as K-import qualified Streamly.Internal.Data.Stream.StreamK as K--import Streamly.Internal.Data.Stream.Bottom-import Streamly.Internal.Data.Stream.Type hiding (Stream)+import qualified Streamly.Internal.Data.Parser as PR+import qualified Streamly.Internal.Data.ParserDrivers as Drivers+import qualified Streamly.Internal.Data.Stream.Nesting as Nesting+import qualified Streamly.Internal.Data.Stream.Transform as StreamD -import Prelude hiding (foldr, init, reverse)+import Prelude hiding+ ( Foldable(..), all, any, head, last, lookup, mapM, mapM_+ , notElem, splitAt, init, tail, (!!))+import Streamly.Internal.Data.Stream.Type hiding (splitAt) --- $setup--- >>> :m--- >>> import Streamly.Internal.Data.Stream (Stream)--- >>> import qualified Streamly.Internal.Data.Stream as Stream--- >>> import qualified Streamly.Internal.Data.Parser as Parser--- >>> import qualified Streamly.Internal.Data.Fold as Fold--- >>> import qualified Streamly.Internal.Data.Unfold as Unfold+#include "DocTestDataStream.hs" --------------------------------------------------------------------------------- Deconstruction+-- Elimination by Folds ------------------------------------------------------------------------------ --- | 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.------ Properties:------ >>> Nothing <- Stream.uncons Stream.nil--- >>> Just ("a", t) <- Stream.uncons (Stream.cons "a" Stream.nil)------ This can be used to consume the stream in an imperative manner one element--- at a time, 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.------ All the folds in this module can be expressed in terms of 'uncons', however,--- this is generally less efficient than specific folds because it takes apart--- the stream one element at a time, therefore, does not take adavantage of--- stream fusion.------ 'foldBreak' is a more general way of consuming a stream piecemeal.------ >>> :{--- uncons xs = do--- r <- Stream.foldBreak Fold.one xs--- return $ case r of--- (Nothing, _) -> Nothing--- (Just h, t) -> Just (h, t)--- :}------ /CPS/----{-# INLINE uncons #-}-uncons :: Monad m => Stream m a -> m (Maybe (a, Stream m a))-uncons m = fmap (fmap (fmap fromStreamK)) $ K.uncons (toStreamK m)---- | Extract all but the last element of the stream, if any.------ Note: This will end up buffering the entire stream.------ /Pre-release/-{-# INLINE init #-}-init :: Monad m => Stream m a -> m (Maybe (Stream m a))-init m = fmap (fmap fromStreamK) $ K.init $ toStreamK m- ------------------------------------------------------------------------------ -- Right Folds ------------------------------------------------------------------------------ --- | 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 = Stream.fromList (2:4:5:undefined)--- >>> step x xs = if odd x then return True else xs--- >>> Stream.foldrM step (return False) s--- True----{-# INLINE foldrM #-}-foldrM :: Monad m => (a -> m b -> m b) -> m b -> Stream m a -> m b-foldrM step acc m = D.foldrM step acc $ toStreamD m---- | 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.------ >>> foldr f z = Stream.foldrM (\a b -> f a <$> b) (return z)----{-# INLINE foldr #-}-foldr :: Monad m => (a -> b -> b) -> b -> Stream m a -> m b-foldr f z = foldrM (\a b -> f a <$> b) (return z)+{-# 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+-- Parsers ------------------------------------------------------------------------------ --- | Lazy left fold to a stream.-{-# INLINE foldlS #-}-foldlS ::- (Stream m b -> a -> Stream m b) -> Stream m b -> Stream m a -> Stream m b-foldlS f z =- fromStreamK- . K.foldlS- (\xs x -> toStreamK $ f (fromStreamK xs) x)- (toStreamK z)- . toStreamK----------------------------------------------------------------------------------- Running a Parser-------------------------------------------------------------------------------+-- XXX It may be a good idea to use constant sized chunks for backtracking. We+-- can take a byte stream but when we have to backtrack we create constant+-- sized chunks. We maintain one forward list and one backward list of constant+-- sized chunks, and a last backtracking offset. That way we just need lists of+-- contents and no need to maintain start/end pointers for individual arrays,+-- reducing bookkeeping work. --- | Parse a stream using the supplied ParserD 'PRD.Parser'.------ /Internal/+-- | Parse a stream using the supplied 'Parser'. ---{-# INLINE_NORMAL parseD #-}-parseD :: Monad m => PRD.Parser a m b -> Stream m a -> m (Either ParseError b)-parseD p = D.parseD p . toStreamD+{-# INLINE parseBreak #-}+parseBreak, parseBreakD :: Monad m =>+ PR.Parser a m b -> Stream m a -> m (Either ParseError b, Stream m a)+parseBreak = Drivers.parseBreak --- XXX Drive directly as parserK rather than converting to parserD first.+RENAME(parseBreakD,parseBreak) --- | Parse a stream using the supplied ParserK 'PRK.Parser'.+-- | Like 'parseBreak' but includes stream position information in the error+-- messages. ----- /Internal/---{-# INLINE parseK #-}---parseK :: Monad m => PRK.Parser a m b -> Stream m a -> m (Either ParseError b)---parseK = parse+{-# INLINE parseBreakPos #-}+parseBreakPos :: Monad m =>+ PR.Parser a m b -> Stream m a -> m (Either ParseErrorPos b, Stream m a)+parseBreakPos = Drivers.parseBreakPos -- | Parse a stream using the supplied 'Parser'. --@@ -242,25 +147,330 @@ -- Note: @parse p@ is not the same as @head . parseMany p@ on an empty stream. -- {-# INLINE [3] parse #-}-parse :: Monad m => Parser a m b -> Stream m a -> m (Either ParseError b)-parse = parseD+parse, parseD :: Monad m => PR.Parser a m b -> Stream m a -> m (Either ParseError b)+parse parser strm = do+ (b, _) <- parseBreak parser strm+ return b -{-# INLINE_NORMAL parseBreakD #-}-parseBreakD :: Monad m =>- PRD.Parser a m b -> Stream m a -> m (Either ParseError b, Stream m a)-parseBreakD parser strm = do- (b, strmD) <- D.parseBreakD parser (toStreamD strm)- return $! (b, fromStreamD strmD)+RENAME(parseD,parse) --- | Parse a stream using the supplied 'Parser'.+-- | Like 'parse' but includes stream position information in the error+-- messages. ----- /CPS/+-- >>> Stream.parsePos (Parser.takeEQ 2 Fold.drain) (Stream.fromList [1])+-- Left (ParseErrorPos 1 "takeEQ: Expecting exactly 2 elements, input terminated on 1") -----{-# INLINE parseBreak #-}---parseBreak :: Monad m => Parser a m b -> Stream m a -> m (Either ParseError b, Stream m a)---parseBreak p strm = D.parseBreak p strm+{-# INLINE [3] parsePos #-}+parsePos :: Monad m => PR.Parser a m b -> Stream m a -> m (Either ParseErrorPos b)+parsePos parser strm = do+ (b, _) <- parseBreakPos parser strm+ return b ------------------------------------------------------------------------------+-- Specialized Folds+------------------------------------------------------------------------------++-- benchmark after dropping 1 item from stream or using unfolds+{-# INLINE_NORMAL null #-}+null :: Monad m => Stream m a -> m Bool+#ifdef USE_FOLDS_EVERYWHERE+null = fold Fold.null+#else+null = foldrM (\_ _ -> return False) (return True)+#endif++{-# INLINE_NORMAL init #-}+init :: Monad m => Stream m a -> m (Maybe (Stream m a))+init stream = do+ r <- uncons stream+ case r of+ Nothing -> return Nothing+ Just (h, Stream step1 state1) ->+ return $ Just $ Stream step (h, state1)++ where++ step gst (a, s1) = do+ res <- step1 (adaptState gst) s1+ return $+ case res of+ Yield x s -> Yield a (x, s)+ Skip s -> Skip (a, s)+ Stop -> Stop++-- | Same as:+--+-- >>> tail = fmap (fmap snd) . Stream.uncons+--+-- 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 = fmap (fmap snd) . uncons+{-+tail (UnStream step state) = go SPEC state+ where+ go !_ st = do+ r <- step defState st+ case r of+ Yield _ s -> return (Just $ Stream step s)+ Skip s -> go SPEC s+ Stop -> return Nothing+-}++-- XXX will it fuse? need custom impl?+{-# INLINE_NORMAL last #-}+last :: Monad m => Stream m a -> m (Maybe a)+#ifdef USE_FOLDS_EVERYWHERE+last = fold Fold.last+#else+last = foldl' (\_ y -> Just y) Nothing+#endif++-- XXX Use the foldrM based impl instead+{-# INLINE_NORMAL elem #-}+elem :: (Monad m, Eq a) => a -> Stream m a -> m Bool+#ifdef USE_FOLDS_EVERYWHERE+elem e = fold (Fold.elem e)+#else+-- elem e m = foldrM (\x xs -> if x == e then return True else xs) (return False) m+elem e (Stream step state) = go SPEC state+ where+ go !_ st = do+ r <- step defState st+ case r of+ Yield x s+ | x == e -> return True+ | otherwise -> go SPEC s+ Skip s -> go SPEC s+ Stop -> return False+#endif++{-# INLINE_NORMAL notElem #-}+notElem :: (Monad m, Eq a) => a -> Stream m a -> m Bool+notElem e s = fmap not (e `elem` s)++{-# INLINE_NORMAL all #-}+all :: Monad m => (a -> Bool) -> Stream m a -> m Bool+#ifdef USE_FOLDS_EVERYWHERE+all p = fold (Fold.all p)+#else+-- all p m = foldrM (\x xs -> if p x then xs else return False) (return True) m+all p (Stream step state) = go SPEC state+ where+ go !_ st = do+ r <- step defState st+ case r of+ Yield x s+ | p x -> go SPEC s+ | otherwise -> return False+ Skip s -> go SPEC s+ Stop -> return True+#endif++{-# INLINE_NORMAL any #-}+any :: Monad m => (a -> Bool) -> Stream m a -> m Bool+#ifdef USE_FOLDS_EVERYWHERE+any p = fold (Fold.any p)+#else+-- any p m = foldrM (\x xs -> if p x then return True else xs) (return False) m+any p (Stream step state) = go SPEC state+ where+ go !_ st = do+ r <- step defState st+ case r of+ Yield x s+ | p x -> return True+ | otherwise -> go SPEC s+ Skip s -> go SPEC s+ Stop -> return False+#endif++{-# INLINE_NORMAL maximum #-}+maximum :: (Monad m, Ord a) => Stream m a -> m (Maybe a)+#ifdef USE_FOLDS_EVERYWHERE+maximum = fold Fold.maximum+#else+maximum (Stream step state) = go SPEC Nothing' state+ where+ go !_ Nothing' st = do+ r <- step defState st+ case r of+ Yield x s -> go SPEC (Just' x) s+ Skip s -> go SPEC Nothing' s+ Stop -> return Nothing+ go !_ (Just' acc) st = do+ r <- step defState st+ case r of+ Yield x s+ | acc <= x -> go SPEC (Just' x) s+ | otherwise -> go SPEC (Just' acc) s+ Skip s -> go SPEC (Just' acc) s+ Stop -> return (Just acc)+#endif++{-# INLINE_NORMAL maximumBy #-}+maximumBy :: Monad m => (a -> a -> Ordering) -> Stream m a -> m (Maybe a)+#ifdef USE_FOLDS_EVERYWHERE+maximumBy cmp = fold (Fold.maximumBy cmp)+#else+maximumBy cmp (Stream step state) = go SPEC Nothing' state+ where+ go !_ Nothing' st = do+ r <- step defState st+ case r of+ Yield x s -> go SPEC (Just' x) s+ Skip s -> go SPEC 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 SPEC (Just' acc) s+ _ -> go SPEC (Just' x) s+ Skip s -> go SPEC (Just' acc) s+ Stop -> return (Just acc)+#endif++{-# INLINE_NORMAL minimum #-}+minimum :: (Monad m, Ord a) => Stream m a -> m (Maybe a)+#ifdef USE_FOLDS_EVERYWHERE+minimum = fold Fold.minimum+#else+minimum (Stream step state) = go SPEC Nothing' state++ where++ go !_ Nothing' st = do+ r <- step defState st+ case r of+ Yield x s -> go SPEC (Just' x) s+ Skip s -> go SPEC Nothing' s+ Stop -> return Nothing+ go !_ (Just' acc) st = do+ r <- step defState st+ case r of+ Yield x s+ | acc <= x -> go SPEC (Just' acc) s+ | otherwise -> go SPEC (Just' x) s+ Skip s -> go SPEC (Just' acc) s+ Stop -> return (Just acc)+#endif++{-# INLINE_NORMAL minimumBy #-}+minimumBy :: Monad m => (a -> a -> Ordering) -> Stream m a -> m (Maybe a)+#ifdef USE_FOLDS_EVERYWHERE+minimumBy cmp = fold (Fold.minimumBy cmp)+#else+minimumBy cmp (Stream step state) = go SPEC Nothing' state++ where++ go !_ Nothing' st = do+ r <- step defState st+ case r of+ Yield x s -> go SPEC (Just' x) s+ Skip s -> go SPEC 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 SPEC (Just' x) s+ _ -> go SPEC (Just' acc) s+ Skip s -> go SPEC (Just' acc) s+ Stop -> return (Just acc)+#endif++{-# INLINE_NORMAL (!!) #-}+(!!) :: (Monad m) => Stream m a -> Int -> m (Maybe a)+#ifdef USE_FOLDS_EVERYWHERE+stream !! i = fold (Fold.index i) stream+#else+(Stream step state) !! i = go SPEC 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 SPEC (n - 1) s+ Skip s -> go SPEC n s+ Stop -> return Nothing+#endif++{-# INLINE_NORMAL lookup #-}+lookup :: (Monad m, Eq a) => a -> Stream m (a, b) -> m (Maybe b)+#ifdef USE_FOLDS_EVERYWHERE+lookup e = fold (Fold.lookup e)+#else+lookup e = foldrM (\(a, b) xs -> if e == a then return (Just b) else xs)+ (return Nothing)+#endif++{-# INLINE_NORMAL findM #-}+findM :: Monad m => (a -> m Bool) -> Stream m a -> m (Maybe a)+#ifdef USE_FOLDS_EVERYWHERE+findM p = fold (Fold.findM p)+#else+findM p = foldrM (\x xs -> p x >>= \r -> if r then return (Just x) else xs)+ (return Nothing)+#endif++{-# INLINE find #-}+find :: Monad m => (a -> Bool) -> Stream m a -> m (Maybe a)+find p = findM (return . p)++{-# INLINE toListRev #-}+toListRev :: Monad m => Stream m a -> m [a]+#ifdef USE_FOLDS_EVERYWHERE+toListRev = fold Fold.toListRev+#else+toListRev = foldl' (flip (:)) []+#endif++------------------------------------------------------------------------------+-- Transformation comprehensions+------------------------------------------------------------------------------++{-# INLINE_NORMAL the #-}+the :: (Eq a, Monad m) => Stream m a -> m (Maybe a)+#ifdef USE_FOLDS_EVERYWHERE+the = fold Fold.the+#else+the (Stream step state) = go SPEC state+ where+ go !_ st = do+ r <- step defState st+ case r of+ Yield x s -> go' SPEC x s+ Skip s -> go SPEC s+ Stop -> return Nothing+ go' !_ n st = do+ r <- step defState st+ case r of+ Yield x s | x == n -> go' SPEC n s+ | otherwise -> return Nothing+ Skip s -> go' SPEC n s+ Stop -> return (Just n)+#endif++------------------------------------------------------------------------------+-- 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 ()+#ifdef USE_FOLDS_EVERYWHERE+mapM_ f = fold (Fold.drainBy f)+#else+mapM_ m = drain . mapM m+#endif++------------------------------------------------------------------------------ -- Multi-stream folds ------------------------------------------------------------------------------ @@ -270,10 +480,91 @@ -- >>> Stream.isPrefixOf (Stream.fromList "hello") (Stream.fromList "hello" :: Stream IO Char) -- True ---{-# INLINE isPrefixOf #-}+{-# INLINE_NORMAL isPrefixOf #-} isPrefixOf :: (Monad m, Eq a) => Stream m a -> Stream m a -> m Bool-isPrefixOf m1 m2 = D.isPrefixOf (toStreamD m1) (toStreamD m2)+isPrefixOf (Stream stepa ta) (Stream stepb tb) = go SPEC Nothing' ta tb + where++ go !_ Nothing' sa sb = do+ r <- stepa defState sa+ case r of+ Yield x sa' -> go SPEC (Just' x) sa' sb+ Skip sa' -> go SPEC Nothing' sa' sb+ Stop -> return True++ go !_ (Just' x) sa sb = do+ r <- stepb defState sb+ case r of+ Yield y sb' ->+ if x == y+ then go SPEC Nothing' sa sb'+ else return False+ Skip sb' -> go SPEC (Just' x) sa sb'+ Stop -> return False++-- | 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.+--+-- >>> Stream.isSubsequenceOf (Stream.fromList "hlo") (Stream.fromList "hello" :: Stream IO Char)+-- True+--+{-# INLINE_NORMAL isSubsequenceOf #-}+isSubsequenceOf :: (Monad m, Eq a) => Stream m a -> Stream m a -> m Bool+isSubsequenceOf (Stream stepa ta) (Stream stepb tb) = go SPEC Nothing' ta tb++ where++ go !_ Nothing' sa sb = do+ r <- stepa defState sa+ case r of+ Yield x sa' -> go SPEC (Just' x) sa' sb+ Skip sa' -> go SPEC Nothing' sa' sb+ Stop -> return True++ go !_ (Just' x) sa sb = do+ r <- stepb defState sb+ case r of+ Yield y sb' ->+ if x == y+ then go SPEC Nothing' sa sb'+ else go SPEC (Just' x) sa sb'+ Skip sb' -> go SPEC (Just' x) sa sb'+ Stop -> return False++-- | @stripPrefix prefix input@ strips the @prefix@ stream from the @input@+-- stream if it is a prefix of input. Returns 'Nothing' if the input does not+-- start with the given prefix, stripped input otherwise. Returns @Just nil@+-- when the prefix is the same as the input stream.+--+-- Space: @O(1)@+--+{-# INLINE_NORMAL stripPrefix #-}+stripPrefix+ :: (Monad m, Eq a)+ => Stream m a -> Stream m a -> m (Maybe (Stream m a))+stripPrefix (Stream stepa ta) (Stream stepb tb) = go SPEC Nothing' ta tb++ where++ go !_ Nothing' sa sb = do+ r <- stepa defState sa+ case r of+ Yield x sa' -> go SPEC (Just' x) sa' sb+ Skip sa' -> go SPEC Nothing' sa' sb+ Stop -> return $ Just (Stream stepb sb)++ go !_ (Just' x) sa sb = do+ r <- stepb defState sb+ case r of+ Yield y sb' ->+ if x == y+ then go SPEC Nothing' sa sb'+ else return Nothing+ Skip sb' -> go SPEC (Just' x) sa sb'+ Stop -> return Nothing+ -- | Returns 'True' if the first stream is an infix of the second. A stream is -- considered an infix of itself. --@@ -288,12 +579,12 @@ -- /Requires 'Storable' constraint/ -- {-# INLINE isInfixOf #-}-isInfixOf :: (MonadIO m, Eq a, Enum a, Storable a, Unbox a)+isInfixOf :: (MonadIO m, Eq a, Enum a, Unbox a) => Stream m a -> Stream m a -> m Bool isInfixOf infx stream = do- arr <- fold Array.write infx+ arr <- fold Array.create infx -- XXX can use breakOnSeq instead (when available)- r <- D.null $ D.drop 1 $ D.splitOnSeq arr Fold.drain $ toStreamD stream+ r <- null $ StreamD.drop 1 $ Nesting.splitSepBySeq_ arr Fold.drain stream return (not r) -- Note: isPrefixOf uses the prefix stream only once. In contrast, isSuffixOf@@ -326,36 +617,15 @@ -- {-# INLINE isSuffixOf #-} isSuffixOf :: (Monad m, Eq a) => Stream m a -> Stream m a -> m Bool-isSuffixOf suffix stream = reverse suffix `isPrefixOf` reverse stream---- | 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.------ >>> Stream.isSubsequenceOf (Stream.fromList "hlo") (Stream.fromList "hello" :: Stream IO Char)--- True----{-# INLINE isSubsequenceOf #-}-isSubsequenceOf :: (Monad m, Eq a) => Stream m a -> Stream m a -> m Bool-isSubsequenceOf m1 m2 = D.isSubsequenceOf (toStreamD m1) (toStreamD m2)---- Note: If we want to return a Maybe value to know whether the--- suffix/infix was present or not along with the stripped stream then--- we need to buffer the whole input stream.+isSuffixOf suffix stream =+ StreamD.reverse suffix `isPrefixOf` StreamD.reverse stream --- | @stripPrefix prefix input@ strips the @prefix@ stream from the @input@--- stream if it is a prefix of input. Returns 'Nothing' if the input does not--- start with the given prefix, stripped input otherwise. Returns @Just nil@--- when the prefix is the same as the input stream.------ Space: @O(1)@----{-# INLINE stripPrefix #-}-stripPrefix- :: (Monad m, Eq a)- => Stream m a -> Stream m a -> m (Maybe (Stream m a))-stripPrefix m1 m2 = fmap fromStreamD <$>- D.stripPrefix (toStreamD m1) (toStreamD m2)+-- | Much faster than 'isSuffixOf'.+{-# INLINE isSuffixOfUnbox #-}+isSuffixOfUnbox :: (MonadIO m, Eq a, Unbox a) =>+ Stream m a -> Stream m a -> m Bool+isSuffixOfUnbox suffix stream =+ StreamD.reverseUnbox suffix `isPrefixOf` StreamD.reverseUnbox stream -- | Drops the given suffix from a stream. Returns 'Nothing' if the stream does -- not end with the given suffix. Returns @Just nil@ when the suffix is the@@ -365,8 +635,6 @@ -- stripSuffix on that especially if the elements have a Storable or Prim -- instance. ----- See also "Streamly.Internal.Data.Stream.Reduce.dropSuffix".--- -- Space: @O(n)@, buffers the entire input stream as well as the suffix -- -- /Pre-release/@@ -374,4 +642,15 @@ stripSuffix :: (Monad m, Eq a) => Stream m a -> Stream m a -> m (Maybe (Stream m a))-stripSuffix m1 m2 = fmap reverse <$> stripPrefix (reverse m1) (reverse m2)+stripSuffix m1 m2 =+ fmap StreamD.reverse+ <$> stripPrefix (StreamD.reverse m1) (StreamD.reverse m2)++-- | Much faster than 'stripSuffix'.+{-# INLINE stripSuffixUnbox #-}+stripSuffixUnbox+ :: (MonadIO m, Eq a, Unbox a)+ => Stream m a -> Stream m a -> m (Maybe (Stream m a))+stripSuffixUnbox m1 m2 =+ fmap StreamD.reverseUnbox+ <$> stripPrefix (StreamD.reverseUnbox m1) (StreamD.reverseUnbox m2)
− src/Streamly/Internal/Data/Stream/Enumerate.hs
@@ -1,560 +0,0 @@--- |--- Module : Streamly.Internal.Data.Stream.Enumerate--- Copyright : (c) 2018 Composewell Technologies------ 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.---- XXX The Unfold.Enumeration module is more modular, check the differences and--- reconcile the two.--module Streamly.Internal.Data.Stream.Enumerate- (- 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.Type (Stream, fromStreamD)--import qualified Streamly.Internal.Data.Stream.StreamD.Generate as D---- $setup--- >>> import Streamly.Data.Fold as Fold--- >>> import Streamly.Internal.Data.Stream as Stream--- >>> import Streamly.Internal.Data.Stream.Enumerate as Stream------------------------------------------------------------------------------------ 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.------ @--- >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFromStepIntegral 0 2--- [0,2,4,6]------ >>> Stream.fold Fold.toList $ Stream.take 3 $ Stream.enumerateFromStepIntegral 0 (-2)--- [0,-2,-4]------ @----{-# INLINE enumerateFromStepIntegral #-}-enumerateFromStepIntegral- :: (Monad m, Integral a)- => a -> a -> Stream 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.------ @--- >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFromIntegral (0 :: Int)--- [0,1,2,3]------ @----{-# INLINE enumerateFromIntegral #-}-enumerateFromIntegral- :: (Monad m, Integral a, Bounded a)- => a -> Stream 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.------ @--- >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFromThenIntegral (0 :: Int) 2--- [0,2,4,6]------ >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFromThenIntegral (0 :: Int) (-2)--- [0,-2,-4,-6]------ @----{-# INLINE enumerateFromThenIntegral #-}-enumerateFromThenIntegral- :: (Monad m, Integral a, Bounded a)- => a -> a -> Stream 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@.------ @--- >>> Stream.fold Fold.toList $ Stream.enumerateFromToIntegral 0 4--- [0,1,2,3,4]------ @----{-# INLINE enumerateFromToIntegral #-}-enumerateFromToIntegral :: (Monad m, Integral a) => a -> a -> Stream 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@.------ @--- >>> Stream.fold Fold.toList $ Stream.enumerateFromThenToIntegral 0 2 6--- [0,2,4,6]------ >>> Stream.fold Fold.toList $ Stream.enumerateFromThenToIntegral 0 (-2) (-6)--- [0,-2,-4,-6]------ @----{-# INLINE enumerateFromThenToIntegral #-}-enumerateFromThenToIntegral- :: (Monad m, Integral a)- => a -> a -> a -> Stream 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:------ @--- >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFromFractional 1.1--- [1.1,2.1,3.1,4.1]------ @-------{-# INLINE enumerateFromFractional #-}-enumerateFromFractional :: (Monad m, Fractional a) => a -> Stream m a-enumerateFromFractional from = fromStreamD $ D.enumerateFromNum 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:------ @--- >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFromThenFractional 1.1 2.1--- [1.1,2.1,3.1,4.1]------ >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFromThenFractional 1.1 (-2.1)--- [1.1,-2.1,-5.300000000000001,-8.500000000000002]------ @----{-# INLINE enumerateFromThenFractional #-}-enumerateFromThenFractional- :: (Monad m, Fractional a)- => a -> a -> Stream m a-enumerateFromThenFractional from next = fromStreamD $ D.enumerateFromThenNum 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:------ @--- >>> Stream.fold Fold.toList $ Stream.enumerateFromToFractional 1.1 4--- [1.1,2.1,3.1,4.1]------ >>> Stream.fold Fold.toList $ Stream.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.----{-# INLINE enumerateFromToFractional #-}-enumerateFromToFractional- :: (Monad m, Fractional a, Ord a)- => a -> a -> Stream 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:------ @--- >>> Stream.fold Fold.toList $ Stream.enumerateFromThenToFractional 0.1 2 6--- [0.1,2.0,3.9,5.799999999999999]------ >>> Stream.fold Fold.toList $ Stream.enumerateFromThenToFractional 0.1 (-2) (-6)--- [0.1,-2.0,-4.1000000000000005,-6.200000000000001]------ @-------{-# INLINE enumerateFromThenToFractional #-}-enumerateFromThenToFractional- :: (Monad m, Fractional a, Ord a)- => a -> a -> a -> Stream 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'.----{-# INLINE enumerateFromToSmall #-}-enumerateFromToSmall :: (Monad m, Enum a) => a -> a -> Stream m a-enumerateFromToSmall from to =- fmap toEnum- $ enumerateFromToIntegral (fromEnum from) (fromEnum to)---- | 'enumerateFromThenTo' for 'Enum' types not larger than 'Int'.----{-# INLINE enumerateFromThenToSmall #-}-enumerateFromThenToSmall :: (Monad m, Enum a)- => a -> a -> a -> Stream m a-enumerateFromThenToSmall from next to =- fmap 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.----{-# INLINE enumerateFromThenSmallBounded #-}-enumerateFromThenSmallBounded :: (Monad m, Enumerable a, Bounded a)- => a -> a -> Stream m a-enumerateFromThenSmallBounded from next =- if fromEnum next >= fromEnum from- then enumerateFromThenTo from next maxBound- else 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.----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'.- --- -- @- -- >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFrom (0 :: Int)- -- [0,1,2,3]- --- -- @- --- -- For 'Fractional' types, enumeration is numerically stable. However, no- -- overflow or underflow checks are performed.- --- -- @- -- >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFrom 1.1- -- [1.1,2.1,3.1,4.1]- --- -- @- --- enumerateFrom :: (Monad m) => a -> Stream 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.- --- -- @- -- >>> Stream.fold Fold.toList $ Stream.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.- --- -- @- -- >>> Stream.fold Fold.toList $ Stream.enumerateFromTo 1.1 4- -- [1.1,2.1,3.1,4.1]- --- -- >>> Stream.fold Fold.toList $ Stream.enumerateFromTo 1.1 4.6- -- [1.1,2.1,3.1,4.1,5.1]- --- -- @- --- enumerateFromTo :: (Monad m) => a -> a -> Stream 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.- --- -- @- -- >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFromThen 0 2- -- [0,2,4,6]- --- -- >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFromThen 0 (-2)- -- [0,-2,-4,-6]- --- -- @- --- enumerateFromThen :: (Monad m) => a -> a -> Stream 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@.- --- -- @- -- >>> Stream.fold Fold.toList $ Stream.enumerateFromThenTo 0 2 6- -- [0,2,4,6]- --- -- >>> Stream.fold Fold.toList $ Stream.enumerateFromThenTo 0 (-2) (-6)- -- [0,-2,-4,-6]- --- -- @- --- enumerateFromThenTo :: (Monad m) => a -> a -> a -> Stream 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'----{-# INLINE enumerate #-}-enumerate :: (Monad m, Bounded a, Enumerable a) => Stream m a-enumerate = enumerateFrom minBound---- |--- > enumerateTo = enumerateFromTo minBound------ Enumerate a 'Bounded' type from its 'minBound' to specified value.----{-# INLINE enumerateTo #-}-enumerateTo :: (Monad m, Bounded a, Enumerable a) => a -> Stream m a-enumerateTo = enumerateFromTo minBound---- |--- > enumerateFromBounded = enumerateFromTo from maxBound------ 'enumerateFrom' for 'Bounded' 'Enum' types.----{-# INLINE enumerateFromBounded #-}-enumerateFromBounded :: (Monad m, Enumerable a, Bounded a)- => a -> Stream 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)--instance Enumerable a => Enumerable (Identity a) where- {-# INLINE enumerateFrom #-}- enumerateFrom (Identity from) =- fmap Identity $ enumerateFrom from- {-# INLINE enumerateFromThen #-}- enumerateFromThen (Identity from) (Identity next) =- fmap Identity $ enumerateFromThen from next- {-# INLINE enumerateFromTo #-}- enumerateFromTo (Identity from) (Identity to) =- fmap Identity $ enumerateFromTo from to- {-# INLINE enumerateFromThenTo #-}- enumerateFromThenTo (Identity from) (Identity next) (Identity to) =- fmap Identity- $ enumerateFromThenTo from next to---- 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/Exception.hs view
@@ -1,6 +1,7 @@+{-# LANGUAGE CPP #-} -- | -- Module : Streamly.Internal.Data.Stream.Exception--- Copyright : (c) 2019 Composewell Technologies+-- Copyright : (c) 2020 Composewell Technologies and Contributors -- License : BSD-3-Clause -- Maintainer : streamly@composewell.com -- Stability : experimental@@ -8,46 +9,209 @@ module Streamly.Internal.Data.Stream.Exception (+ -- * Resources before- , afterUnsafe , afterIO+ , afterUnsafe+ , finallyIO+ , finallyIO'+ , finallyIO''+ , finallyUnsafe+ , gbracket_+ , gbracket , bracketUnsafe- , bracketIO , bracketIO3+ , bracketIO+ , bracketIO'+ , bracketIO''++ , withAcquireIO+ , withAcquireIO'++ -- * Exceptions , onException- , finallyUnsafe- , finallyIO , ghandle , handle ) where -import Control.Exception (Exception)+#include "inline.hs"++import Control.Monad.IO.Class (MonadIO(..))+import Control.Exception (Exception, SomeException, mask_) import Control.Monad.Catch (MonadCatch)-import Control.Monad.IO.Class (MonadIO)-import Streamly.Internal.Data.Stream.Type (Stream, fromStreamD, toStreamD)+import Data.IORef (newIORef)+import GHC.Exts (inline)+import Streamly.Internal.Control.Exception+ (AcquireIO(..), acquire, allocator, releaser)+import Streamly.Internal.Data.IOFinalizer+ (newIOFinalizer, runIOFinalizer, clearingIOFinalizer) -import qualified Streamly.Internal.Data.Stream.StreamD as D+import qualified Control.Monad.Catch as MC+import qualified Data.IntMap.Strict as Map --- $setup--- >>> :m--- >>> import qualified Streamly.Internal.Data.Stream as Stream+import Streamly.Internal.Data.Stream.Type ---------------------------------------------------------------------------------- Exceptions-------------------------------------------------------------------------------+#include "DocTestDataStream.hs" +data GbracketState s1 s2 v+ = GBracketInit+ | GBracketNormal s1 v+ | GBracketException s2++-- | Like 'gbracket' but with following differences:+--+-- * alloc action @m c@ runs with async exceptions enabled+-- * cleanup action @c -> m d@ won't run if the stream is garbage collected+-- after partial evaluation.+--+-- /Inhibits stream fusion/+--+-- /Pre-release/+--+{-# INLINE_NORMAL gbracket_ #-}+gbracket_+ :: Monad m+ => m c -- ^ before+ -> (c -> m d) -- ^ after, on normal stop+ -> (c -> e -> Stream m b -> m (Stream m b)) -- ^ on exception+ -> (forall s. m s -> m (Either e s)) -- ^ try (exception handling)+ -> (c -> Stream m b) -- ^ stream generator+ -> Stream m b+gbracket_ bef aft onExc ftry action =+ Stream step GBracketInit++ where++ {-# INLINE_LATE step #-}+ step _ GBracketInit = do+ r <- bef+ return $ Skip $ GBracketNormal (action r) r++ step gst (GBracketNormal (UnStream step1 st) v) = do+ res <- ftry $ 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+ -- XXX Do not handle async exceptions, just rethrow them.+ Left e -> do+ strm <- onExc v e (UnStream step1 st)+ return $ Skip (GBracketException strm)+ 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++data GbracketIOState s1 s2 v wref+ = GBracketIOInit+ | GBracketIONormal s1 v wref+ | GBracketIOException s2++-- | Run the alloc action @m c@ with async exceptions disabled but keeping+-- blocking operations interruptible (see 'Control.Exception.mask'). Use the+-- output @c@ as input to @c -> Stream m b@ to generate an output stream. When+-- generating the stream use the supplied @try@ operation @forall s. m s -> m+-- (Either e s)@ to catch synchronous exceptions. If an exception occurs run+-- the exception handler @c -> e -> Stream m b -> m (Stream m b)@. Note that+-- 'gbracket' does not rethrow the exception, it has to be done by the+-- exception handler if desired.+--+-- The cleanup action @c -> m d@, runs whenever the stream ends normally, due+-- to a sync or async exception or if it gets garbage collected after a partial+-- lazy evaluation. See 'bracket' for the semantics of the cleanup action.+--+-- 'gbracket' can express all other exception handling combinators.+--+-- /Inhibits stream fusion/+--+-- /Pre-release/+{-# INLINE_NORMAL gbracket #-}+gbracket+ :: MonadIO m+ => IO c -- ^ before+ -> (c -> IO d1) -- ^ on normal stop+ -> (c -> e -> Stream m b -> IO (Stream m b)) -- ^ on exception+ -> (c -> IO d2) -- ^ on GC without normal stop or exception+ -> (forall s. m s -> m (Either e s)) -- ^ try (exception handling)+ -> (c -> Stream m b) -- ^ stream generator+ -> Stream m b+gbracket bef aft onExc onGC ftry action =+ 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+ -- allocation of resource and installation of finalizer must be atomic+ -- with respect to async exception, otherwise we may leave a window+ -- where the resource may not be freed.+ (r, ref) <- liftIO $ mask_ $ do+ r <- bef+ ref <- newIOFinalizer (onGC r)+ return (r, ref)+ return $ Skip $ GBracketIONormal (action r) r ref++ step gst (GBracketIONormal (UnStream step1 st) v ref) = do+ -- IMPORTANT: Note that if an async exception occurs before try or+ -- after try, in those cases the exception will not be intercepted and+ -- the cleanup handler won't run. In those cases the cleanup handler+ -- will run via GC.+ res <- ftry $ 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 ->+ liftIO (clearingIOFinalizer ref (aft v)) >> return Stop+ -- XXX Do not handle async exceptions, just rethrow them.+ Left e -> do+ -- Clearing of finalizer and running of exception handler must+ -- be atomic wrt async exceptions. Otherwise if we have cleared+ -- the finalizer and have not run the exception handler then we+ -- may leak the resource.+ stream <-+ liftIO (clearingIOFinalizer ref (onExc v e (UnStream step1 st)))+ return $ Skip (GBracketIOException stream)+ 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 the action @m b@ before the stream yields its first element. -- -- Same as the following but more efficient due to fusion: ----- >>> before action xs = Stream.nilM action <> xs -- >>> before action xs = Stream.concatMap (const xs) (Stream.fromEffect action) ---{-# INLINE before #-}+{-# INLINE_NORMAL before #-} before :: Monad m => m b -> Stream m a -> Stream m a-before action xs = fromStreamD $ D.before action $ toStreamD xs+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+ -- | Like 'after', with following differences: -- -- * action @m b@ won't run if the stream is garbage collected@@ -61,10 +225,20 @@ -- -- /Pre-release/ ---{-# INLINE afterUnsafe #-}+{-# INLINE_NORMAL afterUnsafe #-} afterUnsafe :: Monad m => m b -> Stream m a -> Stream m a-afterUnsafe action xs = fromStreamD $ D.afterUnsafe action $ toStreamD xs+afterUnsafe 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+ -- | Run the action @IO b@ whenever the stream is evaluated to completion, or -- if it is garbage collected after a partial lazy evaluation. --@@ -73,51 +247,61 @@ -- -- /See also 'afterUnsafe'/ ---{-# INLINE afterIO #-}-afterIO :: MonadIO m => IO b -> Stream m a -> Stream m a-afterIO action xs = fromStreamD $ D.afterIO action $ toStreamD xs+{-# INLINE_NORMAL afterIO #-}+afterIO :: MonadIO m+ => IO b -> Stream m a -> Stream m a+afterIO action (Stream step state) = Stream step' Nothing + where++ {-# INLINE_LATE step' #-}+ step' _ Nothing = do+ ref <- liftIO $ newIOFinalizer 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+ runIOFinalizer ref+ return Stop++-- XXX For high performance error checks in busy streams we may need another+-- Error constructor in step.+ -- | Run the action @m b@ if the stream evaluation is aborted due to an -- exception. The exception is not caught, simply rethrown. --+-- Observes exceptions only in the stream generation, and not in stream+-- consumers.+-- -- /Inhibits stream fusion/ ---{-# INLINE onException #-}+{-# INLINE_NORMAL onException #-} onException :: MonadCatch m => m b -> Stream m a -> Stream m a-onException action xs = fromStreamD $ D.onException action $ toStreamD xs+onException action stream =+ gbracket_+ (return ()) -- before+ return -- after+ (\_ (e :: MC.SomeException) _ -> action >> MC.throwM e)+ (inline MC.try)+ (const stream) --- | Like 'finally' with following differences:------ * action @m b@ won't run if the stream is garbage collected--- after partial evaluation.--- * has slightly better performance than 'finallyIO'.------ /Inhibits stream fusion/------ /Pre-release/----{-# INLINE finallyUnsafe #-}-finallyUnsafe :: MonadCatch m => m b -> Stream m a -> Stream m a-finallyUnsafe action xs = fromStreamD $ D.finallyUnsafe action $ toStreamD xs+{-# INLINE_NORMAL _onException #-}+_onException :: MonadCatch m => m b -> Stream m a -> Stream m a+_onException action (Stream step state) = Stream step' state --- | Run the action @IO b@ whenever the stream stream stops normally, aborts--- due to an exception or if it is garbage collected after a partial lazy--- evaluation.------ The semantics of running the action @IO b@ are similar to the cleanup action--- semantics described in 'bracketIO'.------ >>> finallyIO release = Stream.bracketIO (return ()) (const release)------ /See also 'finallyUnsafe'/------ /Inhibits stream fusion/----{-# INLINE finallyIO #-}-finallyIO :: (MonadIO m, MonadCatch m) =>- IO b -> Stream m a -> Stream m a-finallyIO action xs = fromStreamD $ D.finallyIO action $ toStreamD xs+ 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+ -- | Like 'bracket' but with following differences: -- -- * alloc action @m b@ runs with async exceptions enabled@@ -129,41 +313,22 @@ -- -- /Pre-release/ ---{-# INLINE bracketUnsafe #-}+{-# INLINE_NORMAL bracketUnsafe #-} bracketUnsafe :: MonadCatch m => m b -> (b -> m c) -> (b -> Stream m a) -> Stream m a-bracketUnsafe bef aft bet = fromStreamD $ D.bracketUnsafe bef aft (toStreamD . bet)---- | Run the alloc action @IO b@ with async exceptions disabled but keeping--- blocking operations interruptible (see 'Control.Exception.mask'). Use the--- output @b@ as input to @b -> Stream m a@ to generate an output stream.------ @b@ is usually a resource under the IO monad, e.g. a file handle, that--- requires a cleanup after use. The cleanup action @b -> IO c@, runs whenever--- the stream ends normally, due to a sync or async exception or if it gets--- garbage collected after a partial lazy evaluation.------ 'bracketIO' only guarantees that the cleanup action runs, and it runs with--- async exceptions enabled. The action must ensure that it can successfully--- cleanup the resource in the face of sync or async exceptions.------ When the stream ends normally or on a sync exception, cleanup action runs--- immediately in the current thread context, whereas in other cases it runs in--- the GC context, therefore, cleanup may be delayed until the GC gets to run.------ /See also: 'bracketUnsafe'/------ /Inhibits stream fusion/----{-# INLINE bracketIO #-}-bracketIO :: (MonadIO m, MonadCatch m)- => IO b -> (b -> IO c) -> (b -> Stream m a) -> Stream m a-bracketIO bef aft = bracketIO3 bef aft aft aft+bracketUnsafe bef aft =+ gbracket_+ bef+ aft+ (\a (e :: SomeException) _ -> aft a >> MC.throwM e)+ (inline MC.try) -- For a use case of this see the "streamly-process" package. It needs to kill -- the process in case of exception or garbage collection, but waits for the -- process to terminate in normal cases. +-- XXX Just use bracketIO2 instead - stop and exception.+ -- | Like 'bracketIO' but can use 3 separate cleanup actions depending on the -- mode of termination: --@@ -176,20 +341,393 @@ -- stream is abandoned @onGC@ is executed, if the stream encounters an -- exception @onException@ is executed. --+-- The exception is not caught, it is rethrown.+-- -- /Inhibits stream fusion/ -- -- /Pre-release/-{-# INLINE bracketIO3 #-}-bracketIO3 :: (MonadIO m, MonadCatch m)- => IO b+{-# INLINE_NORMAL bracketIO3 #-}+bracketIO3 :: (MonadIO m, MonadCatch m) =>+ IO b -> (b -> IO c) -> (b -> IO d) -> (b -> IO e) -> (b -> Stream m a) -> Stream m a-bracketIO3 bef aft gc exc bet = fromStreamD $- D.bracketIO3 bef aft exc gc (toStreamD . bet)+bracketIO3 bef aft onExc onGC =+ gbracket+ bef+ aft+ (\a (e :: SomeException) _ -> onExc a >> MC.throwM e)+ onGC+ (inline MC.try) +-- XXX Fix the early termination case not being prompt. Will require a "final"+-- function in the stream constructor.++-- Examples of cases where the stream is not fully consumed:+--+-- * a bracketed stream is folded but before the stream ends, the fold+-- terminates or encounters an exception abandoning the original stream.+-- * 'take' on a bracketed stream terminates without draining the stream+-- completely. To avoid this, bracket should be outermost combinator on a+-- stream.+-- * A synchronous exception is handled using 'handle', in that case the+-- original stream is abandoned and collected by GC.+--+-- In case of async exceptions, if the async exception occurs when we are+-- executing the stream code then it will be intercepted. After the stream+-- element is generated, control is handed over to the consumer (fold), async+-- exceptions occurring in this period are not intercepted by bracketIO, they+-- are intercepted by the fold's bracket instead. If an async exceptions occurs+-- in this part and the stream is abandoned, the cleanup handler runs on GC.++-- | The alloc action @IO b@ is executed with async exceptions disabled but keeping+-- blocking operations interruptible (see 'Control.Exception.mask'). Uses the+-- output @b@ of the IO action as input to the function @b -> Stream m a@ to+-- generate an output stream.+--+-- @b@ is usually a resource allocated under the IO monad, e.g. a file handle, that+-- requires a cleanup after use. The cleanup is done using the @b -> IO c@+-- action. bracketIO guarantees that the allocated resource is eventually (see+-- details below) cleaned up even in the face of sync or async exceptions. If+-- an exception occurs it is not caught, simply rethrown.+--+-- 'bracketIO' only guarantees that the cleanup action runs, and it runs with+-- __async exceptions enabled__. The action must ensure that it can successfully+-- cleanup the resource in the face of sync or async exceptions.+--+-- /Best case/: Cleanup happens immediately in the following cases:+--+-- * the stream is consumed completely+-- * an exception occurs in the bracketed part of the pipeline+--+-- /Worst case/: In the following cases cleanup is deferred to GC.+--+-- * the bracketed stream is partially consumed and abandoned+-- * pipeline is aborted due to an exception outside the bracket+--+-- Use Streamly.Control.Exception.'Streamly.Control.Exception.withAcquireIO'+-- for covering the entire pipeline with guaranteed cleanup at the end of+-- bracket.+--+-- Observes exceptions only in the stream generation, and not in stream+-- consumers.+--+-- /See also: 'bracketUnsafe'/+--+-- /Inhibits stream fusion/+--+{-# INLINE bracketIO #-}+bracketIO :: (MonadIO m, MonadCatch m)+ => IO b -> (b -> IO c) -> (b -> Stream m a) -> Stream m a+bracketIO bef aft = bracketIO3 bef aft aft aft++-- If you are recovering from exceptions using 'handle' then you should use+-- bracketIO'' which releases the resource promptly on exception before the+-- exception handler generates another stream. But for better performance+-- bracketIO' may be better and leave the resource to be freed by GC.+--+-- XXX If we want to recover from exceptions then we should probably have an+-- integrated combinator combining handling with bracketIO'' otherwise we will+-- have multiple layers of "try" which will not be good for perf.++data GbracketIO'State s ref release+ = GBracketIO'Init+ | GBracketIO'Normal s ref release++-- | Like 'bracketIO' but requires an 'Streamly.Control.Exception.AcquireIO' reference in the underlying monad+-- of the stream, and guarantees that all resources are freed before the+-- scope of the monad level resource manager+-- (Streamly.Control.Exception.'Streamly.Control.Exception.withAcquireIO')+-- ends. Where fusion matters, this combinator can be much faster than 'bracketIO' as it+-- allows stream fusion.+--+-- /Best case/: Cleanup happens immediately if the stream is consumed+-- completely.+--+-- /Worst case/: In the following cases cleanup is guaranteed to occur at the+-- end of the monad level bracket. However, if a GC occurs then cleanup will+-- occur even earlier than that.+--+-- * the bracketed stream is partially consumed and abandoned+-- * pipeline is aborted due to an exception+--+-- __This is the recommended default bracket operation.__+--+-- Note: You can use 'Streamly.Control.Exception.acquire' directly, instead of using this combinator, if+-- you don’t need to release the resource when the stream ends. However, if+-- you're using the stream inside another stream (like with concatMap), you+-- usually do want to release it at the end of the stream.+--+-- /Allows stream fusion/+--+{-# INLINE bracketIO' #-}+bracketIO' :: MonadIO m+ => AcquireIO -> IO b -> (b -> IO c) -> (b -> Stream m a) -> Stream m a+bracketIO' bracket alloc free action =+ Stream step GBracketIO'Init++ where++ -- In nested stream cases, where the inner stream is abandoned due to early+ -- termination or due to exception handling, we use GC based cleanup as+ -- fallback because the monad level cleanup may not occur in deterministic+ -- amount of time, but GC may. Users can also implement backpressure+ -- themselves e.g. if the number of open fds is greater than n then perform+ -- GC until it comes down.+ {-# INLINE_LATE step #-}+ step _ GBracketIO'Init = do+ (r, ref, release) <- liftIO $ mask_ $ do+ (r, release) <- liftIO $ acquire bracket alloc free+ ref <- newIOFinalizer release+ return (r, ref, release)+ return $ Skip $ GBracketIO'Normal (action r) ref release++ step gst (GBracketIO'Normal (UnStream step1 st) ref release) = do+ res <- step1 gst st+ case res of+ Yield x s ->+ return $ Yield x (GBracketIO'Normal (Stream step1 s) ref release)+ Skip s ->+ return $ Skip (GBracketIO'Normal (Stream step1 s) ref release)+ Stop ->+ liftIO (clearingIOFinalizer ref release) >> return Stop++-- | Like bracketIO, the only difference is that there is a guarantee that the+-- resources will be freed at the end of the monad level bracket+-- ('Streamly.Control.Exception.AcquireIO').+--+-- /Best case/: Cleanup happens immediately in the following cases:+--+-- * the stream is consumed completely+-- * an exception occurs in the bracketed part of the pipeline+--+-- /Worst case/: In the following cases cleanup is guaranteed to occur at the+-- end of the monad level bracket. However, if a GC occurs before that then+-- cleanup will occur early.+--+-- * the bracketed stream is partially consumed and abandoned+-- * pipeline is aborted due to an exception outside the bracket+--+-- Note: Instead of using this combinator you can directly use+-- 'Streamly.Control.Exception.acquire'+-- if you do not care about releasing the resource at the end of the stream+-- and if you are not recovering from an exception using 'handle'. You may want+-- to care about releasing the resource at the end of a stream if you are using+-- it in a nested manner (e.g. in concatMap).+--+-- /Inhibits stream fusion/+--+{-# INLINE bracketIO'' #-}+bracketIO'' :: (MonadIO m, MonadCatch m)+ => AcquireIO -> IO b -> (b -> IO c) -> (b -> Stream m a) -> Stream m a+bracketIO'' bracket alloc free action =+ Stream step GBracketIO'Init++ where++ {-# INLINE_LATE step #-}+ step _ GBracketIO'Init = do+ (r, ref, release) <- liftIO $ mask_ $ do+ (r, release) <- liftIO $ acquire bracket alloc free+ ref <- newIOFinalizer release+ return (r, ref, release)+ return $ Skip $ GBracketIO'Normal (action r) ref release++ step gst (GBracketIO'Normal (UnStream step1 st) ref release) = do+ -- If an async exception occurs before try or after try, in those cases+ -- the exception will not be intercepted here. In those cases the+ -- release action will run via AcquireIO release hook.+ res <- MC.try $ step1 gst st+ case res of+ Right r ->+ case r of+ Yield x s ->+ return+ $ Yield x (GBracketIO'Normal (Stream step1 s) ref release)+ Skip s ->+ return+ $ Skip (GBracketIO'Normal (Stream step1 s) ref release)+ Stop ->+ liftIO (clearingIOFinalizer ref release) >> return Stop+ Left (e :: SomeException) ->+ liftIO (clearingIOFinalizer ref release) >> MC.throwM e++-- | Like finallyIO, based on bracketIO' semantics.+{-# INLINE finallyIO' #-}+finallyIO' :: MonadIO m => AcquireIO -> IO b -> Stream m a -> Stream m a+finallyIO' bracket free stream =+ bracketIO' bracket (return ()) (const free) (const stream)++-- | Like finallyIO, based on bracketIO'' semantics.+{-# INLINE finallyIO'' #-}+finallyIO'' :: (MonadIO m, MonadCatch m) =>+ AcquireIO -> IO b -> Stream m a -> Stream m a+finallyIO'' bracket free stream =+ bracketIO'' bracket (return ()) (const free) (const stream)++-- | Like 'bracketIO' but with on-demand allocations and manual release+-- facility.+--+-- Here is an example:+--+-- >>> :{+-- close x h = do+-- putStrLn $ "closing: " ++ x+-- hClose h+-- :}+--+-- >>> :{+-- generate ref =+-- Stream.fromList ["file1", "file2"]+-- & Stream.mapM+-- (\x -> do+-- (h, release) <- Exception.acquire ref (openFile x ReadMode) (close x)+-- -- use h here+-- threadDelay 1000000+-- when (x == "file1") $ do+-- putStrLn $ "Manually releasing: " ++ x+-- release+-- return x+-- )+-- & Stream.trace print+-- :}+--+-- >>> :{+-- run =+-- Stream.withAcquireIO generate+-- & Stream.fold Fold.drain+-- :}+--+-- In the above code, you should see the \"closing:\" message for both the+-- files, and only once for each file. Make sure you create "file1" and "file2"+-- before running it.+--+-- Here is an example for just registering hooks to be called eventually:+--+-- >>> :{+-- generate ref =+-- Stream.fromList ["file1", "file2"]+-- & Stream.mapM+-- (\x -> do+-- Exception.register ref $ putStrLn $ "saw: " ++ x+-- threadDelay 1000000+-- return x+-- )+-- & Stream.trace print+-- :}+--+-- >>> :{+-- run =+-- Stream.withAcquireIO generate+-- & Stream.fold Fold.drain+-- :}+--+-- In the above code, even if you interrupt the program with CTRL-C you should+-- still see the "saw:" message for the elements seen before the interrupt.+--+-- See 'bracketIO' documentation for the caveats related to partially consumed+-- streams and async exceptions.+--+-- Use monad level bracket Streamly.Control.Exception.'Streamly..Control.Exception.withAcquireIO'+-- for guaranteed cleanup in the entire pipeline, however, monad level bracket does not provide+-- an automatic cleanup at the end of the stream; you can only release+-- resources manually or via automatic cleanup at the end of the monad bracket.+-- The end of stream cleanup is useful especially in nested streams where we+-- want to cleanup at the end of every inner stream instead of waiting for the+-- outer stream to end for cleaning up to happen.+--+{-# INLINE withAcquireIO #-}+withAcquireIO :: (MonadIO m, MonadCatch m) =>+ (AcquireIO -> Stream m a) -> Stream m a+withAcquireIO action = do+ bracketIO bef (releaser . fst) (\(_, alloc) -> action alloc)++ where++ bef = do+ -- Assuming 64-bit int counter will never overflow+ ref <- liftIO $ newIORef (0 :: Int, Map.empty, Map.empty)+ return (ref, AcquireIO (allocator ref))++-- | We can also combine the stream local 'withAcquireIO' with the global monad+-- level bracket+-- Streamly.Internal.Control.Exception.'Streamly.Internal.Control.Exception.withAcquireIO'.+-- The release actions returned by the local allocator can be registered to be+-- called by the monad level bracket. This way we can guarantee that in the+-- worst case release actions happen at the end of bracket and do not depend on+-- GC. This is the most powerful way of allocating resources on-demand with+-- manual release inside a stream. If required a custom combinator can be+-- written to register the local allocator's release in the global allocator+-- automatically.+--+-- /Unimplemented/+{-# INLINE withAcquireIO' #-}+withAcquireIO' :: -- (MonadIO m, MonadCatch m) =>+ AcquireIO -> (AcquireIO -> Stream m a) -> Stream m a+withAcquireIO' _globalAlloc _action = undefined++data BracketState s v = BracketInit | BracketRun s v++-- | Alternate (custom) implementation of 'bracket'.+--+{-# 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 <- inline 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++-- | Like 'finally' with following differences:+--+-- * action @m b@ won't run if the stream is garbage collected+-- after partial evaluation.+-- * has slightly better performance than 'finallyIO'.+--+-- /Inhibits stream fusion/+--+-- /Pre-release/+--+{-# INLINE finallyUnsafe #-}+finallyUnsafe :: MonadCatch m => m b -> Stream m a -> Stream m a+finallyUnsafe action xs = bracketUnsafe (return ()) (const action) (const xs)++-- | Run the action @IO b@ whenever the stream stream stops normally, aborts+-- due to an exception or if it is garbage collected after a partial lazy+-- evaluation.+--+-- The semantics of running the action @IO b@ are similar to the cleanup action+-- semantics described in 'bracketIO'.+--+-- >>> finallyIO release stream = Stream.bracketIO (return ()) (const release) (const stream)+--+-- See also finallyIO' for stricter resource release guarantees.+--+-- /See also 'finallyUnsafe'/+--+-- /Inhibits stream fusion/+--+{-# INLINE finallyIO #-}+finallyIO :: (MonadIO m, MonadCatch m) => IO b -> Stream m a -> Stream m a+finallyIO action xs = bracketIO3 (return ()) act act act (const xs)+ where act _ = action+ -- | Like 'handle' but the exception handler is also provided with the stream -- that generated the exception as input. The exception handler can thus -- re-evaluate the stream to retry the action that failed. The exception@@ -202,21 +740,51 @@ -- -- /Pre-release/ ---{-# INLINE ghandle #-}+{-# INLINE_NORMAL ghandle #-} ghandle :: (MonadCatch m, Exception e)- => (e -> Stream m a -> Stream m a) -> Stream m a -> Stream m a-ghandle handler =- fromStreamD- . D.ghandle (\e xs -> toStreamD $ handler e (fromStreamD xs))- . toStreamD+ => (e -> Stream m a -> m (Stream m a)) -> Stream m a -> Stream m a+ghandle f stream =+ gbracket_ (return ()) return (const f) (inline MC.try) (const stream) -- | When evaluating a stream if an exception occurs, stream evaluation aborts -- and the specified exception handler is run with the exception as argument.+-- The exception is caught and handled unless the handler decides to rethrow+-- it. Note that exception handling is not applied to the stream returned by+-- the exception handler. --+-- Observes exceptions only in the stream generation, and not in stream+-- consumers.+-- -- /Inhibits stream fusion/ ---{-# INLINE handle #-}+{-# INLINE_NORMAL handle #-} handle :: (MonadCatch m, Exception e)+ => (e -> m (Stream m a)) -> Stream m a -> Stream m a+handle f stream =+ gbracket_ (return ()) return (\_ e _ -> f e) (inline MC.try) (const stream)++-- | Alternate (custom) implementation of 'handle'.+--+{-# INLINE_NORMAL _handle #-}+_handle :: (MonadCatch m, Exception e) => (e -> Stream m a) -> Stream m a -> Stream m a-handle handler xs =- fromStreamD $ D.handle (toStreamD . handler) $ toStreamD xs+_handle f (Stream step state) = Stream step' (Left state)++ where++ {-# INLINE_LATE step' #-}+ step' gst (Left st) = do+ res <- inline 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
− src/Streamly/Internal/Data/Stream/Expand.hs
@@ -1,893 +0,0 @@--- |--- Module : Streamly.Internal.Data.Stream.Expand--- Copyright : (c) 2017 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ Expand a stream by combining two or more streams or by combining streams--- with unfolds.--module Streamly.Internal.Data.Stream.Expand- (- -- * Binary Combinators (Linear)- -- | Functions ending in the shape:- --- -- @Stream m a -> Stream m a -> Stream m a@.- --- -- The functions in this section have a linear or flat n-ary combining- -- characterstics. It means that when combined @n@ times (e.g. @a `serial`- -- b `serial` c ...@) the resulting expression will have an @O(n)@- -- complexity (instead O(n^2) for pair wise combinators described in the- -- next section. These functions can be used efficiently with- -- 'concatMapWith' et. al. combinators that combine streams in a linear- -- fashion (contrast with 'mergeMapWith' which combines streams as a- -- binary tree).-- append- -- * Binary Combinators (Pair Wise)- -- | Like the functions in the section above these functions also combine- -- two streams into a single stream but when used @n@ times linearly they- -- exhibit O(n^2) complexity. They are best combined in a binary tree- -- fashion using 'mergeMapWith' giving a @n * log n@ complexity. Avoid- -- using these with 'concatMapWith' when combining a large or infinite- -- number of streams.-- -- ** Append- , append2-- -- ** Interleave- , interleave- , interleave2- , interleaveFst- , interleaveFst2- , interleaveFstSuffix2- , interleaveMin- , interleaveMin2-- -- ** Round Robin- , roundrobin-- -- ** Merge- , mergeBy- , mergeByM- , mergeByM2- , mergeMinBy- , mergeFstBy-- -- ** Zip- , zipWith- , zipWithM-- -- * Combine Streams and Unfolds- -- |- -- Expand a stream by repeatedly using an unfold and merging the resulting- -- streams. Functions generally ending in the shape:- --- -- @Unfold m a b -> Stream m a -> Stream m b@-- -- ** Unfold and combine streams- -- | Unfold and flatten streams.- , unfoldMany -- XXX Rename to unfoldAppend- , unfoldInterleave- , unfoldRoundRobin-- -- ** Interpose- -- | Insert effects between streams. Like unfoldMany but intersperses an- -- effect between the streams. A special case of gintercalate.- , interpose- , interposeSuffix- -- , interposeBy-- -- ** Intercalate- -- | Insert Streams between Streams.- -- Like unfoldMany but intersperses streams from another source between- -- the streams from the first source.- , intercalate- , intercalateSuffix- , gintercalate- , gintercalateSuffix-- -- * Combine Streams of Streams- -- | Map and serially append streams. 'concatMapM' is a generalization of- -- the binary append operation to append many streams.- , concatMapM- , concatMap- , concatEffect- , concat-- -- * ConcatMapWith- -- | Map and flatten a stream like 'concatMap' but using a custom binary- -- stream merging combinator instead of just appending the streams. The- -- merging occurs sequentially, it works efficiently for 'serial', 'async',- -- 'ahead' like merge operations where we consume one stream before the- -- next or in case of 'wAsync' or 'parallel' where we consume all streams- -- simultaneously anyway.- --- -- However, in cases where the merging consumes streams in a round robin- -- fashion, a pair wise merging using 'mergeMapWith' would be more- -- efficient. These cases include operations like 'mergeBy' or 'zipWith'.-- , concatMapWith- , bindWith- , concatSmapMWith-- -- * MergeMapWith- -- | See the notes about suitable merge functions in the 'concatMapWith'- -- section.- , mergeMapWith-- -- * Iterate- -- | Map and flatten Trees of Streams- , unfoldIterateDfs- , unfoldIterateBfs- , unfoldIterateBfsRev-- , concatIterateWith- , mergeIterateWith-- , concatIterateDfs- , concatIterateBfs-- -- More experimental ops- , concatIterateBfsRev- , concatIterateLeftsWith- , concatIterateScanWith- , concatIterateScan- )-where--#include "inline.hs"--import Streamly.Internal.Data.Stream.Bottom- ( concatEffect, concatMapM, concatMap, smapM, zipWith, zipWithM)-import Streamly.Internal.Data.Stream.Type- ( Stream, fromStreamD, fromStreamK, toStreamD, toStreamK- , bindWith, concatMapWith, cons, nil)-import Streamly.Internal.Data.Unfold.Type (Unfold)--import qualified Streamly.Internal.Data.Stream.StreamD as D-import qualified Streamly.Internal.Data.Stream.StreamK as K (mergeBy, mergeByM)-import qualified Streamly.Internal.Data.Stream.StreamK.Type as K--import Prelude hiding (concat, concatMap, zipWith)---- $setup--- >>> :m--- >>> import Data.Either (either)--- >>> import Data.IORef--- >>> import Streamly.Internal.Data.Stream (Stream)--- >>> import Prelude hiding (zipWith, concatMap, concat)--- >>> import qualified Streamly.Data.Array as Array--- >>> import qualified Streamly.Internal.Data.Fold as Fold--- >>> import qualified Streamly.Internal.Data.Stream as Stream--- >>> import qualified Streamly.Internal.Data.Unfold as Unfold--- >>> import qualified Streamly.Internal.Data.Parser as Parser--- >>> import qualified Streamly.Internal.FileSystem.Dir as Dir-------------------------------------------------------------------------------------- Appending---------------------------------------------------------------------------------infixr 6 `append2`---- | This is fused version of 'append'. It could be up to 100x faster than--- 'append' when combining two fusible streams. However, it slows down--- quadratically with the number of streams being appended. Therefore, it is--- suitable for ad-hoc append of a few streams, and should not be used with--- 'concatMapWith' or 'mergeMapWith'.------ /Fused/----{-# INLINE append2 #-}-append2 ::Monad m => Stream m b -> Stream m b -> Stream m b-append2 m1 m2 = fromStreamD $ D.append (toStreamD m1) (toStreamD m2)--infixr 6 `append`---- | Appends two streams sequentially, yielding all elements from the first--- stream, and then all elements from the second stream.------ >>> s1 = Stream.fromList [1,2]--- >>> s2 = Stream.fromList [3,4]--- >>> Stream.fold Fold.toList $ s1 `Stream.append` s2--- [1,2,3,4]------ This has O(n) append performance where @n@ is the number of streams. It can--- be used to efficiently fold an infinite lazy container of streams--- 'concatMapWith' et. al.------ See 'append2' for a fusible alternative.------ /CPS/-{-# INLINE append #-}-append :: Stream m a -> Stream m a -> Stream m a-append = (<>)----------------------------------------------------------------------------------- Interleaving---------------------------------------------------------------------------------infixr 6 `interleave`---- | 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.------ When joining many streams in a left associative manner earlier streams will--- get exponential priority than the ones joining later. Because of exponential--- weighting it can be used with 'concatMapWith' even on a large number of--- streams.------ See 'interleave2' for a fusible alternative.------ /CPS/-{-# INLINE interleave #-}-interleave :: Stream m a -> Stream m a -> Stream m a-interleave s1 s2 = fromStreamK $ K.interleave (toStreamK s1) (toStreamK s2)--{-# INLINE interleave2 #-}-interleave2 :: Monad m => Stream m a -> Stream m a -> Stream m a-interleave2 s1 s2 = fromStreamD $ D.interleave (toStreamD s1) (toStreamD s2)---- | Like `interleave` but stops interleaving as soon as the first stream--- stops.------ See 'interleaveFst2' for a fusible alternative.------ /CPS/-{-# INLINE interleaveFst #-}-interleaveFst :: Stream m a -> Stream m a -> Stream m a-interleaveFst s1 s2 =- fromStreamK $ K.interleaveFst (toStreamK s1) (toStreamK s2)---- | Like `interleave` but stops interleaving as soon as any of the two streams--- stops.------ See 'interleaveMin2' for a fusible alternative.------ /CPS/-{-# INLINE interleaveMin #-}-interleaveMin :: Stream m a -> Stream m a -> Stream m a-interleaveMin s1 s2 =- fromStreamK $ K.interleaveMin (toStreamK s1) (toStreamK s2)--{-# INLINE interleaveMin2 #-}-interleaveMin2 :: Monad m => Stream m a -> Stream m a -> Stream m a-interleaveMin2 s1 s2 =- fromStreamD $ D.interleaveMin (toStreamD s1) (toStreamD s2)---- | 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--- >>> import Data.Functor.Identity (Identity)--- >>> Stream.interleaveFstSuffix2 "abc" ",,,," :: Stream Identity Char--- fromList "a,b,c,"--- >>> Stream.interleaveFstSuffix2 "abc" "," :: Stream Identity Char--- fromList "a,bc"------ 'interleaveFstSuffix2' is a dual of 'interleaveFst2'.------ Do not use at scale in concatMapWith.------ /Pre-release/-{-# INLINE interleaveFstSuffix2 #-}-interleaveFstSuffix2 :: Monad m => Stream m b -> Stream m b -> Stream m b-interleaveFstSuffix2 m1 m2 =- fromStreamD $ D.interleaveFstSuffix (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--- >>> import Data.Functor.Identity (Identity)--- >>> Stream.interleaveFst2 "abc" ",,,," :: Stream Identity Char--- fromList "a,b,c"--- >>> Stream.interleaveFst2 "abc" "," :: Stream Identity Char--- fromList "a,bc"------ 'interleaveFst2' is a dual of 'interleaveFstSuffix2'.------ Do not use at scale in concatMapWith.------ /Pre-release/-{-# INLINE interleaveFst2 #-}-interleaveFst2 :: Monad m => Stream m b -> Stream m b -> Stream m b-interleaveFst2 m1 m2 =- fromStreamD $ D.interleaveFst (toStreamD m1) (toStreamD m2)----------------------------------------------------------------------------------- Scheduling----------------------------------------------------------------------------------- | 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.------ /Pre-release/-{-# INLINE roundrobin #-}-roundrobin :: Monad m => Stream m b -> Stream m b -> Stream m b-roundrobin m1 m2 = fromStreamD $ D.roundRobin (toStreamD m1) (toStreamD m2)----------------------------------------------------------------------------------- Merging (sorted streams)----------------------------------------------------------------------------------- | 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.------ >>> s1 = Stream.fromList [1,3,5]--- >>> s2 = Stream.fromList [2,4,6,8]--- >>> Stream.fold Fold.toList $ Stream.mergeBy compare s1 s2--- [1,2,3,4,5,6,8]------ See 'mergeByM2' for a fusible alternative.------ /CPS/-{-# INLINE mergeBy #-}-mergeBy :: (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a-mergeBy f m1 m2 = fromStreamK $ K.mergeBy f (toStreamK m1) (toStreamK m2)---- | Like 'mergeBy' but with a monadic comparison function.------ Merge two streams randomly:------ @--- > randomly _ _ = randomIO >>= \x -> return $ if x then LT else GT--- > Stream.toList $ Stream.mergeByM randomly (Stream.fromList [1,1,1,1]) (Stream.fromList [2,2,2,2])--- [2,1,2,2,2,1,1,1]--- @------ Merge two streams in a proportion of 2:1:------ >>> :{--- do--- let s1 = Stream.fromList [1,1,1,1,1,1]--- s2 = Stream.fromList [2,2,2]--- let proportionately m n = do--- ref <- newIORef $ cycle $ Prelude.concat [Prelude.replicate m LT, Prelude.replicate n GT]--- return $ \_ _ -> do--- r <- readIORef ref--- writeIORef ref $ Prelude.tail r--- return $ Prelude.head r--- f <- proportionately 2 1--- xs <- Stream.fold Fold.toList $ Stream.mergeByM f s1 s2--- print xs--- :}--- [1,1,2,1,1,2,1,1,2]------ See 'mergeByM2' for a fusible alternative.------ /CPS/-{-# INLINE mergeByM #-}-mergeByM- :: Monad m- => (a -> a -> m Ordering) -> Stream m a -> Stream m a -> Stream m a-mergeByM f m1 m2 = fromStreamK $ K.mergeByM f (toStreamK m1) (toStreamK m2)---- | Like 'mergeByM' but much faster, works best when merging statically known--- number of streams. When merging more than two streams try to merge pairs and--- pair of pairs in a tree like structure.'mergeByM' works better with variable--- number of streams being merged using 'mergeMapWith'.------ /Internal/-{-# INLINE mergeByM2 #-}-mergeByM2- :: Monad m- => (a -> a -> m Ordering) -> Stream m a -> Stream m a -> Stream m a-mergeByM2 f m1 m2 =- fromStreamD $ D.mergeByM f (toStreamD m1) (toStreamD m2)---- | Like 'mergeByM' but stops merging as soon as any of the two streams stops.------ /Unimplemented/-{-# INLINABLE mergeMinBy #-}-mergeMinBy :: -- Monad m =>- (a -> a -> m Ordering) -> Stream m a -> Stream m a -> Stream m a-mergeMinBy _f _m1 _m2 = undefined- -- fromStreamD $ D.mergeMinBy f (toStreamD m1) (toStreamD m2)---- | Like 'mergeByM' but stops merging as soon as the first stream stops.------ /Unimplemented/-{-# INLINABLE mergeFstBy #-}-mergeFstBy :: -- Monad m =>- (a -> a -> m Ordering) -> Stream m a -> Stream m a -> Stream m a-mergeFstBy _f _m1 _m2 = undefined- -- fromStreamK $ D.mergeFstBy f (toStreamD m1) (toStreamD m2)----------------------------------------------------------------------------------- Combine N Streams - unfoldMany----------------------------------------------------------------------------------- | 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.----{-# INLINE unfoldMany #-}-unfoldMany ::Monad m => Unfold m a b -> Stream m a -> Stream m b-unfoldMany u m = fromStreamD $ D.unfoldMany u (toStreamD m)---- | This does not pair streams like mergeMapWith, instead, it goes through--- each stream one by one and yields one element from each stream. After it--- goes to the last stream it reverses the traversal to come back to the first--- stream yielding elements from each stream on its way back to the first--- stream and so on.------ >>> lists = Stream.fromList [[1,1],[2,2],[3,3],[4,4],[5,5]]--- >>> interleaved = Stream.unfoldInterleave Unfold.fromList lists--- >>> Stream.fold Fold.toList interleaved--- [1,2,3,4,5,5,4,3,2,1]------ Note that this is order of magnitude more efficient than "mergeMapWith--- wSerial" because of fusion.------ /Fused/-{-# INLINE unfoldInterleave #-}-unfoldInterleave ::Monad m => Unfold m a b -> Stream m a -> Stream m b-unfoldInterleave u m =- fromStreamD $ D.unfoldInterleave u (toStreamD m)---- | 'unfoldInterleave' switches to the next stream whenever a value from a--- stream is yielded, it does not switch on a 'Skip'. So if a stream keeps--- skipping for long time other streams won't get a chance to run.--- 'unfoldRoundRobin' switches on Skip as well. So it basically schedules each--- stream fairly irrespective of whether it produces a value or not.----{-# INLINE unfoldRoundRobin #-}-unfoldRoundRobin ::Monad m => Unfold m a b -> Stream m a -> Stream m b-unfoldRoundRobin u m =- fromStreamD $ D.unfoldRoundRobin u (toStreamD m)----------------------------------------------------------------------------------- Combine N Streams - interpose----------------------------------------------------------------------------------- > 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 = Stream.interpose ' '------ /Pre-release/-{-# INLINE interpose #-}-interpose :: Monad m- => c -> Unfold m b c -> Stream m b -> Stream m c-interpose x unf str =- fromStreamD $ D.interpose x unf (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 = Stream.interposeSuffix '\n'------ /Pre-release/-{-# INLINE interposeSuffix #-}-interposeSuffix :: Monad m- => c -> Unfold m b c -> Stream m b -> Stream m c-interposeSuffix x unf str =- fromStreamD $ D.interposeSuffix x unf (toStreamD str)----------------------------------------------------------------------------------- Combine N Streams - intercalate----------------------------------------------------------------------------------- XXX we can swap the order of arguments to gintercalate so that the--- definition of unfoldMany 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.------ > unfoldMany unf str =--- > gintercalate unf str (UF.nilM (\_ -> return ())) (repeat ())---- | 'interleaveFst' followed by unfold and concat.------ /Pre-release/-{-# INLINE gintercalate #-}-gintercalate- :: Monad m- => Unfold m a c -> Stream m a -> Unfold m b c -> Stream m b -> Stream m c-gintercalate unf1 str1 unf2 str2 =- fromStreamD $ D.gintercalate- unf1 (toStreamD str1)- unf2 (toStreamD str2)---- > intercalate unf seed str = gintercalate unf str unf (repeatM seed)---- | 'intersperse' followed by unfold and concat.------ >>> intercalate u a = Stream.unfoldMany u . Stream.intersperse a--- >>> intersperse = Stream.intercalate Unfold.identity--- >>> unwords = Stream.intercalate Unfold.fromList " "------ >>> input = Stream.fromList ["abc", "def", "ghi"]--- >>> Stream.fold Fold.toList $ Stream.intercalate Unfold.fromList " " input--- "abc def ghi"----{-# INLINE intercalate #-}-intercalate :: Monad m- => Unfold m b c -> b -> Stream m b -> Stream m c-intercalate unf seed str = fromStreamD $- D.unfoldMany unf $ D.intersperse seed (toStreamD str)---- | 'interleaveFstSuffix2' followed by unfold and concat.------ /Pre-release/-{-# INLINE gintercalateSuffix #-}-gintercalateSuffix- :: Monad m- => Unfold m a c -> Stream m a -> Unfold m b c -> Stream m b -> Stream m c-gintercalateSuffix unf1 str1 unf2 str2 =- fromStreamD $ D.gintercalateSuffix- unf1 (toStreamD str1)- unf2 (toStreamD str2)---- > intercalateSuffix unf seed str = gintercalateSuffix unf str unf (repeatM seed)---- | 'intersperseMSuffix' followed by unfold and concat.------ >>> intercalateSuffix u a = Stream.unfoldMany u . Stream.intersperseMSuffix a--- >>> intersperseMSuffix = Stream.intercalateSuffix Unfold.identity--- >>> unlines = Stream.intercalateSuffix Unfold.fromList "\n"------ >>> input = Stream.fromList ["abc", "def", "ghi"]--- >>> Stream.fold Fold.toList $ Stream.intercalateSuffix Unfold.fromList "\n" input--- "abc\ndef\nghi\n"----{-# INLINE intercalateSuffix #-}-intercalateSuffix :: Monad m- => Unfold m b c -> b -> Stream m b -> Stream m c-intercalateSuffix unf seed =- fromStreamD . D.intercalateSuffix unf seed . toStreamD----------------------------------------------------------------------------------- Combine N Streams - concatMap----------------------------------------------------------------------------------- | Flatten a stream of streams to a single stream.------ >>> concat = Stream.concatMap id------ /Pre-release/-{-# INLINE concat #-}-concat :: Monad m => Stream m (Stream m a) -> Stream m a-concat = concatMap id----------------------------------------------------------------------------------- Combine N Streams - concatMap----------------------------------------------------------------------------------- | Like 'concatMapWith' but carries a state which can be used to share--- information across multiple steps of concat.------ >>> concatSmapMWith combine f initial = Stream.concatMapWith combine id . Stream.smapM f initial------ /Pre-release/----{-# INLINE concatSmapMWith #-}-concatSmapMWith- :: Monad m- => (Stream m b -> Stream m b -> Stream m b)- -> (s -> a -> m (s, Stream m b))- -> m s- -> Stream m a- -> Stream m b-concatSmapMWith combine f initial =- concatMapWith combine id . smapM f initial---- XXX Implement a StreamD version for fusion.---- | Combine streams in pairs using a binary combinator, the resulting streams--- are then combined again in pairs recursively until we get to a single--- combined stream. The composition would thus form a binary tree.------ For example, you can sort a stream using merge sort like this:------ >>> s = Stream.fromList [5,1,7,9,2]--- >>> generate = Stream.fromPure--- >>> combine = Stream.mergeBy compare--- >>> Stream.fold Fold.toList $ Stream.mergeMapWith combine generate s--- [1,2,5,7,9]------ Note that if the stream length is not a power of 2, the binary tree composed--- by mergeMapWith would not be balanced, which may or may not be important--- depending on what you are trying to achieve.------ /Caution: the stream of streams must be finite/------ /CPS/------ /Pre-release/----{-# INLINE mergeMapWith #-}-mergeMapWith ::- (Stream m b -> Stream m b -> Stream m b)- -> (a -> Stream m b)- -> Stream m a- -> Stream m b-mergeMapWith par f m =- fromStreamK- $ K.mergeMapWith- (\s1 s2 -> toStreamK $ fromStreamK s1 `par` fromStreamK s2)- (toStreamK . f)- (toStreamK m)----------------------------------------------------------------------------------- concatIterate - Map and flatten Trees of Streams----------------------------------------------------------------------------------- | Yield an input element in the output stream, map a stream generator on it--- and repeat the process on the resulting stream. Resulting streams are--- flattened using the 'concatMapWith' combinator. This can be used for a depth--- first style (DFS) traversal of a tree like structure.------ Example, list a directory tree using DFS:------ >>> f = either Dir.readEitherPaths (const Stream.nil)--- >>> input = Stream.fromPure (Left ".")--- >>> ls = Stream.concatIterateWith Stream.append f input------ Note that 'iterateM' is a special case of 'concatIterateWith':------ >>> iterateM f = Stream.concatIterateWith Stream.append (Stream.fromEffect . f) . Stream.fromEffect------ /CPS/------ /Pre-release/----{-# INLINE concatIterateWith #-}-concatIterateWith ::- (Stream m a -> Stream m a -> Stream m a)- -> (a -> Stream m a)- -> Stream m a- -> Stream m a-concatIterateWith combine f = iterateStream-- where-- iterateStream = concatMapWith combine generate-- generate x = x `cons` iterateStream (f x)---- | Traverse the stream in depth first style (DFS). Map each element in the--- input stream to a stream and flatten, recursively map the resulting elements--- as well to a stream and flatten until no more streams are generated.------ Example, list a directory tree using DFS:------ >>> f = either (Just . Dir.readEitherPaths) (const Nothing)--- >>> input = Stream.fromPure (Left ".")--- >>> ls = Stream.concatIterateDfs f input------ This is equivalent to using @concatIterateWith Stream.append@.------ /Pre-release/-{-# INLINE concatIterateDfs #-}-concatIterateDfs :: Monad m =>- (a -> Maybe (Stream m a))- -> Stream m a- -> Stream m a-concatIterateDfs f stream =- fromStreamD- $ D.concatIterateDfs (fmap toStreamD . f ) (toStreamD stream)---- | Similar to 'concatIterateDfs' except that it traverses the stream in--- breadth first style (BFS). First, all the elements in the input stream are--- emitted, and then their traversals are emitted.------ Example, list a directory tree using BFS:------ >>> f = either (Just . Dir.readEitherPaths) (const Nothing)--- >>> input = Stream.fromPure (Left ".")--- >>> ls = Stream.concatIterateBfs f input------ /Pre-release/-{-# INLINE concatIterateBfs #-}-concatIterateBfs :: Monad m =>- (a -> Maybe (Stream m a))- -> Stream m a- -> Stream m a-concatIterateBfs f stream =- fromStreamD- $ D.concatIterateBfs (fmap toStreamD . f ) (toStreamD stream)---- | Same as 'concatIterateBfs' except that the traversal of the last--- element on a level is emitted first and then going backwards up to the first--- element (reversed ordering). This may be slightly faster than--- 'concatIterateBfs'.----{-# INLINE concatIterateBfsRev #-}-concatIterateBfsRev :: Monad m =>- (a -> Maybe (Stream m a))- -> Stream m a- -> Stream m a-concatIterateBfsRev f stream =- fromStreamD- $ D.concatIterateBfsRev (fmap toStreamD . f ) (toStreamD stream)---- | Like 'concatIterateWith' but uses the pairwise flattening combinator--- 'mergeMapWith' for flattening the resulting streams. This can be used for a--- balanced traversal of a tree like structure.------ Example, list a directory tree using balanced traversal:------ >>> f = either Dir.readEitherPaths (const Stream.nil)--- >>> input = Stream.fromPure (Left ".")--- >>> ls = Stream.mergeIterateWith Stream.interleave f input------ /CPS/------ /Pre-release/----{-# INLINE mergeIterateWith #-}-mergeIterateWith ::- (Stream m a -> Stream m a -> Stream m a)- -> (a -> Stream m a)- -> Stream m a- -> Stream m a-mergeIterateWith combine f = iterateStream-- where-- iterateStream = mergeMapWith combine generate-- generate x = x `cons` iterateStream (f x)---- | Same as @concatIterateDfs@ but more efficient due to stream fusion.------ Example, list a directory tree using DFS:------ >>> f = Unfold.either Dir.eitherReaderPaths Unfold.nil--- >>> input = Stream.fromPure (Left ".")--- >>> ls = Stream.unfoldIterateDfs f input------ /Pre-release/-{-# INLINE unfoldIterateDfs #-}-unfoldIterateDfs :: Monad m => Unfold m a a -> Stream m a -> Stream m a-unfoldIterateDfs u = fromStreamD . D.unfoldIterateDfs u . toStreamD---- | Like 'unfoldIterateDfs' but uses breadth first style traversal.------ /Pre-release/-{-# INLINE unfoldIterateBfs #-}-unfoldIterateBfs :: Monad m => Unfold m a a -> Stream m a -> Stream m a-unfoldIterateBfs u = fromStreamD . D.unfoldIterateBfs u . toStreamD---- | Like 'unfoldIterateBfs' but processes the children in reverse order,--- therefore, may be slightly faster.------ /Pre-release/-{-# INLINE unfoldIterateBfsRev #-}-unfoldIterateBfsRev :: Monad m => Unfold m a a -> Stream m a -> Stream m a-unfoldIterateBfsRev u =- fromStreamD . D.unfoldIterateBfsRev u . toStreamD----------------------------------------------------------------------------------- Flattening Graphs----------------------------------------------------------------------------------- To traverse graphs we need a state to be carried around in the traversal.--- For example, we can use a hashmap to store the visited status of nodes.---- | Like 'iterateMap' but carries a state in the stream generation function.--- This can be used to traverse graph like structures, we can remember the--- visited nodes in the state to avoid cycles.------ Note that a combination of 'iterateMap' and 'usingState' can also be used to--- traverse graphs. However, this function provides a more localized state--- instead of using a global state.------ See also: 'mfix'------ /Pre-release/----{-# INLINE concatIterateScanWith #-}-concatIterateScanWith- :: Monad m- => (Stream m a -> Stream m a -> Stream m a)- -> (b -> a -> m (b, Stream m a))- -> m b- -> Stream m a- -> Stream m a-concatIterateScanWith combine f initial stream =- concatEffect $ do- b <- initial- iterateStream (b, stream)-- where-- iterateStream (b, s) = pure $ concatMapWith combine (generate b) s-- generate b a = a `cons` feedback b a-- feedback b a = concatEffect $ f b a >>= iterateStream---- Next stream is to be generated by the return value of the previous stream. A--- general intuitive way of doing that could be to use an appending monad--- instance for streams where the result of the previous stream is used to--- generate the next one. In the first pass we can just emit the values in the--- stream and keep building a buffered list/stream, once done we can then--- process the buffered stream.--{-# INLINE concatIterateScan #-}-concatIterateScan- :: Monad m- => (b -> a -> m b)- -> (b -> m (Maybe (b, Stream m a)))- -> b- -> Stream m a-concatIterateScan scanner generate initial =- fromStreamD- $ D.concatIterateScan- scanner (fmap (fmap (fmap toStreamD)) . generate) initial----------------------------------------------------------------------------------- Either streams----------------------------------------------------------------------------------- Keep concating either streams as long as rights are generated, stop as soon--- as a left is generated and concat the left stream.------ See also: 'handle'------ /Unimplemented/----{--concatMapEitherWith- :: (forall x. t m x -> t m x -> t m x)- -> (a -> t m (Either (Stream m b) b))- -> Stream m a- -> Stream m b-concatMapEitherWith = undefined--}---- XXX We should prefer using the Maybe stream returning signatures over this.--- This API should perhaps be removed in favor of those.---- | In an 'Either' stream iterate on 'Left's. This is a special case of--- 'concatIterateWith':------ >>> concatIterateLeftsWith combine f = Stream.concatIterateWith combine (either f (const Stream.nil))------ To traverse a directory tree:------ >>> input = Stream.fromPure (Left ".")--- >>> ls = Stream.concatIterateLeftsWith Stream.append Dir.readEither input------ /Pre-release/----{-# INLINE concatIterateLeftsWith #-}-concatIterateLeftsWith- :: (b ~ Either a c)- => (Stream m b -> Stream m b -> Stream m b)- -> (a -> Stream m b)- -> Stream m b- -> Stream m b-concatIterateLeftsWith combine f =- concatIterateWith combine (either f (const nil))
src/Streamly/Internal/Data/Stream/Generate.hs view
@@ -1,460 +1,1214 @@-{-# OPTIONS_GHC -Wno-orphans #-}---- |--- Module : Streamly.Internal.Data.Stream.Generate--- Copyright : (c) 2017 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC----module Streamly.Internal.Data.Stream.Generate- (- -- * Primitives- Stream.nil- , Stream.nilM- , Stream.cons- , Stream.consM-- -- * From 'Unfold'- , unfold-- -- * Unfolding- , unfoldr- , unfoldrM-- -- * From Values- , Stream.fromPure- , Stream.fromEffect- , repeat- , repeatM- , replicate- , replicateM-- -- * Enumeration- , Enumerable (..)- , enumerate- , enumerateTo-- -- * Time Enumeration- , times- , timesWith- , absTimes- , absTimesWith- , relTimes- , relTimesWith- , durations- , timeout-- -- * Iteration- , iterate- , iterateM-- -- * Cyclic Elements- , mfix-- -- * From Containers- , Bottom.fromList- , fromFoldable-- -- * From memory- , fromPtr- , fromPtrN- , fromByteStr#- -- , fromByteArray#- , fromUnboxedIORef- )-where--#include "inline.hs"--import Control.Monad.IO.Class (MonadIO)-import Data.Word (Word8)-import Foreign.Storable (Storable)-import GHC.Exts (Addr#, Ptr (Ptr))-import Streamly.Internal.Data.Stream.Bottom- (absTimesWith, relTimesWith, timesWith)-import Streamly.Internal.Data.Stream.Enumerate- (Enumerable(..), enumerate, enumerateTo)-import Streamly.Internal.Data.Stream.Type- (Stream, fromStreamD, fromStreamK, toStreamK)-import Streamly.Internal.Data.Time.Units (AbsTime, RelTime64, addToAbsTime64)-import Streamly.Internal.Data.Unboxed (Unbox)-import Streamly.Internal.Data.Unfold.Type (Unfold)--import qualified Streamly.Internal.Data.IORef.Unboxed as Unboxed-import qualified Streamly.Internal.Data.Stream.Bottom as Bottom-import qualified Streamly.Internal.Data.Stream.StreamD as D-import qualified Streamly.Internal.Data.Stream.StreamK.Type as K-import qualified Streamly.Internal.Data.Stream.Type as Stream-import qualified Streamly.Internal.Data.Stream.Transform as Stream (sequence)--import Prelude hiding (iterate, replicate, repeat, take)---- $setup--- >>> :m--- >>> import Control.Concurrent (threadDelay)--- >>> import Data.Function (fix, (&))--- >>> import Data.Semigroup (cycle1)--- >>> import Streamly.Internal.Data.Stream.Cross (CrossStream(..))--- >>> import qualified Streamly.Data.Fold as Fold--- >>> import qualified Streamly.Data.Unfold as Unfold--- >>> import qualified Streamly.Internal.Data.Stream as Stream--- >>> import GHC.Exts (Ptr (Ptr))----------------------------------------------------------------------------------- From Unfold----------------------------------------------------------------------------------- | Convert an 'Unfold' into a stream by supplying it an input seed.------ >>> s = Stream.unfold Unfold.replicateM (3, putStrLn "hello")--- >>> Stream.fold Fold.drain s--- hello--- hello--- hello----{-# INLINE unfold #-}-unfold :: Monad m => Unfold m a b -> a -> Stream m b-unfold unf = Stream.fromStreamD . D.unfold unf----------------------------------------------------------------------------------- Generation by Unfolding----------------------------------------------------------------------------------- |--- >>> :{--- unfoldr step s =--- case step s of--- Nothing -> Stream.nil--- Just (a, b) -> a `Stream.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 > 2--- then Nothing--- else Just (b, b + 1)--- in Stream.fold Fold.toList $ Stream.unfoldr f 0--- :}--- [0,1,2]----{-# INLINE_EARLY unfoldr #-}-unfoldr :: Monad m => (b -> Maybe (a, b)) -> b -> Stream m a-unfoldr step seed = fromStreamD (D.unfoldr step seed)-{-# RULES "unfoldr fallback to StreamK" [1]- forall a b. D.toStreamK (D.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 > 2--- then return Nothing--- else return (Just (b, b + 1))--- in Stream.fold Fold.toList $ Stream.unfoldrM f 0--- :}--- [0,1,2]----{-# INLINE unfoldrM #-}-unfoldrM :: Monad m => (b -> m (Maybe (a, b))) -> b -> Stream m a-unfoldrM step = fromStreamD . D.unfoldrM step----------------------------------------------------------------------------------- From Values----------------------------------------------------------------------------------- |--- Generate an infinite stream by repeating a pure value.----{-# INLINE_NORMAL repeat #-}-repeat :: Monad m => a -> Stream m a-repeat = fromStreamD . D.repeat---- |--- >>> repeatM = Stream.sequence . Stream.repeat--- >>> repeatM = fix . Stream.consM--- >>> repeatM = cycle1 . Stream.fromEffect------ Generate a stream by repeatedly executing a monadic action forever.------ >>> :{--- repeatAction =--- Stream.repeatM (threadDelay 1000000 >> print 1)--- & Stream.take 10--- & Stream.fold Fold.drain--- :}----{-# INLINE_NORMAL repeatM #-}-repeatM :: Monad m => m a -> Stream m a-repeatM = Stream.sequence . repeat---- |--- >>> replicate n = Stream.take n . Stream.repeat------ Generate a stream of length @n@ by repeating a value @n@ times.----{-# INLINE_NORMAL replicate #-}-replicate :: Monad m => Int -> a -> Stream m a-replicate n = fromStreamD . D.replicate n---- |--- >>> replicateM n = Stream.sequence . Stream.replicate n------ Generate a stream by performing a monadic action @n@ times.-{-# INLINE_NORMAL replicateM #-}-replicateM :: Monad m => Int -> m a -> Stream m a-replicateM n = Stream.sequence . replicate n----------------------------------------------------------------------------------- Time Enumeration----------------------------------------------------------------------------------- | @times@ returns a stream of time value tuples with clock of 10 ms--- granularity. The first component of the tuple is an absolute time reference--- (epoch) denoting the start of the stream and the second component is a time--- relative to the reference.------ >>> f = Fold.drainMapM (\x -> print x >> threadDelay 1000000)--- >>> Stream.fold f $ Stream.take 3 $ Stream.times--- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))--- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))--- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))------ Note: This API is not safe on 32-bit machines.------ /Pre-release/----{-# INLINE times #-}-times :: MonadIO m => Stream m (AbsTime, RelTime64)-times = timesWith 0.01---- | @absTimes@ returns a stream of absolute timestamps using a clock of 10 ms--- granularity.------ >>> f = Fold.drainMapM print--- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.absTimes--- AbsTime (TimeSpec {sec = ..., nsec = ...})--- AbsTime (TimeSpec {sec = ..., nsec = ...})--- AbsTime (TimeSpec {sec = ..., nsec = ...})------ Note: This API is not safe on 32-bit machines.------ /Pre-release/----{-# INLINE absTimes #-}-absTimes :: MonadIO m => Stream m AbsTime-absTimes = fmap (uncurry addToAbsTime64) times---- | @relTimes@ returns a stream of relative time values starting from 0,--- using a clock of granularity 10 ms.------ >>> f = Fold.drainMapM print--- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.relTimes--- RelTime64 (NanoSecond64 ...)--- RelTime64 (NanoSecond64 ...)--- RelTime64 (NanoSecond64 ...)------ Note: This API is not safe on 32-bit machines.------ /Pre-release/----{-# INLINE relTimes #-}-relTimes :: MonadIO m => Stream m RelTime64-relTimes = fmap snd times---- | @durations g@ returns a stream of relative time values measuring the time--- elapsed since the immediate predecessor element of the stream was generated.--- The first element of the stream is always 0. @durations@ uses a clock of--- granularity @g@ specified in seconds. A low granularity clock is more--- expensive in terms of CPU usage. The minimum granularity is 1 millisecond.--- Durations lower than 1 ms will be 0.------ Note: This API is not safe on 32-bit machines.------ /Unimplemented/----{-# INLINE durations #-}-durations :: -- Monad m =>- Double -> t m RelTime64-durations = undefined---- | Generate a singleton event at or after the specified absolute time. Note--- that this is different from a threadDelay, a threadDelay starts from the--- time when the action is evaluated, whereas if we use AbsTime based timeout--- it will immediately expire if the action is evaluated too late.------ /Unimplemented/----{-# INLINE timeout #-}-timeout :: -- Monad m =>- AbsTime -> t m ()-timeout = undefined----------------------------------------------------------------------------------- Iterating functions----------------------------------------------------------------------------------- |--- >>> iterate f x = x `Stream.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.------ >>> Stream.fold Fold.toList $ Stream.take 5 $ Stream.iterate (+1) 1--- [1,2,3,4,5]----{-# INLINE_NORMAL iterate #-}-iterate :: Monad m => (a -> a) -> a -> Stream m a-iterate step = fromStreamD . D.iterate step---- |--- >>> iterateM f m = m >>= \a -> return a `Stream.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.------ >>> :{--- Stream.iterateM (\x -> print x >> return (x + 1)) (return 0)--- & Stream.take 3--- & Stream.fold Fold.toList--- :}--- 0--- 1--- [0,1,2]----{-# INLINE iterateM #-}-iterateM :: Monad m => (a -> m a) -> m a -> Stream m a-iterateM step = fromStreamD . D.iterateM step---- | We can define cyclic structures using @let@:------ >>> let (a, b) = ([1, b], head a) in (a, b)--- ([1,1],1)------ The function @fix@ defined as:------ >>> fix f = let x = f x in x------ ensures that the argument of a function and its output refer to the same--- lazy value @x@ i.e. the same location in memory. Thus @x@ can be defined--- in terms of itself, creating structures with cyclic references.------ >>> f ~(a, b) = ([1, b], head a)--- >>> fix f--- ([1,1],1)------ 'Control.Monad.mfix' is essentially the same as @fix@ but for monadic--- values.------ Using 'mfix' for streams we can construct a stream in which each element of--- the stream is defined in a cyclic fashion. The argument of the function--- being fixed represents the current element of the stream which is being--- returned by the stream monad. Thus, we can use the argument to construct--- itself.------ In the following example, the argument @action@ of the function @f@--- represents the tuple @(x,y)@ returned by it in a given iteration. We define--- the first element of the tuple in terms of the second.------ >>> import Streamly.Internal.Data.Stream as Stream--- >>> import System.IO.Unsafe (unsafeInterleaveIO)------ >>> :{--- main = Stream.fold (Fold.drainMapM print) $ Stream.mfix f--- where--- f action = unCrossStream $ do--- let incr n act = fmap ((+n) . snd) $ unsafeInterleaveIO act--- x <- CrossStream (Stream.sequence $ Stream.fromList [incr 1 action, incr 2 action])--- y <- CrossStream (Stream.fromList [4,5])--- return (x, y)--- :}------ Note: you cannot achieve this by just changing the order of the monad--- statements because that would change the order in which the stream elements--- are generated.------ Note that the function @f@ must be lazy in its argument, that's why we use--- 'unsafeInterleaveIO' on @action@ because IO monad is strict.------ /CPS/------ /Pre-release/-{-# INLINE mfix #-}-mfix :: Monad m => (m a -> Stream m a) -> Stream m a-mfix f = fromStreamK $ K.mfix (toStreamK . f)----------------------------------------------------------------------------------- Conversions----------------------------------------------------------------------------------- |--- >>> fromFoldable = Prelude.foldr Stream.cons Stream.nil------ Construct a stream from a 'Foldable' containing pure values:------ /CPS/----{-# INLINE fromFoldable #-}-fromFoldable :: Foldable f => f a -> Stream m a-fromFoldable = fromStreamK . K.fromFoldable----------------------------------------------------------------------------------- From pointers----------------------------------------------------------------------------------- | Keep reading 'Storable' elements from 'Ptr' onwards.------ /Unsafe:/ The caller is responsible for safe addressing.------ /Pre-release/-{-# INLINE fromPtr #-}-fromPtr :: (MonadIO m, Storable a) => Ptr a -> Stream m a-fromPtr = Stream.fromStreamD . D.fromPtr---- | Take @n@ 'Storable' elements starting from 'Ptr' onwards.------ >>> fromPtrN n = Stream.take n . Stream.fromPtr------ /Unsafe:/ The caller is responsible for safe addressing.------ /Pre-release/-{-# INLINE fromPtrN #-}-fromPtrN :: (MonadIO m, Storable a) => Int -> Ptr a -> Stream m a-fromPtrN n = Stream.fromStreamD . D.take n . D.fromPtr---- | Read bytes from an 'Addr#' until a 0 byte is encountered, the 0 byte is--- not included in the stream.------ >>> fromByteStr# addr = Stream.takeWhile (/= 0) $ Stream.fromPtr $ Ptr addr------ /Unsafe:/ The caller is responsible for safe addressing.------ Note that this is completely safe when reading from Haskell string--- literals because they are guaranteed to be NULL terminated:------ >>> Stream.fold Fold.toList $ Stream.fromByteStr# "\1\2\3\0"#--- [1,2,3]----{-# INLINE fromByteStr# #-}-fromByteStr# :: MonadIO m => Addr# -> Stream m Word8-fromByteStr# addr =- Stream.fromStreamD $ D.takeWhile (/= 0) $ D.fromPtr $ Ptr addr---- | Construct a stream by reading an 'Unboxed' 'IORef' repeatedly.------ /Pre-release/----{-# INLINE fromUnboxedIORef #-}-fromUnboxedIORef :: (MonadIO m, Unbox a) => Unboxed.IORef a -> Stream m a-fromUnboxedIORef = fromStreamD . Unboxed.toStreamD+{-# LANGUAGE CPP #-}+-- |+-- Module : Streamly.Internal.Data.Stream.Generate+-- Copyright : (c) 2020 Composewell Technologies and Contributors+-- (c) Roman Leshchinskiy 2008-2010+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--++-- A few combinators in this module have been adapted from the vector package+-- (c) Roman Leshchinskiy. See the notes in specific combinators.+--+module Streamly.Internal.Data.Stream.Generate+ (+ -- * Primitives+ nil+ , cons++ -- * Unfolding+ , unfoldr+ , unfoldrM++ -- * From Values+ , repeat+ , repeatM+ , replicate+ , replicateM++ -- * Enumeration+ -- ** Enumerating 'Num' Types+ , enumerateFromStepNum+ , enumerateFromNum+ , enumerateFromThenNum++ -- ** 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++ -- ** Enumerable Type Class+ , Enumerable(..)++ -- * Time Enumeration+ , times+ , timesWith+ , absTimes+ , absTimesWith+ , relTimes+ , relTimesWith+ , durations+ , timeout++ -- * From Generators+ -- | Generate a monadic stream from a seed.+ , fromIndices+ , fromIndicesM+ , generate+ , generateM++ -- * Iteration+ , iterate+ , iterateM++ -- * From Containers+ -- | Transform an input structure into a stream.++ , fromListM+ , fromFoldable+ , fromFoldableM++ -- * From Pointers+ , fromPtr+ , fromPtrN+ , fromCString#+ , fromW16CString#++ -- * Deprecated+ , fromByteStr#+ )+where++#include "inline.hs"+#include "ArrayMacros.h"++import Control.Monad.IO.Class (MonadIO(..))+import Data.Functor.Identity (Identity(..))+import Foreign.Ptr (Ptr, plusPtr)+import Foreign.Storable (Storable (peek), sizeOf)+import GHC.Exts (Addr#, Ptr (Ptr))+import Streamly.Internal.Data.Time.Clock+ (Clock(Monotonic), asyncClock, readClock)+import Streamly.Internal.Data.Time.Units+ (toAbsTime, AbsTime, toRelTime64, RelTime64, addToAbsTime64)+import Streamly.Internal.System.IO (unsafeInlineIO)++#ifdef USE_UNFOLDS_EVERYWHERE+import qualified Streamly.Internal.Data.Unfold as Unfold+#endif++import Data.Fixed+import Data.Int+import Data.Ratio+import Data.Word+import Numeric.Natural+import Prelude hiding (iterate, repeat, replicate, take, takeWhile)+import Streamly.Internal.Data.Stream.Type++#include "DocTestDataStream.hs"++------------------------------------------------------------------------------+-- Primitives+------------------------------------------------------------------------------++-- XXX implement in terms of nilM?++-- | A stream that terminates without producing any output or side effect.+--+-- >>> Stream.toList Stream.nil+-- []+--+{-# INLINE_NORMAL nil #-}+nil :: Applicative m => Stream m a+nil = Stream (\_ _ -> pure Stop) ()++infixr 5 `cons`++-- XXX implement in terms of consM?+-- cons x = consM (return x)+-- From an implementation perspective, StreamK.cons translates into a+-- functional call whereas fused cons translates into a conditional branch+-- (jump). However, the overhead of the function call in StreamK.cons only+-- occurs once, while the overhead of the conditional branch in fused cons is+-- incurred for each subsequent element in the stream. As a result,+-- StreamK.cons has a time complexity of O(n), while fused cons has a time+-- complexity of O(n^2), where @n@ represents the number of 'cons' used.++-- When composing a few elements together statically, a balanced tree composed+-- using 'cons' and 'append' is more efficient than a right associated one+-- composed using 'cons':+--+-- >>> s1 = 1 `Stream.cons` 2 `Stream.cons` Stream.nil+-- >>> s2 = 2 `Stream.cons` 3 `Stream.cons` Stream.nil+-- >>> s = s1 `Stream.append` s2+--+-- However, generating a stream using a case statement or indexing into a+-- static literal array would be the best. Check if the case statement+-- translates to a look up table or a binary search.++-- | WARNING! O(n^2) time complexity wrt number of elements. Use the O(n)+-- complexity StreamK.'Streamly.Data.StreamK.cons' unless you want to+-- statically fuse just a few elements.+--+-- Fuse a pure value at the head of an existing stream::+--+-- >>> s = 1 `Stream.cons` Stream.fromList [2,3]+-- >>> Stream.toList s+-- [1,2,3]+--+-- Definition:+--+-- >>> cons x xs = return x `Stream.consM` xs+--+{-# INLINE_NORMAL cons #-}+cons :: Applicative m => a -> Stream m a -> Stream m a+cons x (Stream step state) = Stream step1 Nothing+ where+ {-# INLINE_LATE step1 #-}+ step1 _ Nothing = pure $ Yield x (Just state)+ step1 gst (Just st) = do+ (\case+ Yield a s -> Yield a (Just s)+ Skip s -> Skip (Just s)+ Stop -> Stop) <$> step gst st++------------------------------------------------------------------------------+-- Unfolding+------------------------------------------------------------------------------++-- Adapted from vector package++-- | 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 > 2+-- then return Nothing+-- else return (Just (b, b + 1))+-- in Stream.toList $ Stream.unfoldrM f 0+-- :}+-- [0,1,2]+--+{-# INLINE_NORMAL unfoldrM #-}+unfoldrM :: Monad m => (s -> m (Maybe (a, s))) -> s -> Stream m a+#ifdef USE_UNFOLDS_EVERYWHERE+unfoldrM next = unfold (Unfold.unfoldrM next)+#else+unfoldrM next = Stream step+ where+ {-# INLINE_LATE step #-}+ step _ st = do+ r <- next st+ return $ case r of+ Just (x, s) -> Yield x s+ Nothing -> Stop+#endif++-- | 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 > 2+-- then Nothing+-- else Just (b, b + 1)+-- in Stream.toList $ Stream.unfoldr f 0+-- :}+-- [0,1,2]+--+{-# INLINE_LATE unfoldr #-}+unfoldr :: Monad m => (s -> Maybe (a, s)) -> s -> Stream m a+unfoldr f = unfoldrM (return . f)++------------------------------------------------------------------------------+-- From values+------------------------------------------------------------------------------++-- |+-- >>> repeatM act = Stream.iterateM (const act) act+-- >>> repeatM = Stream.sequence . Stream.repeat+--+-- Generate a stream by repeatedly executing a monadic action forever.+--+-- >>> :{+-- repeatAction =+-- Stream.repeatM (threadDelay 1000000 >> print 1)+-- & Stream.take 10+-- & Stream.fold Fold.drain+-- :}+--+{-# INLINE_NORMAL repeatM #-}+repeatM :: Monad m => m a -> Stream m a+#ifdef USE_UNFOLDS_EVERYWHERE+repeatM = unfold Unfold.repeatM+#else+repeatM x = Stream (\_ _ -> x >>= \r -> return $ Yield r ()) ()+#endif++-- |+-- Generate an infinite stream by repeating a pure value.+--+-- >>> repeat = Stream.iterate id+-- >>> repeat x = Stream.repeatM (pure x)+--+{-# INLINE_NORMAL repeat #-}+repeat :: Monad m => a -> Stream m a+#ifdef USE_UNFOLDS_EVERYWHERE+repeat x = repeatM (pure x)+#else+repeat x = Stream (\_ _ -> return $ Yield x ()) ()+#endif++-- Adapted from the vector package++-- |+-- >>> replicateM n = Stream.sequence . Stream.replicate n+--+-- Generate a stream by performing a monadic action @n@ times.+{-# INLINE_NORMAL replicateM #-}+replicateM :: Monad m => Int -> m a -> Stream m a+#ifdef USE_UNFOLDS_EVERYWHERE+replicateM n p = unfold Unfold.replicateM (n, p)+#else+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)+#endif++-- |+-- >>> replicate n = Stream.take n . Stream.repeat+-- >>> replicate n x = Stream.replicateM n (pure x)+--+-- Generate a stream of length @n@ by repeating a value @n@ times.+--+{-# INLINE_NORMAL replicate #-}+replicate :: Monad m => Int -> a -> Stream m a+replicate n x = replicateM n (return x)++------------------------------------------------------------------------------+-- Enumeration of Num+------------------------------------------------------------------------------++-- | 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+#ifdef USE_UNFOLDS_EVERYWHERE+enumerateFromStepNum from stride =+ unfold Unfold.enumerateFromStepNum (from, stride)+#else+enumerateFromStepNum from stride = Stream step 0+ where+ {-# INLINE_LATE step #-}+ step _ !i = return $ (Yield $! (from + i * stride)) $! (i + 1)+#endif++{-# INLINE_NORMAL enumerateFromNum #-}+enumerateFromNum :: (Monad m, Num a) => a -> Stream m a+enumerateFromNum from = enumerateFromStepNum from 1++{-# INLINE_NORMAL enumerateFromThenNum #-}+enumerateFromThenNum :: (Monad m, Num a) => a -> a -> Stream m a+enumerateFromThenNum from next = enumerateFromStepNum from (next - from)++------------------------------------------------------------------------------+-- Enumeration of Integrals+------------------------------------------------------------------------------++#ifndef USE_UNFOLDS_EVERYWHERE+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+#endif++-- XXX This can perhaps be simplified and written in terms of+-- enumeratFromStepIntegral as we have done in unfolds.++-- | 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@.+--+-- >>> Stream.toList $ Stream.enumerateFromThenToIntegral 0 2 6+-- [0,2,4,6]+--+-- >>> Stream.toList $ Stream.enumerateFromThenToIntegral 0 (-2) (-6)+-- [0,-2,-4,-6]+--+{-# INLINE_NORMAL enumerateFromThenToIntegral #-}+enumerateFromThenToIntegral+ :: (Monad m, Integral a)+ => a -> a -> a -> Stream m a+#ifdef USE_UNFOLDS_EVERYWHERE+enumerateFromThenToIntegral from next to =+ unfold Unfold.enumerateFromThenToIntegral (from, next, to)+#else+enumerateFromThenToIntegral from next to+ | next >= from = enumerateFromThenToIntegralUp from next to+ | otherwise = enumerateFromThenToIntegralDn from next to+#endif++-- | 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.+--+-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenIntegral (0 :: Int) 2+-- [0,2,4,6]+--+-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenIntegral (0 :: Int) (-2)+-- [0,-2,-4,-6]+--+{-# INLINE_NORMAL enumerateFromThenIntegral #-}+enumerateFromThenIntegral+ :: (Monad m, Integral a, Bounded a)+ => a -> a -> Stream m a+#ifdef USE_UNFOLDS_EVERYWHERE+enumerateFromThenIntegral from next =+ unfold Unfold.enumerateFromThenIntegralBounded (from, next)+#else+enumerateFromThenIntegral from next =+ if next > from+ then enumerateFromThenToIntegralUp from next maxBound+ else enumerateFromThenToIntegralDn from next minBound+#endif++-- | @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.+--+-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromStepIntegral 0 2+-- [0,2,4,6]+--+-- >>> Stream.toList $ Stream.take 3 $ Stream.enumerateFromStepIntegral 0 (-2)+-- [0,-2,-4]+--+{-# INLINE_NORMAL enumerateFromStepIntegral #-}+enumerateFromStepIntegral :: (Integral a, Monad m) => a -> a -> Stream m a+#ifdef USE_UNFOLDS_EVERYWHERE+enumerateFromStepIntegral from stride =+ unfold Unfold.enumerateFromStepIntegral (from, stride)+#else+enumerateFromStepIntegral from stride =+ from `seq` stride `seq` Stream step from+ where+ {-# INLINE_LATE step #-}+ step _ !x = return $ Yield x $! (x + stride)+#endif++-- | 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@.+--+-- >>> Stream.toList $ Stream.enumerateFromToIntegral 0 4+-- [0,1,2,3,4]+--+{-# INLINE enumerateFromToIntegral #-}+enumerateFromToIntegral :: (Monad m, Integral a) => a -> a -> Stream m a+enumerateFromToIntegral from to =+ takeWhile (<= to) $ enumerateFromStepIntegral from 1++-- | 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.+--+-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromIntegral (0 :: Int)+-- [0,1,2,3]+--+{-# INLINE enumerateFromIntegral #-}+enumerateFromIntegral :: (Monad m, Integral a, Bounded a) => a -> Stream m a+enumerateFromIntegral from = enumerateFromToIntegral from maxBound++------------------------------------------------------------------------------+-- Enumeration of Fractionals+------------------------------------------------------------------------------++-- 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.++-- 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:+--+-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromFractional 1.1+-- [1.1,2.1,3.1,4.1]+--+{-# INLINE enumerateFromFractional #-}+enumerateFromFractional :: (Monad m, Fractional a) => a -> Stream m a+enumerateFromFractional = enumerateFromNum++-- | 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:+--+-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenFractional 1.1 2.1+-- [1.1,2.1,3.1,4.1]+--+-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenFractional 1.1 (-2.1)+-- [1.1,-2.1,-5.300000000000001,-8.500000000000002]+--+{-# INLINE enumerateFromThenFractional #-}+enumerateFromThenFractional+ :: (Monad m, Fractional a)+ => a -> a -> Stream m a+enumerateFromThenFractional = enumerateFromThenNum++-- | 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:+--+-- >>> Stream.toList $ Stream.enumerateFromToFractional 1.1 4+-- [1.1,2.1,3.1,4.1]+--+-- >>> Stream.toList $ Stream.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.+--+{-# 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++-- | 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:+--+-- >>> Stream.toList $ Stream.enumerateFromThenToFractional 0.1 2 6+-- [0.1,2.0,3.9,5.799999999999999]+--+-- >>> Stream.toList $ Stream.enumerateFromThenToFractional 0.1 (-2) (-6)+-- [0.1,-2.0,-4.1000000000000005,-6.200000000000001]+--+{-# INLINE_NORMAL enumerateFromThenToFractional #-}+enumerateFromThenToFractional+ :: (Monad m, Fractional a, Ord a)+ => a -> a -> a -> Stream m a+enumerateFromThenToFractional from next to =+ takeWhile predicate $ enumerateFromThenFractional from next+ where+ mid = (next - from) / 2+ predicate | next >= from = (<= to + mid)+ | otherwise = (>= to + mid)++-------------------------------------------------------------------------------+-- Enumeration of Enum types not larger than Int+-------------------------------------------------------------------------------+--+-- | 'enumerateFromTo' for 'Enum' types not larger than 'Int'.+--+{-# INLINE enumerateFromToSmall #-}+enumerateFromToSmall :: (Monad m, Enum a) => a -> a -> Stream m a+enumerateFromToSmall from to =+ fmap toEnum+ $ enumerateFromToIntegral (fromEnum from) (fromEnum to)++-- | 'enumerateFromThenTo' for 'Enum' types not larger than 'Int'.+--+{-# INLINE enumerateFromThenToSmall #-}+enumerateFromThenToSmall :: (Monad m, Enum a)+ => a -> a -> a -> Stream m a+enumerateFromThenToSmall from next to =+ fmap 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.+--+{-# INLINE enumerateFromThenSmallBounded #-}+enumerateFromThenSmallBounded :: (Monad m, Enumerable a, Bounded a)+ => a -> a -> Stream m a+enumerateFromThenSmallBounded from next =+ if fromEnum next >= fromEnum from+ then enumerateFromThenTo from next maxBound+ else 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.+--+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'.+ --+ -- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFrom (0 :: Int)+ -- [0,1,2,3]+ --+ -- For 'Fractional' types, enumeration is numerically stable. However, no+ -- overflow or underflow checks are performed.+ --+ -- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFrom 1.1+ -- [1.1,2.1,3.1,4.1]+ --+ enumerateFrom :: (Monad m) => a -> Stream 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.+ --+ -- >>> Stream.toList $ Stream.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.+ --+ -- >>> Stream.toList $ Stream.enumerateFromTo 1.1 4+ -- [1.1,2.1,3.1,4.1]+ --+ -- >>> Stream.toList $ Stream.enumerateFromTo 1.1 4.6+ -- [1.1,2.1,3.1,4.1,5.1]+ --+ enumerateFromTo :: (Monad m) => a -> a -> Stream 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.+ --+ -- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThen 0 2+ -- [0,2,4,6]+ --+ -- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThen 0 (-2)+ -- [0,-2,-4,-6]+ --+ enumerateFromThen :: (Monad m) => a -> a -> Stream 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@.+ --+ -- >>> Stream.toList $ Stream.enumerateFromThenTo 0 2 6+ -- [0,2,4,6]+ --+ -- >>> Stream.toList $ Stream.enumerateFromThenTo 0 (-2) (-6)+ -- [0,-2,-4,-6]+ --+ enumerateFromThenTo :: (Monad m) => a -> a -> a -> Stream 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'+--+{-# INLINE enumerate #-}+enumerate :: (Monad m, Bounded a, Enumerable a) => Stream m a+enumerate = enumerateFrom minBound++-- |+-- >>> enumerateTo = Stream.enumerateFromTo minBound+--+-- Enumerate a 'Bounded' type from its 'minBound' to specified value.+--+{-# INLINE enumerateTo #-}+enumerateTo :: (Monad m, Bounded a, Enumerable a) => a -> Stream m a+enumerateTo = enumerateFromTo minBound++-- |+-- >>> enumerateFromBounded from = Stream.enumerateFromTo from maxBound+--+-- 'enumerateFrom' for 'Bounded' 'Enum' types.+--+{-# INLINE enumerateFromBounded #-}+enumerateFromBounded :: (Monad m, Enumerable a, Bounded a)+ => a -> Stream 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)++instance Enumerable a => Enumerable (Identity a) where+ {-# INLINE enumerateFrom #-}+ enumerateFrom (Identity from) =+ fmap Identity $ enumerateFrom from+ {-# INLINE enumerateFromThen #-}+ enumerateFromThen (Identity from) (Identity next) =+ fmap Identity $ enumerateFromThen from next+ {-# INLINE enumerateFromTo #-}+ enumerateFromTo (Identity from) (Identity to) =+ fmap Identity $ enumerateFromTo from to+ {-# INLINE enumerateFromThenTo #-}+ enumerateFromThenTo (Identity from) (Identity next) (Identity to) =+ fmap Identity+ $ enumerateFromThenTo from next to++-- 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)+-}+------------------------------------------------------------------------------+-- Time Enumeration+------------------------------------------------------------------------------++-- | @timesWith g@ returns a stream of time value tuples. The first component+-- of the tuple is an absolute time reference (epoch) denoting the start of the+-- stream and the second component is a time relative to the reference.+--+-- The argument @g@ specifies the granularity of the relative time in seconds.+-- A lower granularity clock gives higher precision but is more expensive in+-- terms of CPU usage. Any granularity lower than 1 ms is treated as 1 ms.+--+-- >>> import Control.Concurrent (threadDelay)+-- >>> f = Fold.drainMapM (\x -> print x >> threadDelay 1000000)+-- >>> Stream.fold f $ Stream.take 3 $ Stream.timesWith 0.01+-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))+-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))+-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))+--+-- Note: This API is not safe on 32-bit machines.+--+-- /Pre-release/+--+{-# INLINE_NORMAL timesWith #-}+timesWith :: MonadIO m => Double -> Stream m (AbsTime, RelTime64)+timesWith g = Stream step Nothing++ where++ {-# INLINE_LATE step #-}+ step _ Nothing = do+ clock <- liftIO $ asyncClock Monotonic g+ a <- liftIO $ readClock clock+ return $ Skip $ Just (clock, a)++ step _ s@(Just (clock, t0)) = do+ a <- liftIO $ readClock clock+ -- 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 t0, toRelTime64 (a - t0)) s++-- | @absTimesWith 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. Any granularity lower than 1 ms is treated+-- as 1 ms.+--+-- >>> f = Fold.drainMapM print+-- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.absTimesWith 0.01+-- AbsTime (TimeSpec {sec = ..., nsec = ...})+-- AbsTime (TimeSpec {sec = ..., nsec = ...})+-- AbsTime (TimeSpec {sec = ..., nsec = ...})+--+-- Note: This API is not safe on 32-bit machines.+--+-- /Pre-release/+--+{-# INLINE absTimesWith #-}+absTimesWith :: MonadIO m => Double -> Stream m AbsTime+absTimesWith = fmap (uncurry addToAbsTime64) . timesWith++-- | @relTimesWith g@ returns a stream of relative time values starting from 0,+-- using a clock of granularity @g@ specified in seconds. A low granularity+-- clock is more expensive in terms of CPU usage. Any granularity lower than 1+-- ms is treated as 1 ms.+--+-- >>> f = Fold.drainMapM print+-- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.relTimesWith 0.01+-- RelTime64 (NanoSecond64 ...)+-- RelTime64 (NanoSecond64 ...)+-- RelTime64 (NanoSecond64 ...)+--+-- Note: This API is not safe on 32-bit machines.+--+-- /Pre-release/+--+{-# INLINE relTimesWith #-}+relTimesWith :: MonadIO m => Double -> Stream m RelTime64+relTimesWith = fmap snd . timesWith++-- | @times@ returns a stream of time value tuples with clock of 10 ms+-- granularity. The first component of the tuple is an absolute time reference+-- (epoch) denoting the start of the stream and the second component is a time+-- relative to the reference.+--+-- >>> f = Fold.drainMapM (\x -> print x >> threadDelay 1000000)+-- >>> Stream.fold f $ Stream.take 3 $ Stream.times+-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))+-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))+-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))+--+-- Note: This API is not safe on 32-bit machines.+--+-- /Pre-release/+--+{-# INLINE times #-}+times :: MonadIO m => Stream m (AbsTime, RelTime64)+times = timesWith 0.01++-- | @absTimes@ returns a stream of absolute timestamps using a clock of 10 ms+-- granularity.+--+-- >>> f = Fold.drainMapM print+-- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.absTimes+-- AbsTime (TimeSpec {sec = ..., nsec = ...})+-- AbsTime (TimeSpec {sec = ..., nsec = ...})+-- AbsTime (TimeSpec {sec = ..., nsec = ...})+--+-- Note: This API is not safe on 32-bit machines.+--+-- /Pre-release/+--+{-# INLINE absTimes #-}+absTimes :: MonadIO m => Stream m AbsTime+absTimes = fmap (uncurry addToAbsTime64) times++-- | @relTimes@ returns a stream of relative time values starting from 0,+-- using a clock of granularity 10 ms.+--+-- >>> f = Fold.drainMapM print+-- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.relTimes+-- RelTime64 (NanoSecond64 ...)+-- RelTime64 (NanoSecond64 ...)+-- RelTime64 (NanoSecond64 ...)+--+-- Note: This API is not safe on 32-bit machines.+--+-- /Pre-release/+--+{-# INLINE relTimes #-}+relTimes :: MonadIO m => Stream m RelTime64+relTimes = fmap snd times++-- | @durations g@ returns a stream of relative time values measuring the time+-- elapsed since the immediate predecessor element of the stream was generated.+-- The first element of the stream is always 0. @durations@ uses a clock of+-- granularity @g@ specified in seconds. A low granularity clock is more+-- expensive in terms of CPU usage. The minimum granularity is 1 millisecond.+-- Durations lower than 1 ms will be 0.+--+-- Note: This API is not safe on 32-bit machines.+--+-- /Unimplemented/+--+{-# INLINE durations #-}+durations :: -- Monad m =>+ Double -> t m RelTime64+durations = undefined++-- | Generate a singleton event at or after the specified absolute time. Note+-- that this is different from a threadDelay, a threadDelay starts from the+-- time when the action is evaluated, whereas if we use AbsTime based timeout+-- it will immediately expire if the action is evaluated too late.+--+-- /Unimplemented/+--+{-# INLINE timeout #-}+timeout :: -- Monad m =>+ AbsTime -> t m ()+timeout = undefined++-------------------------------------------------------------------------------+-- From Generators+-------------------------------------------------------------------------------++{-# INLINE_NORMAL fromIndicesM #-}+fromIndicesM :: Monad m => (Int -> m a) -> Stream m a+#ifdef USE_UNFOLDS_EVERYWHERE+fromIndicesM gen = unfold (Unfold.fromIndicesM gen) 0+#else+fromIndicesM gen = Stream step 0+ where+ {-# INLINE_LATE step #-}+ step _ i = do+ x <- gen i+ return $ Yield x (i + 1)+#endif++{-# INLINE fromIndices #-}+fromIndices :: Monad m => (Int -> a) -> Stream m a+fromIndices gen = fromIndicesM (return . gen)++-- Adapted from the vector package+{-# 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)++-------------------------------------------------------------------------------+-- Iteration+-------------------------------------------------------------------------------++-- | 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.+--+-- >>> :{+-- Stream.iterateM (\x -> print x >> return (x + 1)) (return 0)+-- & Stream.take 3+-- & Stream.toList+-- :}+-- 0+-- 1+-- [0,1,2]+--+{-# INLINE_NORMAL iterateM #-}+iterateM :: Monad m => (a -> m a) -> m a -> Stream m a+#ifdef USE_UNFOLDS_EVERYWHERE+iterateM step = unfold (Unfold.iterateM step)+#else+iterateM step = Stream (\_ st -> st >>= \(!x) -> return $ Yield x (step x))+#endif++-- | Generate an infinite stream with @x@ as the first element and each+-- successive element derived by applying the function @f@ on the previous+-- element.+--+-- >>> Stream.toList $ Stream.take 5 $ Stream.iterate (+1) 1+-- [1,2,3,4,5]+--+{-# INLINE_NORMAL iterate #-}+iterate :: Monad m => (a -> a) -> a -> Stream m a+iterate step st = iterateM (return . step) (return st)++-------------------------------------------------------------------------------+-- From containers+-------------------------------------------------------------------------------++-- | Convert a list of monadic actions to a 'Stream'+{-# INLINE_LATE fromListM #-}+fromListM :: Monad m => [m a] -> Stream m a+#ifdef USE_UNFOLDS_EVERYWHERE+fromListM = unfold Unfold.fromListM+#else+fromListM = Stream step+ where+ {-# INLINE_LATE step #-}+ step _ (m:ms) = m >>= \x -> return $ Yield x ms+ step _ [] = return Stop+#endif++-- |+-- >>> fromFoldable = Prelude.foldr Stream.cons Stream.nil+--+-- Construct a stream from a 'Foldable' containing pure values:+--+-- /WARNING: O(n^2), suitable only for a small number of+-- elements in the stream/+--+{-# INLINE fromFoldable #-}+fromFoldable :: (Monad m, Foldable f) => f a -> Stream m a+fromFoldable = Prelude.foldr cons nil++-- |+-- >>> fromFoldableM = Prelude.foldr Stream.consM Stream.nil+--+-- Construct a stream from a 'Foldable' containing pure values:+--+-- /WARNING: O(n^2), suitable only for a small number of+-- elements in the stream/+--+{-# INLINE fromFoldableM #-}+fromFoldableM :: (Monad m, Foldable f) => f (m a) -> Stream m a+fromFoldableM = Prelude.foldr consM nil++-------------------------------------------------------------------------------+-- From pointers+-------------------------------------------------------------------------------++-- | Keep reading 'Storable' elements from an immutable 'Ptr' onwards.+--+-- /Unsafe:/ The caller is responsible for safe addressing.+--+-- /Pre-release/+{-# INLINE fromPtr #-}+fromPtr :: forall m a. (Monad m, Storable a) => Ptr a -> Stream m a+fromPtr = Stream step++ where++ {-# INLINE_LATE step #-}+ step _ p = do+ let !x = unsafeInlineIO $ peek p+ return $ Yield x (PTR_NEXT(p, a))++-- | Take @n@ 'Storable' elements starting from an immutable 'Ptr' onwards.+--+-- >>> fromPtrN n = Stream.take n . Stream.fromPtr+--+-- /Unsafe:/ The caller is responsible for safe addressing.+--+-- /Pre-release/+{-# INLINE fromPtrN #-}+fromPtrN :: (Monad m, Storable a) => Int -> Ptr a -> Stream m a+fromPtrN n = take n . fromPtr++-- | Read bytes from an immutable 'Addr#' until a 0 byte is encountered, the 0+-- byte is not included in the stream.+--+-- >>> :set -XMagicHash+-- >>> fromCString# addr = Stream.takeWhile (/= 0) $ Stream.fromPtr $ (Ptr addr :: Ptr Word8)+--+-- /Unsafe:/ The caller is responsible for safe addressing.+--+-- Note that this is completely safe when reading from Haskell string+-- literals because they are guaranteed to be NULL terminated:+--+-- >>> Stream.toList $ Stream.fromCString# "\1\2\3\0"#+-- [1,2,3]+--+{-# INLINE fromCString# #-}+fromCString# :: Monad m => Addr# -> Stream m Word8+fromCString# addr = takeWhile (/= 0) $ fromPtr $ Ptr addr++{-# DEPRECATED fromByteStr# "Please use fromCString# instead" #-}+{-# INLINE fromByteStr# #-}+fromByteStr# :: Monad m => Addr# -> Stream m Word8+fromByteStr# = fromCString#++-- | Read Word16 from an immutable 'Addr#' until a 0 Word16 is encountered, the+-- 0 Word16 is not included in the stream.+--+-- >>> :set -XMagicHash+-- >>> fromW16CString# addr = Stream.takeWhile (/= 0) $ Stream.fromPtr $ (Ptr addr :: Ptr Word16)+--+-- /Unsafe:/ The caller is responsible for safe addressing.+--+{-# INLINE fromW16CString# #-}+fromW16CString# :: Monad m => Addr# -> Stream m Word16+fromW16CString# addr = takeWhile (/= 0) $ fromPtr $ Ptr addr
src/Streamly/Internal/Data/Stream/Lift.hs view
@@ -1,16 +1,19 @@+{-# LANGUAGE CPP #-} -- | -- Module : Streamly.Internal.Data.Stream.Lift--- Copyright : (c) 2019 Composewell Technologies+-- Copyright : (c) 2018 Composewell Technologies -- License : BSD-3-Clause -- Maintainer : streamly@composewell.com -- Stability : experimental -- Portability : GHC+--+-- Transform the underlying monad of a stream. module Streamly.Internal.Data.Stream.Lift ( -- * Generalize Inner Monad morphInner- , generalizeInner+ , generalizeInner -- XXX rename to morphPure -- * Transform Inner Monad , liftInnerWith@@ -19,22 +22,19 @@ ) where -import Data.Functor.Identity (Identity (..))-import Streamly.Internal.Data.Stream.Type- (Stream, fromStreamD, toStreamD, fromStreamK, toStreamK)+#include "inline.hs" -import qualified Streamly.Internal.Data.Stream.StreamD as D-import qualified Streamly.Internal.Data.Stream.StreamK as K+import Data.Functor.Identity (Identity(..))+import Streamly.Internal.Data.SVar.Type (adaptState) --- $setup--- >>> :m--- >>> import Data.Functor.Identity (runIdentity)--- >>> import Streamly.Internal.Data.Stream as Stream+import Streamly.Internal.Data.Stream.Type ---------------------------------------------------------------------------------- Generalize the underlying monad-------------------------------------------------------------------------------+#include "DocTestDataStream.hs" +-------------------------------------------------------------------------------+-- Generalize Inner Monad+-------------------------------------------------------------------------------+ -- | Transform the inner monad of a stream using a natural transformation. -- -- Example, generalize the inner monad from Identity to any other:@@ -43,11 +43,17 @@ -- -- Also known as hoist. ----- /CPS/-{-# INLINE morphInner #-}-morphInner :: (Monad m, Monad n)- => (forall x. m x -> n x) -> Stream m a -> Stream n a-morphInner f xs = fromStreamK $ K.hoist f (toStreamK xs)+{-# INLINE_NORMAL morphInner #-}+morphInner :: Monad n => (forall x. m x -> n x) -> Stream m a -> Stream n a+morphInner 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 -- | Generalize the inner monad of the stream from 'Identity' to any monad. --@@ -55,41 +61,69 @@ -- -- >>> generalizeInner = Stream.morphInner (return . runIdentity) ----- /CPS/--- {-# INLINE generalizeInner #-} generalizeInner :: Monad m => Stream Identity a -> Stream m a generalizeInner = morphInner (return . runIdentity)- -- fromStreamK $ K.hoist (return . runIdentity) (toStreamK xs) ---------------------------------------------------------------------------------- Add and remove a monad transformer-------------------------------------------------------------------------------+-------------------------------------------------------------------------------+-- Transform Inner Monad+------------------------------------------------------------------------------- -- | Lift the inner monad @m@ of a stream @Stream m a@ to @t m@ using the -- supplied lift function. ---{-# INLINE liftInnerWith #-}-liftInnerWith :: (Monad m, Monad (t m))- => (forall b. m b -> t m b) -> Stream m a -> Stream (t m) a-liftInnerWith lift xs = fromStreamD $ D.liftInnerWith lift (toStreamD xs)+{-# INLINE_NORMAL liftInnerWith #-}+liftInnerWith :: (Monad (t m)) =>+ (forall b. m b -> t m b) -> Stream m a -> Stream (t m) a+liftInnerWith lift (Stream step state) = Stream step1 state + where++ {-# INLINE_LATE step1 #-}+ step1 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+ -- | Evaluate the inner monad of a stream using the supplied runner function. ---{-# INLINE runInnerWith #-}-runInnerWith :: (Monad m, Applicative (t m)) =>+{-# INLINE_NORMAL runInnerWith #-}+runInnerWith :: Monad m => (forall b. t m b -> m b) -> Stream (t m) a -> Stream m a-runInnerWith run xs = fromStreamD $ D.runInnerWith run (toStreamD xs)+runInnerWith run (Stream step state) = Stream step1 state + where++ {-# INLINE_LATE step1 #-}+ step1 gst st = do+ r <- run $ step (adaptState gst) st+ return $ case r of+ Yield x s -> Yield x s+ Skip s -> Skip s+ Stop -> Stop+ -- | Evaluate the inner monad of a stream using the supplied stateful runner -- function and the initial state. The state returned by an invocation of the -- runner is supplied as input state to the next invocation. ---{-# INLINE runInnerWithState #-}-runInnerWithState :: (Monad m, Applicative (t m)) =>- (forall b. s -> t m b -> m (b, s))+{-# INLINE_NORMAL runInnerWithState #-}+runInnerWithState :: Monad m =>+ (forall b. s -> t m b -> m (b, s)) -> m s -> Stream (t m) a -> Stream m (s, a)-runInnerWithState run initial xs =- fromStreamD $ D.runInnerWithState run initial (toStreamD xs)+runInnerWithState run initial (Stream step state) =+ Stream step1 (state, initial)++ where++ {-# INLINE_LATE step1 #-}+ step1 gst (st, action) = do+ sv <- action+ (r, !sv1) <- run sv (step (adaptState gst) st)+ return $ case r of+ Yield x s -> Yield (sv1, x) (s, return sv1)+ Skip s -> Skip (s, return sv1)+ Stop -> Stop
+ src/Streamly/Internal/Data/Stream/Nesting.hs view
@@ -0,0 +1,3981 @@+{-# LANGUAGE CPP #-}+-- |+-- Module : Streamly.Internal.Data.Stream.Nesting+-- Copyright : (c) 2018 Composewell Technologies+-- (c) Roman Leshchinskiy 2008-2010+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- This module contains transformations involving multiple streams, unfolds or+-- folds. There are two types of transformations generational or eliminational.+-- Generational transformations are like the "Generate" module but they+-- generate a stream by combining streams instead of elements. Eliminational+-- transformations are like the "Eliminate" module but they transform a stream+-- by eliminating parts of the stream instead of eliminating the whole stream.+--+-- These combinators involve transformation, generation, elimination so can be+-- classified under any of those.++-- The zipWithM combinator in this module has been adapted from the vector+-- package (c) Roman Leshchinskiy.+--+-- Flipped versions can be named as:+-- mapFor/forEach, concatFor, unfoldStepFor (only step function)+-- foreach would be better for streams than mapFor as map could be used for any+-- type not just containers with multiple elements.+--+-- Flipped versions for folding streams:+-- groupsFor :: stream -> fold -> stream (flipped groupsWhile)+--+-- Flipped versions for folds:+-- foldMany :: outer fold -> inner fold -> fold (original version)+-- groupFoldFor :: inner fold -> outer fold -> fold (flipped version)+-- groupStepFor :: inner fold -> outer fold step -> fold (flipped version)+-- This can be convenient for defining the outer fold step using a lambda.+--+module Streamly.Internal.Data.Stream.Nesting+ (+ -- * Generate+ -- | Combining streams to generate streams.++ -- ** Combine Two Streams+ -- | Functions ending in the shape:+ --+ -- @Stream m a -> Stream m a -> Stream m a@.++ -- *** Interleaving+ -- | Interleave elements from two streams alternately. A special case of+ -- unfoldEachInterleave. Interleave is equivalent to mergeBy with a round+ -- robin merge function.+ InterleaveState(..)+ , interleave+ , interleaveEndBy'+ , interleaveSepBy'+ , interleaveBeginBy+ , interleaveEndBy+ , interleaveSepBy++ -- *** Co-operative Scheduling+ -- | Execute streams alternately irrespective of whether they generate+ -- elements or not. Note that scheduling is affected by the Skip+ -- constructor; implementations with more skips receive proportionally less+ -- scheduling time. A more programmer controlled approach would be to emit+ -- a Maybe in a stream and use the output driven scheduling combinators+ -- instead of Skip driven, even if a stream emits Nothing, the output will+ -- force scheduling of another stream.+ --+ , roundRobin -- interleaveFair?/ParallelFair++ -- *** Merging+ -- | Interleave elements from two streams based on a condition.+ , mergeBy+ , mergeByM+ , mergeMinBy+ , mergeFstBy++ -- ** Combine N Streams+ -- | Functions generally ending in these shapes:+ --+ -- @+ -- concat: f (Stream m a) -> Stream m a+ -- concatMap: (a -> Stream m b) -> Stream m a -> Stream m b+ -- unfoldEach: Unfold m a b -> Stream m a -> Stream m b+ -- @++ -- *** unfoldEach+ -- | Generate streams by using an unfold on each element of an input+ -- stream, append the resulting streams and flatten. A special case of+ -- intercalate.+ , unfoldEachFoldBy+ , ConcatUnfoldInterleaveState (..)+ , bfsUnfoldEach+ , altBfsUnfoldEach+ , fairUnfoldEach++ -- *** unfoldEach joined by elements+ -- | Like unfoldEach but intersperses an element between the streams after+ -- unfolding. A special case of intercalate.+ , unfoldEachSepBy+ , unfoldEachSepByM+ , unfoldEachEndBy+ , unfoldEachEndByM++ -- *** unfoldEach joined by sequences+ -- | Like unfoldEach but intersperses a sequence between the unfolded+ -- streams before unfolding. A special case of intercalate.+ , unfoldEachSepBySeq+ , unfoldEachEndBySeq++ -- *** unfoldEach joined by streams+ -- | Like unfoldEach but intersperses streams between the unfolded streams.+ , intercalateSepBy+ , intercalateEndBy++ -- *** concatMap+ , fairConcatMapM+ , fairConcatMap+ , fairConcatForM+ , fairConcatFor++ -- *** unfoldSched+ -- Note appending does not make sense for sched, only bfs or diagonal.++ -- | Like unfoldEach but schedules the generated streams based on time+ -- slice instead of based on the outputs.+ , unfoldSched+ -- , altUnfoldSched -- alternating directions+ , fairUnfoldSched++ -- *** schedMap+ , schedMapM+ , schedMap+ , fairSchedMapM+ , fairSchedMap++ -- *** schedFor+ , schedForM+ , schedFor+ , fairSchedForM+ , fairSchedFor++ -- * Eliminate+ -- | Folding and Parsing chunks of streams to eliminate nested streams.+ -- Functions generally ending in these shapes:+ --+ -- @+ -- f (Fold m a b) -> t m a -> t m b+ -- f (Parser a m b) -> t m a -> t m b+ -- @++ -- ** Folding+ -- | Apply folds on a stream.+ , foldSequence+ , foldIterateM++ -- ** Parsing+ -- | Parsing is opposite to flattening. 'parseMany' is dual to concatMap or+ -- unfoldEach concatMap generates a stream from single values in a+ -- stream and flattens, parseMany does the opposite of flattening by+ -- splitting the stream and then folds each such split to single value in+ -- the output stream.+ , parseMany+ , parseManyPos+ , parseSequence+ , parseManyTill+ , parseIterate+ , parseIteratePos++ -- ** Grouping+ -- | Group segments of a stream and fold. Special case of parsing.+ , groupsWhile+ , groupsRollingBy++ -- ** Splitting+ -- | A special case of parsing.+ , takeEndBySeq+ , takeEndBySeq_+ , wordsBy+ , splitSepBySeq_+ , splitEndBySeq+ , splitEndBySeq_+ , splitOnSuffixSeq -- internal++ , splitBeginBy_+ , splitEndBySeqOneOf+ , splitSepBySeqOneOf++ -- * Transform (Nested Containers)+ -- | Opposite to compact in ArrayStream+ , splitInnerBy -- XXX innerSplitOn+ , splitInnerBySuffix -- XXX innerSplitOnSuffix++ -- * Reduce By Streams+ , dropPrefix+ , dropInfix+ , dropSuffix++ -- * Deprecated+ , interpose+ , interposeM+ , interposeSuffix+ , interposeSuffixM+ , gintercalate+ , gintercalateSuffix+ , intercalate+ , intercalateSuffix+ , unfoldInterleave+ , unfoldRoundRobin+ , interleaveMin+ , interleaveFst+ , interleaveFstSuffix+ , parseManyD+ , parseIterateD+ , groupsBy+ , splitOnSeq+ )+where++#include "deprecation.h"+#include "inline.hs"+#include "ArrayMacros.h"++import Control.Exception (assert)+import Control.Monad.IO.Class (MonadIO(..))+import Data.Bits (shiftR, shiftL, (.|.), (.&.))+import Data.Proxy (Proxy(..))+import Data.Word (Word32)+import Fusion.Plugin.Types (Fuse(..))+import GHC.Types (SPEC(..))++import Streamly.Internal.Data.Array.Type (Array(..))+import Streamly.Internal.Data.Fold.Type (Fold(..))+import Streamly.Internal.Data.MutArray.Type (MutArray(..))+import Streamly.Internal.Data.Parser (ParseError(..), ParseErrorPos)+import Streamly.Internal.Data.RingArray (RingArray(..))+import Streamly.Internal.Data.SVar.Type (adaptState)+import Streamly.Internal.Data.Unbox (Unbox(..))+import Streamly.Internal.Data.Unfold.Type (Unfold(..))++import qualified Streamly.Internal.Data.Array.Type as A+import qualified Streamly.Internal.Data.MutArray.Type as MutArray+import qualified Streamly.Internal.Data.Fold as FL+import qualified Streamly.Internal.Data.Parser as PR+import qualified Streamly.Internal.Data.Parser as PRD+import qualified Streamly.Internal.Data.ParserDrivers as Drivers+import qualified Streamly.Internal.Data.RingArray as RB+import qualified Streamly.Internal.Data.Stream.Generate as Stream+import qualified Streamly.Internal.Data.Unfold.Type as Unfold++import Streamly.Internal.Data.Stream.Transform+ (intersperse, intersperseEndByM)+import Streamly.Internal.Data.Stream.Type hiding (splitAt)++import Prelude hiding (concatMap, mapM, zipWith, splitAt)++#include "DocTestDataStream.hs"++------------------------------------------------------------------------------+-- Interleaving+------------------------------------------------------------------------------++data InterleaveState s1 s2 = InterleaveFirst s1 s2 | InterleaveSecond s1 s2+ | InterleaveSecondOnly s2 | InterleaveFirstOnly s1++-- XXX Ideally we should change the order of the arguments but we have the same+-- convention in append as well, we will have to change that too. Also, the+-- argument order of append makes sense for infix use.++-- | WARNING! O(n^2) time complexity wrt number of streams. Suitable for+-- statically fusing a small number of streams. Use the O(n) complexity+-- StreamK.'Streamly.Data.StreamK.interleave' otherwise.+--+-- Interleaves two streams, yielding one element from each stream alternately,+-- starting from the first stream. When one stream is exhausted, all the+-- remaining elements of the other stream are emitted in the output stream.+--+-- Both the streams are completely exhausted.+--+-- @+-- (a b c) (. . .) => a . b . c .+-- (a b c) (. . ) => a . b . c+-- (a b ) (. . .) => a . b . .+-- @+--+-- Examples:+--+-- >>> f x y = Stream.toList $ Stream.interleave (Stream.fromList x) (Stream.fromList y)+-- >>> f "abc" "..."+-- "a.b.c."+-- >>> f "abc" ".."+-- "a.b.c"+-- >>> f "ab" "..."+-- "a.b.."+--+{-# 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++-- XXX Check the performance of the implementation, we can write a custom one.++{-# ANN module "HLint: ignore Use zip" #-}++-- | Interleave the two streams such that the elements of the second stream are+-- ended by the elements of the first stream. If one of the streams is+-- exhausted then interleaving stops.+--+-- @+-- (. . .) (a b c) => a . b . c .+-- (. . ) (a b c) => a . b . -- c is discarded+-- (. . .) (a b ) => a . b . -- . is discarded+-- @+--+-- Examples:+--+-- >>> f x y = Stream.toList $ Stream.interleaveEndBy' (Stream.fromList x) (Stream.fromList y)+-- >>> f "..." "abc"+-- "a.b.c."+-- >>> f ".." "abc"+-- "a.b."+-- >>> f "..." "ab"+-- "a.b."+--+-- Definition:+--+-- >>> interleaveEndBy' s1 s2 = Stream.unfoldEach Unfold.fromTuple $ Stream.zipWith (,) s2 s1+--+-- Similarly, we can defined interleaveBeginBy' as:+--+-- >>> interleaveBeginBy' = flip interleaveEndBy'+--+{-# INLINE_NORMAL interleaveEndBy' #-}+interleaveEndBy' :: Monad m => Stream m a -> Stream m a -> Stream m a+interleaveEndBy' s1 s2 = unfoldEach Unfold.fromTuple $ zipWith (,) s2 s1++-- | Like `interleave` but stops interleaving as soon as any of the two streams+-- stops. The suffix 'Min' in the name determines the stop behavior.+--+-- This is the same as interleaveEndBy' but it might emit an additional element+-- at the end.+--+{-# DEPRECATED interleaveMin "Please use flip interleaveEndBy' instead." #-}+{-# 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++-- | Interleave the two streams such that the elements of the first stream are+-- infixed between the elements of the second stream. If one of the streams is+-- exhausted then interleaving stops.+--+-- @+-- (. . .) (a b c) => a . b . c -- additional . is discarded+-- (. . ) (a b c) => a . b . c+-- (. ) (a b c) => a . b -- c is discarded+-- @+--+-- >>> f x y = Stream.toList $ Stream.interleaveSepBy' (Stream.fromList x) (Stream.fromList y)+-- >>> f "..." "abc"+-- "a.b.c"+-- >>> f ".." "abc"+-- "a.b.c"+-- >>> f "." "abc"+-- "a.b"+--+{-# INLINE_NORMAL interleaveSepBy' #-}+interleaveSepBy' :: Monad m => Stream m a -> Stream m a -> Stream m a+-- XXX Not an efficient implementation, need to write a fused one.+interleaveSepBy' s1 s2 = concatEffect $ do+ r <- uncons s2+ case r of+ Nothing -> return Stream.nil+ Just (h, t) ->+ return $ h `Stream.cons`+ unfoldEach Unfold.fromTuple (zipWith (,) s1 t)++-- | Interleave the two streams such that the elements of the second stream are+-- prefixed by the elements of the first stream. Interleaving stops when and+-- only when the second stream is exhausted. Shortfall of the prefix stream is+-- ignored and excess is discarded.+--+-- @+-- (. . .) (a b c) => . a . b . c+-- (. . .) (a b ) => . a . b -- additional . is discarded+-- (. . ) (a b c) => . a . b c -- missing . is ignored+-- @+--+-- /Unimplemented/+--+{-# INLINE_NORMAL interleaveBeginBy #-}+interleaveBeginBy :: -- Monad m =>+ Stream m a -> Stream m a -> Stream m a+interleaveBeginBy = undefined++-- | Like 'interleaveEndBy'' but interleaving stops when and only when the+-- second stream is exhausted. Shortfall of the suffix stream is ignored and+-- excess is discarded.+--+-- @+-- (. . .) (a b c) => a . b . c .+-- (. . ) (a b c) => a . b . c -- missing . is ignored+-- (. . .) (a b ) => a . b . -- additional . is discarded+-- @+--+-- >>> f x y = Stream.toList $ Stream.interleaveEndBy (Stream.fromList x) (Stream.fromList y)+-- >>> f "..." "abc"+-- "a.b.c."+-- >>> f ".." "abc"+-- "a.b.c"+-- >>> f "..." "ab"+-- "a.b."+--+{-# INLINE_NORMAL interleaveEndBy #-}+interleaveEndBy :: Monad m => Stream m a -> Stream m a -> Stream m a+interleaveEndBy (Stream step2 state2) (Stream step1 state1) =+ 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++{-# INLINE interleaveFstSuffix #-}+{-# DEPRECATED interleaveFstSuffix "Please use flip interleaveEndBy instead." #-}+interleaveFstSuffix :: Monad m => Stream m a -> Stream m a -> Stream m a+interleaveFstSuffix = flip interleaveEndBy++data InterleaveInfixState s1 s2 a+ = InterleaveInfixFirst s1 s2+ | InterleaveInfixSecondBuf s1 s2+ | InterleaveInfixSecondYield s1 s2 a+ | InterleaveInfixFirstYield s1 s2 a+ | InterleaveInfixFirstOnly s1++-- | Like 'interleaveSepBy'' but interleaving stops when and only when the+-- second stream is exhausted. Shortfall of the infix stream is ignored and+-- excess is discarded.+--+-- @+-- (. . .) (a b c) => a . b . c -- additional . is discarded+-- (. . ) (a b c) => a . b . c+-- (. ) (a b c) => a . b c -- missing . is ignored+-- @+--+-- Examples:+--+-- >>> f x y = Stream.toList $ Stream.interleaveSepBy (Stream.fromList x) (Stream.fromList y)+-- >>> f "..." "abc"+-- "a.b.c"+-- >>> f ".." "abc"+-- "a.b.c"+-- >>> f "." "abc"+-- "a.bc"+--+{-# INLINE_NORMAL interleaveSepBy #-}+interleaveSepBy :: Monad m => Stream m a -> Stream m a -> Stream m a+interleaveSepBy (Stream step2 state2) (Stream step1 state1) =+ 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++{-# DEPRECATED interleaveFst "Please use flip interleaveSepBy instead." #-}+{-# INLINE_NORMAL interleaveFst #-}+interleaveFst :: Monad m => Stream m a -> Stream m a -> Stream m a+interleaveFst = flip interleaveSepBy++------------------------------------------------------------------------------+-- Scheduling+------------------------------------------------------------------------------++-- | 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 choose 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`.+--+-- Scheduling is affected by the Skip constructor; implementations with more+-- skips receive proportionally less scheduling time.+--+-- /Pre-release/+{-# 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++------------------------------------------------------------------------------+-- Merging+------------------------------------------------------------------------------++-- | Like 'mergeBy' but with a monadic comparison function.+--+-- Example, to merge two streams randomly:+--+-- @+-- > randomly _ _ = randomIO >>= \x -> return $ if x then LT else GT+-- > Stream.toList $ Stream.mergeByM randomly (Stream.fromList [1,1,1,1]) (Stream.fromList [2,2,2,2])+-- [2,1,2,2,2,1,1,1]+-- @+--+-- Example, merge two streams in a proportion of 2:1:+--+-- >>> :set -fno-warn-unrecognised-warning-flags+-- >>> :set -fno-warn-x-partial+-- >>> :{+-- do+-- let s1 = Stream.fromList [1,1,1,1,1,1]+-- s2 = Stream.fromList [2,2,2]+-- let proportionately m n = do+-- ref <- newIORef $ cycle $ Prelude.concat [Prelude.replicate m LT, Prelude.replicate n GT]+-- return $ \_ _ -> do+-- r <- readIORef ref+-- writeIORef ref $ Prelude.tail r+-- return $ Prelude.head r+-- f <- proportionately 2 1+-- xs <- Stream.fold Fold.toList $ Stream.mergeByM f s1 s2+-- print xs+-- :}+-- [1,1,2,1,1,2,1,1,2]+--+{-# 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++-- | WARNING! O(n^2) time complexity wrt number of streams. Suitable for+-- statically fusing a small number of streams. Use the O(n) complexity+-- StreamK.'Streamly.Data.StreamK.mergeBy' otherwise.+--+-- 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.+--+-- >>> s1 = Stream.fromList [1,3,5]+-- >>> s2 = Stream.fromList [2,4,6,8]+-- >>> Stream.fold Fold.toList $ Stream.mergeBy compare s1 s2+-- [1,2,3,4,5,6,8]+--+{-# 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)++-- | Like 'mergeByM' but stops merging as soon as any of the two streams stops.+--+-- /Unimplemented/+{-# INLINABLE mergeMinBy #-}+mergeMinBy :: -- Monad m =>+ (a -> a -> m Ordering) -> Stream m a -> Stream m a -> Stream m a+mergeMinBy _f _m1 _m2 = undefined+ -- fromStreamD $ D.mergeMinBy f (toStreamD m1) (toStreamD m2)++-- | Like 'mergeByM' but stops merging as soon as the first stream stops.+--+-- /Unimplemented/+{-# INLINABLE mergeFstBy #-}+mergeFstBy :: -- Monad m =>+ (a -> a -> m Ordering) -> Stream m a -> Stream m a -> Stream m a+mergeFstBy _f _m1 _m2 = undefined+ -- fromStreamK $ D.mergeFstBy f (toStreamD m1) (toStreamD m2)++------------------------------------------------------------------------------+-- Combine N Streams - unfoldEach+------------------------------------------------------------------------------++-- XXX If we want to have strictly N elements in each batch then we can supply a+-- Maybe input to the fold. That could be another variant of this combinator.++-- | Stream must be finite. Unfolds each element of the input stream to+-- generate streams. After generating one element from each stream fold those+-- using the supplied fold and emit the result in the output stream. Continue+-- doing this until the streams are exhausted.+--+-- /Unimplemented/+{-# INLINE_NORMAL unfoldEachFoldBy #-}+unfoldEachFoldBy :: -- Monad m =>+ Fold m b c -> Unfold m a b -> Stream m a -> Stream m c+unfoldEachFoldBy = undefined++data BfsUnfoldEachState o i =+ BfsUnfoldEachOuter o ([i] -> [i])+ | BfsUnfoldEachInner [i] ([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.++-- | Like 'unfoldEach' but interleaves the resulting streams in a breadth first+-- manner instead of appending them. Unfolds each element in the input stream+-- to a stream and then interleave the resulting streams.+--+-- >>> lists = Stream.fromList [[1,4,7],[2,5,8],[3,6,9]]+-- >>> Stream.toList $ Stream.bfsUnfoldEach Unfold.fromList lists+-- [1,2,3,4,5,6,7,8,9]+--+-- CAUTION! Do not use on infinite streams.+--+{-# INLINE_NORMAL bfsUnfoldEach #-}+bfsUnfoldEach :: Monad m =>+ Unfold m a b -> Stream m a -> Stream m b+bfsUnfoldEach (Unfold istep inject) (Stream ostep ost) =+ Stream step (BfsUnfoldEachOuter ost id)++ where++ {-# INLINE_LATE step #-}+ step gst (BfsUnfoldEachOuter o ls) = do+ r <- ostep (adaptState gst) o+ case r of+ Yield a o' -> do+ i <- inject a+ i `seq` return (Skip (BfsUnfoldEachOuter o' (ls . (i :))))+ Skip o' -> return $ Skip (BfsUnfoldEachOuter o' ls)+ Stop -> return $ Skip (BfsUnfoldEachInner (ls []) id)++ step _ (BfsUnfoldEachInner [] rs) =+ case rs [] of+ [] -> return Stop+ ls -> return $ Skip (BfsUnfoldEachInner ls id)++ step _ (BfsUnfoldEachInner (st:ls) rs) = do+ r <- istep st+ return $ case r of+ Yield x s -> Yield x (BfsUnfoldEachInner ls (rs . (s :)))+ Skip s -> Skip (BfsUnfoldEachInner (s:ls) rs)+ Stop -> Skip (BfsUnfoldEachInner ls rs)++data ConcatUnfoldInterleaveState o i =+ ConcatUnfoldInterleaveOuter o [i]+ | ConcatUnfoldInterleaveInner o [i]+ | ConcatUnfoldInterleaveInnerL [i] [i]+ | ConcatUnfoldInterleaveInnerR [i] [i]++-- | Like 'bfsUnfoldEach' but reverses the traversal direction after reaching+-- the last stream and then after reaching the first stream, thus alternating+-- the directions. This could be a little bit more efficient if the order of+-- traversal is not important.+--+-- >>> lists = Stream.fromList [[1,4,7],[2,5,8],[3,6,9]]+-- >>> Stream.toList $ Stream.altBfsUnfoldEach Unfold.fromList lists+-- [1,2,3,6,5,4,7,8,9]+--+-- CAUTION! Do not use on infinite streams.+--+{-# INLINE_NORMAL altBfsUnfoldEach #-}+altBfsUnfoldEach, unfoldInterleave :: Monad m =>+ Unfold m a b -> Stream m a -> Stream m b+altBfsUnfoldEach (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)++RENAME(unfoldInterleave,altBfsUnfoldEach)++-- 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 unfoldEachInterleave this one switches streams on Skips.++-- | Similar to 'bfsUnfoldEach' but scheduling is independent of output.+--+-- This is an N-ary version of 'roundRobin'.+--+-- >>> lists = Stream.fromList [[1,4,7],[2,5,8],[3,6,9]]+-- >>> Stream.toList $ Stream.unfoldSched Unfold.fromList lists+-- [1,2,3,4,5,6,7,8,9]+--+-- Scheduling is affected by the Skip constructor; implementations with more+-- skips receive proportionally less scheduling time.+--+-- CAUTION! Do not use on infinite streams.+--+{-# INLINE_NORMAL unfoldSched #-}+unfoldSched, unfoldRoundRobin :: Monad m =>+ Unfold m a b -> Stream m a -> Stream m b+unfoldSched (Unfold istep inject) (Stream ostep ost) =+ Stream step (BfsUnfoldEachOuter ost id)++ where++ {-# INLINE_LATE step #-}+ step gst (BfsUnfoldEachOuter o ls) = do+ r <- ostep (adaptState gst) o+ case r of+ Yield a o' -> do+ i <- inject a+ i `seq` return (Skip (BfsUnfoldEachOuter o' (ls . (i :))))+ Skip o' -> return $ Skip (BfsUnfoldEachOuter o' ls)+ Stop -> return $ Skip (BfsUnfoldEachInner (ls []) id)++ step _ (BfsUnfoldEachInner [] rs) =+ case rs [] of+ [] -> return Stop+ ls -> return $ Skip (BfsUnfoldEachInner ls id)++ step _ (BfsUnfoldEachInner (st:ls) rs) = do+ r <- istep st+ return $ case r of+ Yield x s -> Yield x (BfsUnfoldEachInner ls (rs . (s :)))+ Skip s -> Skip (BfsUnfoldEachInner ls (rs . (s :)))+ Stop -> Skip (BfsUnfoldEachInner ls rs)++RENAME(unfoldRoundRobin,unfoldSched)++-- | Round robin co-operative scheduling of multiple streams.+--+-- Like concatMap but schedules the generated streams in a round robin+-- fashion. Note that it does not strive to interleave the outputs of the+-- streams, just gives the streams a chance to run whether it produces an+-- output or not. Therefore, the outputs may not seem to be fairly interleaved+-- if a stream decides to skip the output.+--+-- Scheduling is affected by the Skip constructor; implementations with more+-- skips receive proportionally less scheduling time.+--+-- CAUTION! Do not use on infinite streams.+--+{-# INLINE_NORMAL schedMapM #-}+schedMapM :: Monad m => (a -> m (Stream m b)) -> Stream m a -> Stream m b+schedMapM f (Stream ostep ost) =+ Stream step (BfsUnfoldEachOuter ost id)++ where++ {-# INLINE_LATE step #-}+ step gst (BfsUnfoldEachOuter o ls) = do+ r <- ostep (adaptState gst) o+ case r of+ Yield a o' -> do+ i <- f a+ return (Skip (BfsUnfoldEachOuter o' (ls . (i :))))+ Skip o' -> return $ Skip (BfsUnfoldEachOuter o' ls)+ Stop -> return $ Skip (BfsUnfoldEachInner (ls []) id)++ step _ (BfsUnfoldEachInner [] rs) =+ case rs [] of+ [] -> return Stop+ ls -> return $ Skip (BfsUnfoldEachInner ls id)++ step gst (BfsUnfoldEachInner (UnStream istep st:ls) rs) = do+ r <- istep gst st+ return $ case r of+ Yield x s -> Yield x (BfsUnfoldEachInner ls (rs . (Stream istep s :)))+ Skip s -> Skip (BfsUnfoldEachInner ls (rs . (Stream istep s :)))+ Stop -> Skip (BfsUnfoldEachInner ls rs)++-- | See 'SchedFor' for documentation.+--+-- Scheduling is affected by the Skip constructor; implementations with more+-- skips receive proportionally less scheduling time.+--+-- CAUTION! Do not use on infinite streams.+--+{-# INLINE schedMap #-}+schedMap :: Monad m => (a -> Stream m b) -> Stream m a -> Stream m b+schedMap f = schedMapM (return . f)++-- | See 'SchedFor' for documentation.+--+-- Scheduling is affected by the Skip constructor; implementations with more+-- skips receive proportionally less scheduling time.+--+-- CAUTION! Do not use on infinite streams.+--+{-# INLINE schedForM #-}+schedForM :: Monad m => Stream m a -> (a -> m (Stream m b)) -> Stream m b+schedForM = flip schedMapM++-- | Similar to 'bfsConcatFor' but scheduling is independent of output.+--+-- >>> lists = Stream.fromList [[1,4,7],[2,5,8],[3,6,9]]+-- >>> Stream.toList $ Stream.schedFor lists $ \xs -> Stream.fromList xs+-- [1,2,3,4,5,6,7,8,9]+--+-- Scheduling is affected by the Skip constructor; implementations with more+-- skips receive proportionally less scheduling time.+--+-- CAUTION! Do not use on infinite streams.+--+{-# INLINE schedFor #-}+schedFor :: Monad m => Stream m a -> (a -> Stream m b) -> Stream m b+schedFor = flip schedMap++-- | Similar to 'fairUnfoldEach' but scheduling is independent of the output.+--+-- >>> :{+-- outerLoop = Stream.fromList [1,2,3]+-- innerLoop = Unfold.carry $ Unfold.lmap (const [4,5,6]) Unfold.fromList+-- :}+--+-- >>> Stream.toList $ Stream.fairUnfoldSched innerLoop outerLoop+-- [(1,4),(1,5),(2,4),(1,6),(2,5),(3,4),(2,6),(3,5),(3,6)]+--+-- Scheduling is affected by the Skip constructor; implementations with more+-- skips receive proportionally less scheduling time.+--+{-# INLINE_NORMAL fairUnfoldSched #-}+fairUnfoldSched :: Monad m =>+ Unfold m a b -> Stream m a -> Stream m b+fairUnfoldSched (Unfold istep inject) (Stream ostep ost) =+ Stream step (FairUnfoldInit ost id)++ where++ {-# INLINE_LATE step #-}+ step gst (FairUnfoldInit o ls) = do+ r <- ostep (adaptState gst) o+ case r of+ Yield a o' -> do+ i <- inject a+ i `seq` return (Skip (FairUnfoldNext o' id (ls [i])))+ Skip o' -> return $ Skip (FairUnfoldNext o' id (ls []))+ Stop -> return $ Skip (FairUnfoldDrain id (ls []))++ step _ (FairUnfoldNext o ys []) =+ return $ Skip (FairUnfoldInit o ys)++ step _ (FairUnfoldNext o ys (st:ls)) = do+ r <- istep st+ return $ case r of+ Yield x s -> Yield x (FairUnfoldNext o (ys . (s :)) ls)+ Skip s -> Skip (FairUnfoldNext o (ys . (s :)) ls)+ Stop -> Skip (FairUnfoldNext o ys ls)++ step _ (FairUnfoldDrain ys []) =+ case ys [] of+ [] -> return Stop+ xs -> return $ Skip (FairUnfoldDrain id xs)++ step _ (FairUnfoldDrain ys (st:ls)) = do+ r <- istep st+ return $ case r of+ Yield x s -> Yield x (FairUnfoldDrain (ys . (s :)) ls)+ Skip s -> Skip (FairUnfoldDrain (ys . (s :)) ls)+ Stop -> Skip (FairUnfoldDrain ys ls)++-- | See 'fairConcatFor' for more details. This is similar except that this+-- uses unfolds, therefore, it is much faster due to fusion.+--+-- >>> :{+-- outerLoop = Stream.fromList [1,2,3]+-- innerLoop = Unfold.carry $ Unfold.lmap (const [4,5,6]) Unfold.fromList+-- :}+--+-- >>> Stream.toList $ Stream.fairUnfoldEach innerLoop outerLoop+-- [(1,4),(1,5),(2,4),(1,6),(2,5),(3,4),(2,6),(3,5),(3,6)]+--+{-# INLINE_NORMAL fairUnfoldEach #-}+fairUnfoldEach :: Monad m =>+ Unfold m a b -> Stream m a -> Stream m b+fairUnfoldEach (Unfold istep inject) (Stream ostep ost) =+ Stream step (FairUnfoldInit ost id)++ where++ {-# INLINE_LATE step #-}+ step gst (FairUnfoldInit o ls) = do+ r <- ostep (adaptState gst) o+ case r of+ Yield a o' -> do+ i <- inject a+ i `seq` return (Skip (FairUnfoldNext o' id (ls [i])))+ Skip o' -> return $ Skip (FairUnfoldInit o' ls)+ Stop -> return $ Skip (FairUnfoldDrain id (ls []))++ step _ (FairUnfoldNext o ys []) =+ return $ Skip (FairUnfoldInit o ys)++ step _ (FairUnfoldNext o ys (st:ls)) = do+ r <- istep st+ return $ case r of+ Yield x s -> Yield x (FairUnfoldNext o (ys . (s :)) ls)+ Skip s -> Skip (FairUnfoldNext o ys (s : ls))+ Stop -> Skip (FairUnfoldNext o ys ls)++ step _ (FairUnfoldDrain ys []) =+ case ys [] of+ [] -> return Stop+ xs -> return $ Skip (FairUnfoldDrain id xs)++ step _ (FairUnfoldDrain ys (st:ls)) = do+ r <- istep st+ return $ case r of+ Yield x s -> Yield x (FairUnfoldDrain (ys . (s :)) ls)+ Skip s -> Skip (FairUnfoldDrain ys (s : ls))+ Stop -> Skip (FairUnfoldDrain ys ls)++-- | See 'fairSchedFor' for documentation.+--+-- Scheduling is affected by the Skip constructor; implementations with more+-- skips receive proportionally less scheduling time.+--+{-# INLINE_NORMAL fairSchedMapM #-}+fairSchedMapM :: Monad m =>+ (a -> m (Stream m b)) -> Stream m a -> Stream m b+fairSchedMapM f (Stream ostep ost) =+ Stream step (FairUnfoldInit ost id)++ where++ {-# INLINE_LATE step #-}+ step gst (FairUnfoldInit o ls) = do+ r <- ostep (adaptState gst) o+ case r of+ Yield a o' -> do+ i <- f a+ i `seq` return (Skip (FairUnfoldNext o' id (ls [i])))+ Skip o' -> return $ Skip (FairUnfoldNext o' id (ls []))+ Stop -> return $ Skip (FairUnfoldDrain id (ls []))++ step _ (FairUnfoldNext o ys []) =+ return $ Skip (FairUnfoldInit o ys)++ step gst (FairUnfoldNext o ys (UnStream istep st:ls)) = do+ r <- istep gst st+ return $ case r of+ Yield x s -> Yield x (FairUnfoldNext o (ys . (Stream istep s :)) ls)+ Skip s -> Skip (FairUnfoldNext o (ys . (Stream istep s :)) ls)+ Stop -> Skip (FairUnfoldNext o ys ls)++ step _ (FairUnfoldDrain ys []) =+ case ys [] of+ [] -> return Stop+ xs -> return $ Skip (FairUnfoldDrain id xs)++ step gst (FairUnfoldDrain ys (UnStream istep st:ls)) = do+ r <- istep gst st+ return $ case r of+ Yield x s -> Yield x (FairUnfoldDrain (ys . (Stream istep s :)) ls)+ Skip s -> Skip (FairUnfoldDrain (ys . (Stream istep s :)) ls)+ Stop -> Skip (FairUnfoldDrain ys ls)++-- | See 'fairSchedFor' for documentation.+--+-- Scheduling is affected by the Skip constructor; implementations with more+-- skips receive proportionally less scheduling time.+--+{-# INLINE fairSchedMap #-}+fairSchedMap :: Monad m => (a -> Stream m b) -> Stream m a -> Stream m b+fairSchedMap f = fairSchedMapM (return . f)++-- | See 'fairSchedFor' for documentation.+--+-- Scheduling is affected by the Skip constructor; implementations with more+-- skips receive proportionally less scheduling time.+--+{-# INLINE fairSchedForM #-}+fairSchedForM :: Monad m => Stream m a -> (a -> m (Stream m b)) -> Stream m b+fairSchedForM = flip fairSchedMapM++-- | 'fairSchedFor' is just like 'fairConcatFor', it traverses the depth and+-- breadth of nesting equally. It maintains fairness among different levels of+-- loop iterations. Therefore, the outer and the inner loops in a nested loop+-- get equal priority. It can be used to nest infinite streams without starving+-- outer streams due to inner ones.+--+-- There is one crucial difference, while 'fairConcatFor' necessarily produces+-- an output from one stream before it schedules the next, 'fairSchedFor'+-- schedules the next stream even if a stream did not produce an output. Thus+-- it interleaves the CPU rather than the outputs of the streams. Thus even if+-- an infinite stream does not produce an output it can not block all other+-- streams.+--+-- Note that the order of emitting the output from different streams may not be+-- predictable, it depends on the skip points inside the stream. Scheduling is+-- affected by the Skip constructor; implementations with more skips receive+-- proportionally less scheduling time.+--+-- == Non-Productive Streams+--+-- Unlike in 'fairConcatFor', if one of the two interleaved streams does not+-- produce an output at all and continues forever then the other stream will+-- still get scheduled. The following program will hang forever for+-- 'fairConcatFor' but will work fine with 'fairSchedFor'.+--+-- >>> :{+-- oddsIf x = Stream.fromList (if x then [1,3..] else [2,4..])+-- filterEven x = if even x then Stream.fromPure x else Stream.nil+-- :}+--+-- >>> :{+-- evens =+-- Stream.fairSchedFor (Stream.fromList [True,False]) $ \r ->+-- Stream.fairSchedFor (oddsIf r) filterEven+-- :}+--+-- >>> Stream.toList $ Stream.take 3 $ evens+-- [2,4,6]+--+-- When @r@ is True, the nested 'fairSchedFor' is a non-productive infinite+-- loop, but still the outer loop gets a chance to generate the @False@ value,+-- and the @evens@ function can produce output. The same code won't terminate+-- if we use 'fairConcatFor' instead of 'fairSchedFor'. Thus even without+-- explicit concurrency we can schedule multiple streams on the same CPU.+--+-- == Logic Programming+--+-- When exploring large streams in logic programming, 'fairSchedFor' can be+-- used as a safe alternative to 'fairConcatFor' as it cannot block due to+-- non-productive infinite streams.+--+{-# INLINE fairSchedFor #-}+fairSchedFor :: Monad m => Stream m a -> (a -> Stream m b) -> Stream m b+fairSchedFor = flip fairSchedMap++-- | See 'fairConcatFor' for documentation.+{-# INLINE_NORMAL fairConcatMapM #-}+fairConcatMapM :: Monad m =>+ (a -> m (Stream m b)) -> Stream m a -> Stream m b+fairConcatMapM f (Stream ostep ost) =+ Stream step (FairUnfoldInit ost id)++ where++ {-# INLINE_LATE step #-}+ step gst (FairUnfoldInit o ls) = do+ r <- ostep (adaptState gst) o+ case r of+ Yield a o' -> do+ i <- f a+ i `seq` return (Skip (FairUnfoldNext o' id (ls [i])))+ Skip o' -> return $ Skip (FairUnfoldInit o' ls)+ Stop -> return $ Skip (FairUnfoldDrain id (ls []))++ step _ (FairUnfoldNext o ys []) =+ return $ Skip (FairUnfoldInit o ys)++ step gst (FairUnfoldNext o ys (UnStream istep st:ls)) = do+ r <- istep gst st+ return $ case r of+ Yield x s -> Yield x (FairUnfoldNext o (ys . (Stream istep s :)) ls)+ Skip s -> Skip (FairUnfoldNext o ys (UnStream istep s:ls))+ Stop -> Skip (FairUnfoldNext o ys ls)++ step _ (FairUnfoldDrain ys []) =+ case ys [] of+ [] -> return Stop+ xs -> return $ Skip (FairUnfoldDrain id xs)++ step gst (FairUnfoldDrain ys (UnStream istep st:ls)) = do+ r <- istep gst st+ return $ case r of+ Yield x s -> Yield x (FairUnfoldDrain (ys . (Stream istep s :)) ls)+ Skip s -> Skip (FairUnfoldDrain ys (Stream istep s : ls))+ Stop -> Skip (FairUnfoldDrain ys ls)++-- | See 'fairConcatFor' for documentation.+{-# INLINE fairConcatMap #-}+fairConcatMap :: Monad m => (a -> Stream m b) -> Stream m a -> Stream m b+fairConcatMap f = fairConcatMapM (return . f)++-- | See 'fairConcatFor' for documentation.+{-# INLINE fairConcatForM #-}+fairConcatForM :: Monad m => Stream m a -> (a -> m (Stream m b)) -> Stream m b+fairConcatForM = flip fairConcatMapM++-- | 'fairConcatFor' is like 'concatFor' but traverses the depth and breadth of+-- nesting equally. Therefore, the outer and the inner loops in a nested loop+-- get equal priority. It can be used to nest infinite streams without starving+-- outer streams due to inner ones.+--+-- Given a stream of three streams:+--+-- @+-- 1. [1,2,3]+-- 2. [4,5,6]+-- 3. [7,8,9]+-- @+--+-- Here, outer loop is the stream of streams and the inner loops are the+-- individual streams. The traversal sweeps the diagonals in the above grid to+-- give equal chance to outer and inner loops. The resulting stream is+-- @(1),(2,4),(3,5,7),(6,8),(9)@, diagonals are parenthesized for emphasis.+--+-- == Looping+--+-- A single stream case is equivalent to 'concatFor':+--+-- >>> Stream.toList $ Stream.fairConcatFor (Stream.fromList [1,2]) $ \x -> Stream.fromPure x+-- [1,2]+--+-- == Fair Nested Looping+--+-- Multiple streams nest like @for@ loops. The result is a cross product of the+-- streams. However, the ordering of the results of the cross product is such+-- that each stream gets consumed equally. In other words, inner iterations of+-- a nested loop get the same priority as the outer iterations. Inner+-- iterations do not finish completely before the outer iterations start.+--+-- >>> :{+-- Stream.toList $ do+-- Stream.fairConcatFor (Stream.fromList [1,2,3]) $ \x ->+-- Stream.fairConcatFor (Stream.fromList [4,5,6]) $ \y ->+-- Stream.fromPure (x, y)+-- :}+-- [(1,4),(1,5),(2,4),(1,6),(2,5),(3,4),(2,6),(3,5),(3,6)]+--+-- == Nesting Infinite Streams+--+-- Example with infinite streams. Print all pairs in the cross product with sum+-- less than a specified number.+--+-- >>> :{+-- Stream.toList+-- $ Stream.takeWhile (\(x,y) -> x + y < 6)+-- $ Stream.fairConcatFor (Stream.fromList [1..]) $ \x ->+-- Stream.fairConcatFor (Stream.fromList [1..]) $ \y ->+-- Stream.fromPure (x, y)+-- :}+-- [(1,1),(1,2),(2,1),(1,3),(2,2),(3,1),(1,4),(2,3),(3,2),(4,1)]+--+-- == How the nesting works?+--+-- If we look at the cross product of [1,2,3], [4,5,6], the streams being+-- combined using 'fairConcatFor' are the following sequential loop iterations:+--+-- @+-- (1,4) (1,5) (1,6) -- first iteration of the outer loop+-- (2,4) (2,5) (2,6) -- second iteration of the outer loop+-- (3,4) (3,5) (3,6) -- third iteration of the outer loop+-- @+--+-- The result is a triangular or diagonal traversal of these iterations:+--+-- @+-- [(1,4),(1,5),(2,4),(1,6),(2,5),(3,4),(2,6),(3,5),(3,6)]+-- @+--+-- == Non-Termination Cases+--+-- If one of the two interleaved streams does not produce an output at all and+-- continues forever then the other stream will never get scheduled. This is+-- because a stream is unscheduled only after it produces an output. This can+-- lead to non-terminating programs, an example is provided below.+--+-- >>> :{+-- oddsIf x = Stream.fromList (if x then [1,3..] else [2,4..])+-- filterEven x = if even x then Stream.fromPure x else Stream.nil+-- :}+--+-- >>> :{+-- evens =+-- Stream.fairConcatFor (Stream.fromList [True,False]) $ \r ->+-- Stream.concatFor (oddsIf r) filterEven+-- :}+--+-- The @evens@ function does not terminate because, when r is True, the nested+-- 'concatFor' is a non-productive infinite loop, therefore, the outer loop+-- never gets a chance to generate the @False@ value.+--+-- But the following refactoring of the above code works as expected:+--+-- >>> :{+-- mixed =+-- Stream.fairConcatFor (Stream.fromList [True,False]) $ \r ->+-- Stream.concatFor (oddsIf r) Stream.fromPure+-- :}+--+-- >>> evens = Stream.fairConcatFor mixed filterEven+-- >>> Stream.toList $ Stream.take 3 $ evens+-- [2,4,6]+--+-- This works because in @mixed@ both the streams being interleaved are+-- productive.+--+-- Care should be taken how you write your program, keep in mind the scheduling+-- implications. To avoid such scheduling problems in serial interleaving, you+-- can use 'fairSchedFor' or concurrent scheduling i.e. parFairConcatFor. Due+-- to concurrent scheduling the other branch will make progress even if one is+-- an infinite loop producing nothing.+--+-- == Logic Programming+--+-- Streamly provides all operations for logic programming. It provides+-- functionality equivalent to 'LogicT' type from the 'logict' package.+-- The @MonadLogic@ operations can be implemented using the available stream+-- operations. For example, 'uncons' is @msplit@, 'interleave' corresponds to+-- the @interleave@ operation of MonadLogic, 'fairConcatFor' is the+-- fair bind (@>>-@) operation. 'fairSchedFor' is an even better alternative+-- for fair bind, it guarantees that non-productive infinite streams cannot+-- block progress.+--+-- == Related Operations+--+-- See also "Streamly.Internal.Data.StreamK.fairConcatFor".+--+{-# INLINE fairConcatFor #-}+fairConcatFor :: Monad m => Stream m a -> (a -> Stream m b) -> Stream m b+fairConcatFor = flip fairConcatMap++------------------------------------------------------------------------------+-- Combine N Streams - interpose+------------------------------------------------------------------------------++{-# ANN type InterposeSuffixState Fuse #-}+data InterposeSuffixState s1 i1 =+ InterposeSuffixFirst s1+ -- | InterposeSuffixFirstYield s1 i1+ | InterposeSuffixFirstInner s1 i1+ | InterposeSuffixSecond s1++-- XXX 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+-- behavior for now.++-- | Monadic variant of 'unfoldEachEndBy'.+--+-- Definition:+--+-- >>> unfoldEachEndByM x = Stream.intercalateEndBy Unfold.identity (Stream.repeatM x)+--+{-# INLINE_NORMAL unfoldEachEndByM #-}+unfoldEachEndByM, interposeSuffixM :: Monad m =>+ m c -> Unfold m b c -> Stream m b -> Stream m c+unfoldEachEndByM+ 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)++-- | Unfold the elements of a stream, append the given element after each+-- unfolded stream and then concat them into a single stream.+--+-- Definition:+--+-- >>> unfoldEachEndBy x = Stream.intercalateEndBy Unfold.identity (Stream.repeat x)+--+-- Usage:+--+-- >>> unlines = Stream.unfoldEachEndBy '\n'+--+-- /Pre-release/+{-# INLINE unfoldEachEndBy #-}+unfoldEachEndBy, interposeSuffix :: Monad m+ => c -> Unfold m b c -> Stream m b -> Stream m c+unfoldEachEndBy x = unfoldEachEndByM (return x)++RENAME(interposeSuffix,unfoldEachEndBy)+RENAME(interposeSuffixM,unfoldEachEndByM)++{-# ANN type InterposeState Fuse #-}+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).++-- | Monadic variant of 'unfoldEachSepBy'.+--+-- Definition:+--+-- >>> unfoldEachSepByM x = Stream.intercalateSepBy Unfold.identity (Stream.repeatM x)+--+{-# INLINE_NORMAL unfoldEachSepByM #-}+unfoldEachSepByM, interposeM :: Monad m =>+ m c -> Unfold m b c -> Stream m b -> Stream m c+unfoldEachSepByM+ 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)+ -}++-- | Unfold the elements of a stream, intersperse the given element between the+-- unfolded streams and then concat them into a single stream.+--+-- Definition:+--+-- >>> unfoldEachSepBy x = Stream.unfoldEachSepByM (return x)+-- >>> unfoldEachSepBy x = Stream.intercalateSepBy Unfold.identity (Stream.repeat x)+--+-- Usage:+--+-- >>> unwords = Stream.unfoldEachSepBy ' '+--+-- /Pre-release/+{-# INLINE unfoldEachSepBy #-}+unfoldEachSepBy, interpose :: Monad m+ => c -> Unfold m b c -> Stream m b -> Stream m c+unfoldEachSepBy x = unfoldEachSepByM (return x)++RENAME(interposeM,unfoldEachSepByM)+RENAME(interpose,unfoldEachSepBy)++------------------------------------------------------------------------------+-- Combine N Streams - intercalate+------------------------------------------------------------------------------++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++-- | See 'intercalateSepBy' for detailed documentation.+--+-- You can think of this as 'interleaveEndBy' on the stream of streams followed+-- by concat. Same as the following but more efficient:+--+-- >>> intercalateEndBy u1 s1 u2 s2 = Stream.concat $ Stream.interleaveEndBy (fmap (Stream.unfold u1) s1) (fmap (Stream.unfold u2) s2)+--+-- /Pre-release/+{-# INLINE_NORMAL intercalateEndBy #-}+intercalateEndBy :: Monad m =>+ Unfold m a c -> Stream m a+ -> Unfold m b c -> Stream m b+ -> Stream m c+intercalateEndBy+ (Unfold istep2 inject2) (Stream step2 state2)+ (Unfold istep1 inject1) (Stream step1 state1) =+ 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++-- |+--+-- >>> gintercalateSuffix u1 s1 u2 s2 = Stream.intercalateEndBy u2 s2 u1 s1+--+{-# DEPRECATED gintercalateSuffix "Please use intercalateEndBy instead. Note the change in argument order." #-}+{-# INLINE gintercalateSuffix #-}+gintercalateSuffix+ :: Monad m+ => Unfold m a c -> Stream m a -> Unfold m b c -> Stream m b -> Stream m c+gintercalateSuffix u1 s1 u2 s2 = intercalateEndBy u2 s2 u1 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++-- | The first stream @Stream m b@ is turned into a stream of streams by+-- unfolding each element using the first unfold, similarly @Stream m a@ is+-- also turned into a stream of streams. The second stream of streams is+-- interspersed with the streams from the first stream in an infix manner and+-- then the resulting stream is flattened.+--+-- You can think of this as 'interleaveSepBy' on the stream of streams followed+-- by concat. Same as the following but more efficient:+--+-- >>> intercalateSepBy u1 s1 u2 s2 = Stream.concat $ Stream.interleaveSepBy (fmap (Stream.unfold u1) s1) (fmap (Stream.unfold u2) s2)+--+-- If the separator stream consists of nil streams then it becomes equivalent+-- to 'unfoldEach':+--+-- >>> unfoldEach = Stream.intercalateSepBy (Unfold.nilM (const (return ()))) (Stream.repeat ())+--+-- /Pre-release/+{-# INLINE_NORMAL intercalateSepBy #-}+intercalateSepBy+ :: Monad m+ => Unfold m b c -> Stream m b+ -> Unfold m a c -> Stream m a+ -> Stream m c+{-+intercalateSepBy u1 s1 u2 s2 =+ Stream.concat $ interleaveSepBy (fmap (unfold u1) s1) (fmap (unfold u2) s2)+-}+intercalateSepBy+ (Unfold istep2 inject2) (Stream step2 state2)+ (Unfold istep1 inject1) (Stream step1 state1) =+ 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)+ -}++-- |+--+-- >>> gintercalate u1 s1 u2 s2 = Stream.intercalateSepBy u2 s2 u1 s1+--+{-# DEPRECATED gintercalate "Please use intercalateSepBy instead." #-}+{-# INLINE gintercalate #-}+gintercalate :: Monad m =>+ Unfold m a c -> Stream m a -> Unfold m b c -> Stream m b -> Stream m c+gintercalate u1 s1 u2 s2 = intercalateSepBy u2 s2 u1 s1++-- | Unfold each element of the stream, end each unfold by a sequence generated+-- by unfolding the supplied value.+--+-- Definition:+--+-- >>> unfoldEachEndBySeq a u = Stream.unfoldEach u . Stream.intersperseEndByM a+-- >>> unfoldEachEndBySeq a u = Stream.intercalateEndBy u (Stream.repeat a) u+--+-- Idioms:+--+-- >>> intersperseEndByM x = Stream.unfoldEachEndBySeq x Unfold.identity+-- >>> unlines = Stream.unfoldEachEndBySeq "\n" Unfold.fromList+--+-- Usage:+--+-- >>> input = Stream.fromList ["abc", "def", "ghi"]+-- >>> Stream.toList $ Stream.unfoldEachEndBySeq "\n" Unfold.fromList input+-- "abc\ndef\nghi\n"+--+{-# INLINE unfoldEachEndBySeq #-}+unfoldEachEndBySeq :: Monad m+ => b -> Unfold m b c -> Stream m b -> Stream m c+unfoldEachEndBySeq seed unf = unfoldEach unf . intersperseEndByM (return seed)++{-# DEPRECATED intercalateSuffix "Please use unfoldEachEndBySeq instead." #-}+{-# INLINE intercalateSuffix #-}+intercalateSuffix :: Monad m+ => Unfold m b c -> b -> Stream m b -> Stream m c+intercalateSuffix u x = unfoldEachEndBySeq x u++-- | Unfold each element of the stream, separate the successive unfolds by a+-- sequence generated by unfolding the supplied value.+--+-- Definition:+--+-- >>> unfoldEachSepBySeq a u = Stream.unfoldEach u . Stream.intersperse a+-- >>> unfoldEachSepBySeq a u = Stream.intercalateSepBy u (Stream.repeat a) u+--+-- Idioms:+--+-- >>> intersperse x = Stream.unfoldEachSepBySeq x Unfold.identity+-- >>> unwords = Stream.unfoldEachSepBySeq " " Unfold.fromList+--+-- Usage:+--+-- >>> input = Stream.fromList ["abc", "def", "ghi"]+-- >>> Stream.toList $ Stream.unfoldEachSepBySeq " " Unfold.fromList input+-- "abc def ghi"+--+{-# INLINE unfoldEachSepBySeq #-}+unfoldEachSepBySeq :: Monad m+ => b -> Unfold m b c -> Stream m b -> Stream m c+unfoldEachSepBySeq seed unf str = unfoldEach unf $ intersperse seed str++{-# DEPRECATED intercalate "Please use unfoldEachSepBySeq instead." #-}+{-# INLINE intercalate #-}+intercalate :: Monad m+ => Unfold m b c -> b -> Stream m b -> Stream m c+intercalate u x = unfoldEachSepBySeq x u++------------------------------------------------------------------------------+-- Folding+------------------------------------------------------------------------------++-- | Apply a stream of folds to an input stream and emit the results in the+-- output stream.+--+-- /Unimplemented/+--+{-# INLINE foldSequence #-}+foldSequence+ :: -- Monad m =>+ Stream m (Fold m a b)+ -> Stream m a+ -> Stream m b+foldSequence _f _m = undefined++{-# ANN type FIterState Fuse #-}+data FIterState s f m a b+ = FIterInit s f+ | forall fs. FIterStream s (fs -> a -> m (FL.Step fs b)) fs (fs -> m b)+ (fs -> m b)+ | FIterYield b (FIterState s f m a b)+ | FIterStop++-- | Iterate a fold generator on a stream. The initial value @b@ is used to+-- generate the first fold, the fold is applied on the stream and the result of+-- the fold is used to generate the next fold and so on.+--+-- Usage:+--+-- >>> import Data.Monoid (Sum(..))+-- >>> f x = return (Fold.take 2 (Fold.sconcat x))+-- >>> s = fmap Sum $ Stream.fromList [1..10]+-- >>> Stream.fold Fold.toList $ fmap getSum $ Stream.foldIterateM f (pure 0) s+-- [3,10,21,36,55,55]+--+-- This is the streaming equivalent of monad like sequenced application of+-- folds where next fold is dependent on the previous fold.+--+-- /Pre-release/+--+{-# INLINE_NORMAL foldIterateM #-}+foldIterateM ::+ Monad m => (b -> m (FL.Fold m a b)) -> m b -> Stream m a -> Stream m b+foldIterateM func seed0 (Stream step state) =+ Stream stepOuter (FIterInit state seed0)++ where++ {-# INLINE iterStep #-}+ iterStep from st fstep extract final = do+ res <- from+ return+ $ Skip+ $ case res of+ FL.Partial fs -> FIterStream st fstep fs extract final+ FL.Done fb -> FIterYield fb $ FIterInit st (return fb)++ {-# INLINE_LATE stepOuter #-}+ stepOuter _ (FIterInit st seed) = do+ (FL.Fold fstep initial extract final) <- seed >>= func+ iterStep initial st fstep extract final+ stepOuter gst (FIterStream st fstep fs extract final) = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> do+ iterStep (fstep fs x) s fstep extract final+ Skip s -> return $ Skip $ FIterStream s fstep fs extract final+ Stop -> do+ b <- final fs+ return $ Skip $ FIterYield b FIterStop+ stepOuter _ (FIterYield a next) = return $ Yield a next+ stepOuter _ FIterStop = return Stop++------------------------------------------------------------------------------+-- Parsing+------------------------------------------------------------------------------++-- | Apply a 'Parser' repeatedly on a stream and emit the parsed values in the+-- output stream.+--+-- Usage:+--+-- >>> s = Stream.fromList [1..10]+-- >>> parser = Parser.takeBetween 0 2 Fold.sum+-- >>> Stream.toList $ Stream.parseMany parser s+-- [Right 3,Right 7,Right 11,Right 15,Right 19]+--+-- This is the streaming equivalent of the 'Streamly.Data.Parser.many' parse+-- combinator.+--+-- Known Issues: When the parser fails there is no way to get the remaining+-- stream.+--+{-# INLINE parseMany #-}+parseMany+ :: Monad m+ => PRD.Parser a m b+ -> Stream m a+ -> Stream m (Either ParseError b)+parseMany = Drivers.parseMany++-- | Like 'parseMany' but includes stream position information in the error+-- messages.+--+{-# INLINE parseManyPos #-}+parseManyPos+ :: Monad m+ => PRD.Parser a m b+ -> Stream m a+ -> Stream m (Either ParseErrorPos b)+parseManyPos = Drivers.parseManyPos++{-# DEPRECATED parseManyD "Please use parseMany instead." #-}+{-# INLINE parseManyD #-}+parseManyD+ :: Monad m+ => PR.Parser a m b+ -> Stream m a+ -> Stream m (Either ParseError b)+parseManyD = parseMany++-- | Apply a stream of parsers to an input stream and emit the results in the+-- output stream.+--+-- /Unimplemented/+--+{-# INLINE parseSequence #-}+parseSequence+ :: -- Monad m =>+ Stream m (PR.Parser a m b)+ -> Stream m a+ -> Stream m b+parseSequence _f _m = undefined++-- XXX Change the parser arguments' order++-- | @parseManyTill collect test stream@ tries the parser @test@ on the input,+-- if @test@ fails it backtracks and tries @collect@, after @collect@ succeeds+-- @test@ is tried again and so on. The parser stops when @test@ succeeds. The+-- output of @test@ is discarded and the output of @collect@ is emitted in the+-- output stream. The parser fails if @collect@ fails.+--+-- /Unimplemented/+--+{-# INLINE parseManyTill #-}+parseManyTill ::+ -- MonadThrow m =>+ PR.Parser a m b+ -> PR.Parser a m x+ -> Stream m a+ -> Stream m b+parseManyTill = undefined++-- | Iterate a parser generating function on a stream. The initial value @b@ is+-- used to generate the first parser, the parser is applied on the stream and+-- the result is used to generate the next parser and so on.+--+-- Example:+--+-- >>> import Data.Monoid (Sum(..))+-- >>> s = Stream.fromList [1..10]+-- >>> Stream.toList $ fmap getSum $ Stream.catRights $ Stream.parseIterate (\b -> Parser.takeBetween 0 2 (Fold.sconcat b)) (Sum 0) $ fmap Sum s+-- [3,10,21,36,55,55]+--+-- This is the streaming equivalent of monad like sequenced application of+-- parsers where next parser is dependent on the previous parser.+--+-- /Pre-release/+--+{-# INLINE parseIterate #-}+parseIterate+ :: Monad m+ => (b -> PRD.Parser a m b)+ -> b+ -> Stream m a+ -> Stream m (Either ParseError b)+parseIterate = Drivers.parseIterate++-- | Like 'parseIterate' but includes stream position information in the error+-- messages.+--+{-# INLINE parseIteratePos #-}+parseIteratePos+ :: Monad m+ => (b -> PRD.Parser a m b)+ -> b+ -> Stream m a+ -> Stream m (Either ParseErrorPos b)+parseIteratePos = Drivers.parseIteratePos++{-# DEPRECATED parseIterateD "Please use parseIterate instead." #-}+{-# INLINE parseIterateD #-}+parseIterateD+ :: Monad m+ => (b -> PR.Parser a m b)+ -> b+ -> Stream m a+ -> Stream m (Either ParseError b)+parseIterateD = parseIterate++------------------------------------------------------------------------------+-- Grouping+------------------------------------------------------------------------------++data GroupByState st fs a b+ = GroupingInit st+ | GroupingDo st !fs+ | GroupingInitWith st !a+ | GroupingDoWith st !fs !a+ | GroupingYield !b (GroupByState st fs a b)+ | GroupingDone++-- | Keep collecting items in a group as long as the comparison function+-- returns true. The comparison function is @cmp old new@ where @old@ is the+-- first item in the group and @new@ is the incoming item being tested for+-- membership of the group. The collected items are folded by the supplied+-- fold.+--+-- Definition:+--+-- >>> groupsWhile cmp f = Stream.parseMany (Parser.groupBy cmp f)+{-# INLINE_NORMAL groupsWhile #-}+groupsWhile :: Monad m+ => (a -> a -> Bool)+ -> Fold m a b+ -> Stream m a+ -> Stream m b+{-+groupsWhile eq fld = parseMany (PRD.groupBy eq fld)+-}+groupsWhile cmp (Fold fstep initial _ final) (Stream step state) =+ Stream stepOuter (GroupingInit state)++ where++ {-# INLINE_LATE stepOuter #-}+ stepOuter _ (GroupingInit st) = do+ -- XXX Note that if the stream stops without yielding a single element+ -- in the group we discard the "initial" effect.+ res <- initial+ return+ $ case res of+ FL.Partial s -> Skip $ GroupingDo st s+ FL.Done b -> Yield b $ GroupingInit st+ stepOuter gst (GroupingDo st fs) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ r <- fstep fs x+ case r of+ FL.Partial fs1 -> go SPEC x s fs1+ FL.Done b -> return $ Yield b (GroupingInit s)+ Skip s -> return $ Skip $ GroupingDo s fs+ Stop -> final fs >> 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+ r <- fstep acc x+ case r of+ FL.Partial fs1 -> go SPEC prev s fs1+ FL.Done b -> return $ Yield b (GroupingInit s)+ else do+ r <- final acc+ return $ Yield r (GroupingInitWith s x)+ Skip s -> go SPEC prev s acc+ Stop -> do+ r <- final acc+ return $ Yield r GroupingDone+ stepOuter _ (GroupingInitWith st x) = do+ res <- initial+ return+ $ case res of+ FL.Partial s -> Skip $ GroupingDoWith st s x+ FL.Done b -> Yield b $ GroupingInitWith st x+ stepOuter gst (GroupingDoWith st fs prev) = do+ res <- fstep fs prev+ case res of+ FL.Partial fs1 -> go SPEC st fs1+ FL.Done b -> return $ Yield b (GroupingInit st)++ 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 prev x+ then do+ r <- fstep acc x+ case r of+ FL.Partial fs1 -> go SPEC s fs1+ FL.Done b -> return $ Yield b (GroupingInit s)+ else do+ r <- final acc+ return $ Yield r (GroupingInitWith s x)+ Skip s -> go SPEC s acc+ Stop -> do+ r <- final acc+ return $ Yield r GroupingDone+ stepOuter _ (GroupingYield _ _) = error "groupsWhile: Unreachable"+ stepOuter _ GroupingDone = return Stop++-- | The argument order of the comparison function in `groupsWhile` is+-- different than that of `groupsBy`.+--+-- In `groupsBy` the comparison function takes the next element as the first+-- argument and the previous element as the second argument. In `groupsWhile`+-- the first argument is the previous element and second argument is the next+-- element.+{-# DEPRECATED groupsBy "Please use groupsWhile instead. Please note the change in the argument order of the comparison function." #-}+{-# INLINE_NORMAL groupsBy #-}+groupsBy :: Monad m+ => (a -> a -> Bool)+ -> Fold m a b+ -> Stream m a+ -> Stream m b+groupsBy cmp = groupsWhile (flip cmp)++-- |+--+-- Definition:+--+-- >>> groupsRollingBy cmp f = Stream.parseMany (Parser.groupByRolling cmp f)+--+{-# INLINE_NORMAL groupsRollingBy #-}+groupsRollingBy :: Monad m+ => (a -> a -> Bool)+ -> Fold m a b+ -> Stream m a+ -> Stream m b+{-+groupsRollingBy eq fld = parseMany (PRD.groupByRolling eq fld)+-}+groupsRollingBy cmp (Fold fstep initial _ final) (Stream step state) =+ Stream stepOuter (GroupingInit state)++ where++ {-# INLINE_LATE stepOuter #-}+ stepOuter _ (GroupingInit st) = do+ -- XXX Note that if the stream stops without yielding a single element+ -- in the group we discard the "initial" effect.+ res <- initial+ return+ $ case res of+ FL.Partial fs -> Skip $ GroupingDo st fs+ FL.Done fb -> Yield fb $ GroupingInit st+ stepOuter gst (GroupingDo st fs) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ r <- fstep fs x+ case r of+ FL.Partial fs1 -> go SPEC x s fs1+ FL.Done fb -> return $ Yield fb (GroupingInit s)+ Skip s -> return $ Skip $ GroupingDo s fs+ Stop -> final fs >> 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+ r <- fstep acc x+ case r of+ FL.Partial fs1 -> go SPEC x s fs1+ FL.Done b -> return $ Yield b (GroupingInit s)+ else do+ r <- final acc+ return $ Yield r (GroupingInitWith s x)+ Skip s -> go SPEC prev s acc+ Stop -> do+ r <- final acc+ return $ Yield r GroupingDone+ stepOuter _ (GroupingInitWith st x) = do+ res <- initial+ return+ $ case res of+ FL.Partial s -> Skip $ GroupingDoWith st s x+ FL.Done b -> Yield b $ GroupingInitWith st x+ stepOuter gst (GroupingDoWith st fs previous) = do+ res <- fstep fs previous+ case res of+ FL.Partial s -> go SPEC previous st s+ FL.Done b -> return $ Yield b (GroupingInit st)++ where++ -- XXX GHC: groupsWhile has one less parameter in this go loop and it+ -- fuses. However, groupsRollingBy does not fuse, removing the prev+ -- parameter makes it fuse. Something needs to be fixed in GHC. The+ -- workaround for this is noted in the comments below.+ go !_ prev !stt !acc = do+ res <- step (adaptState gst) stt+ case res of+ Yield x s -> do+ if cmp prev x+ then do+ r <- fstep acc x+ case r of+ FL.Partial fs1 -> go SPEC x s fs1+ FL.Done b -> return $ Yield b (GroupingInit st)+ else do+ {-+ r <- final acc+ return $ Yield r (GroupingInitWith s x)+ -}+ -- The code above does not let groupBy fuse. We use the+ -- alternative code below instead. Instead of jumping+ -- to GroupingInitWith state, we unroll the code of+ -- GroupingInitWith state here to help GHC with stream+ -- fusion.+ result <- initial+ r <- final acc+ return+ $ Yield r+ $ case result of+ FL.Partial fsi -> GroupingDoWith s fsi x+ FL.Done b -> GroupingYield b (GroupingInit s)+ Skip s -> go SPEC prev s acc+ Stop -> do+ r <- final acc+ return $ Yield r GroupingDone+ stepOuter _ (GroupingYield r next) = return $ Yield r next+ stepOuter _ GroupingDone = return Stop++------------------------------------------------------------------------------+-- Splitting - by a predicate+------------------------------------------------------------------------------++data WordsByState st fs b+ = WordsByInit st+ | WordsByDo st !fs+ | WordsByDone+ | WordsByYield !b (WordsByState st fs b)++-- | Split the stream after stripping leading, trailing, and repeated+-- separators determined by the predicate supplied. The tokens after splitting+-- are collected by the supplied fold. In other words, the tokens are parsed in+-- the same way as words are parsed from whitespace separated text.+--+-- >>> f x = Stream.toList $ Stream.wordsBy (== '.') Fold.toList $ Stream.fromList x+-- >>> f "a.b"+-- ["a","b"]+-- >>> f "a..b"+-- ["a","b"]+-- >>> f ".a..b."+-- ["a","b"]+--+{-# INLINE_NORMAL wordsBy #-}+wordsBy :: Monad m => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+wordsBy predicate (Fold fstep initial _ final) (Stream step state) =+ Stream stepOuter (WordsByInit state)++ where++ {-# INLINE_LATE stepOuter #-}+ stepOuter _ (WordsByInit st) = do+ res <- initial+ return+ $ case res of+ FL.Partial s -> Skip $ WordsByDo st s+ FL.Done b -> Yield b (WordsByInit st)++ stepOuter gst (WordsByDo st fs) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ if predicate x+ then do+ resi <- initial+ return+ $ case resi of+ FL.Partial fs1 -> Skip $ WordsByDo s fs1+ FL.Done b -> Yield b (WordsByInit s)+ else do+ r <- fstep fs x+ case r of+ FL.Partial fs1 -> go SPEC s fs1+ FL.Done b -> return $ Yield b (WordsByInit s)+ Skip s -> return $ Skip $ WordsByDo s fs+ Stop -> final fs >> return Stop++ where++ go !_ stt !acc = do+ res <- step (adaptState gst) stt+ case res of+ Yield x s -> do+ if predicate x+ then do+ {-+ r <- final acc+ return $ Yield r (WordsByInit s)+ -}+ -- The above code does not fuse well. Need to check why+ -- GHC is not able to simplify it well. Using the code+ -- below, instead of jumping through the WordsByInit+ -- state always, we directly go to WordsByDo state in+ -- the common case of Partial.+ resi <- initial+ r <- final acc+ return+ $ Yield r+ $ case resi of+ FL.Partial fs1 -> WordsByDo s fs1+ FL.Done b -> WordsByYield b (WordsByInit s)+ else do+ r <- fstep acc x+ case r of+ FL.Partial fs1 -> go SPEC s fs1+ FL.Done b -> return $ Yield b (WordsByInit s)+ Skip s -> go SPEC s acc+ Stop -> do+ r <- final acc+ return $ Yield r WordsByDone++ stepOuter _ WordsByDone = return Stop++ stepOuter _ (WordsByYield b next) = return $ Yield b next++------------------------------------------------------------------------------+-- Splitting on a sequence+------------------------------------------------------------------------------++-- String search algorithms:+-- http://www-igm.univ-mlv.fr/~lecroq/string/index.html++-- XXX Can GHC find a way to modularise this? Can we write different cases+-- i.e.g single element, word hash, karp-rabin as different functions and then+-- be able to combine them into a single state machine?++{-# ANN type TakeEndBySeqState Fuse #-}+data TakeEndBySeqState mba rb rh ck w s b x =+ TakeEndBySeqInit+ | TakeEndBySeqYield !b (TakeEndBySeqState mba rb rh ck w s b x)+ | TakeEndBySeqDone++ | TakeEndBySeqSingle s x++ | TakeEndBySeqWordInit !Int !w s+ | TakeEndBySeqWordLoop !w s+ | TakeEndBySeqWordDone !Int !w++ | TakeEndBySeqKRInit s mba+ | TakeEndBySeqKRInit1 s mba !Int+ | TakeEndBySeqKRLoop s mba !rh !ck+ | TakeEndBySeqKRCheck s mba !rh+ | TakeEndBySeqKRDone !Int rb++-- | If the pattern is empty the output stream is empty.+{-# INLINE_NORMAL takeEndBySeqWith #-}+takeEndBySeqWith+ :: forall m a. (MonadIO m, Unbox a, Enum a, Eq a)+ => Bool+ -> Array a+ -> Stream m a+ -> Stream m a+takeEndBySeqWith withSep patArr (Stream step state) =+ Stream stepOuter TakeEndBySeqInit++ where++ patLen = A.length patArr+ patBytes = A.byteLength patArr+ maxIndex = patLen - 1+ maxOffset = patBytes - SIZE_OF(a)+ elemBits = SIZE_OF(a) * 8++ -- For word pattern case+ wordMask :: Word+ wordMask = (1 `shiftL` (elemBits * patLen)) - 1++ elemMask :: Word+ elemMask = (1 `shiftL` elemBits) - 1++ wordPat :: Word+ wordPat = wordMask .&. A.foldl' addToWord 0 patArr++ addToWord wd a = (wd `shiftL` elemBits) .|. fromIntegral (fromEnum a)++ -- For Rabin-Karp search+ 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++ skip = return . Skip++ {-# INLINE yield #-}+ yield x !s = skip $ TakeEndBySeqYield x s++ {-# INLINE_LATE stepOuter #-}+ stepOuter _ TakeEndBySeqInit = do+ -- XXX When we statically specify the method compiler is able to+ -- simplify the code better and removes the handling of other states.+ -- When it is determined dynamically, the code is less efficient. For+ -- example, the single element search degrades by 80% if the handling+ -- of other cases is present. We need to investigate this further but+ -- until then we can guide the compiler statically where we can. If we+ -- want to use single element search statically then we can use+ -- takeEndBy instead.+ --+ -- XXX Is there a way for GHC to statically determine patLen when we+ -- use an array created from a static string as pattern e.g. "\n".+ case () of+ _ | patLen == 0 -> return Stop+ | patLen == 1 -> do+ pat <- liftIO $ A.unsafeGetIndexIO 0 patArr+ return $ Skip $ TakeEndBySeqSingle state pat+ | SIZE_OF(a) * patLen <= sizeOf (Proxy :: Proxy Word) ->+ return $ Skip $ TakeEndBySeqWordInit 0 0 state+ | otherwise -> do+ (MutArray mba _ _ _) :: MutArray a <-+ liftIO $ MutArray.emptyOf patLen+ skip $ TakeEndBySeqKRInit state mba++ ---------------------+ -- Single yield point+ ---------------------++ stepOuter _ (TakeEndBySeqYield x next) = return $ Yield x next++ -----------------+ -- Done+ -----------------++ stepOuter _ TakeEndBySeqDone = return Stop++ -----------------+ -- Single Pattern+ -----------------++ stepOuter gst (TakeEndBySeqSingle st pat) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s ->+ if pat /= x+ then yield x (TakeEndBySeqSingle s pat)+ else do+ if withSep+ then yield x TakeEndBySeqDone+ else return Stop+ Skip s -> skip $ TakeEndBySeqSingle s pat+ Stop -> return Stop++ ---------------------------+ -- Short Pattern - Shift Or+ ---------------------------++ -- Note: Karp-Rabin is roughly 15% slower than word hash for a 2 element+ -- pattern. This may be useful for common cases like splitting lines using+ -- "\r\n".+ stepOuter _ (TakeEndBySeqWordDone 0 _) = do+ return Stop+ stepOuter _ (TakeEndBySeqWordDone n wrd) = do+ let old = elemMask .&. (wrd `shiftR` (elemBits * (n - 1)))+ in yield+ (toEnum $ fromIntegral old)+ (TakeEndBySeqWordDone (n - 1) wrd)++ -- XXX If we remove this init state for perf experiment the time taken+ -- reduces to half, there may be some optimization opportunity here.+ stepOuter gst (TakeEndBySeqWordInit idx wrd st) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ let wrd1 = addToWord wrd x+ next+ | idx /= maxIndex =+ TakeEndBySeqWordInit (idx + 1) wrd1 s+ | wrd1 .&. wordMask /= wordPat =+ TakeEndBySeqWordLoop wrd1 s+ | otherwise = TakeEndBySeqDone+ if withSep+ then yield x next+ else skip next+ Skip s -> skip $ TakeEndBySeqWordInit idx wrd s+ Stop ->+ if withSep+ then return Stop+ else skip $ TakeEndBySeqWordDone idx wrd++ stepOuter gst (TakeEndBySeqWordLoop wrd st) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ -- XXX Never use a lazy expression as state, that causes issues+ -- in simplification because the state argument of Yield is+ -- lazy, maybe we can make that strict.+ let wrd1 = addToWord wrd x+ old = (wordMask .&. wrd)+ `shiftR` (elemBits * (patLen - 1))+ !y =+ if withSep+ then x+ else toEnum $ fromIntegral old+ -- Note: changing the nesting order of if and yield makes a+ -- difference in performance.+ if wrd1 .&. wordMask /= wordPat+ then yield y (TakeEndBySeqWordLoop wrd1 s)+ else yield y TakeEndBySeqDone+ Skip s -> skip $ TakeEndBySeqWordLoop wrd s+ Stop ->+ if withSep+ then return Stop+ else skip $ TakeEndBySeqWordDone patLen wrd++ -------------------------------+ -- General Pattern - Karp Rabin+ -------------------------------++ stepOuter gst (TakeEndBySeqKRInit st0 mba) = do+ res <- step (adaptState gst) st0+ case res of+ Yield x s -> do+ liftIO $ pokeAt 0 mba x+ if withSep+ then yield x (TakeEndBySeqKRInit1 s mba (SIZE_OF(a)))+ else skip $ TakeEndBySeqKRInit1 s mba (SIZE_OF(a))+ Skip s -> skip $ TakeEndBySeqKRInit s mba+ Stop -> return Stop++ stepOuter gst (TakeEndBySeqKRInit1 st mba offset) = do+ res <- step (adaptState gst) st+ let arr :: Array a = Array+ { arrContents = mba+ , arrStart = 0+ , arrEnd = patBytes+ }+ case res of+ Yield x s -> do+ liftIO $ pokeAt offset mba x+ let next =+ if offset /= maxOffset+ then TakeEndBySeqKRInit1 s mba (offset + SIZE_OF(a))+ else+ let ringHash = A.foldl' addCksum 0 arr+ in if ringHash == patHash+ then TakeEndBySeqKRCheck s mba 0+ else TakeEndBySeqKRLoop s mba 0 ringHash+ if withSep+ then yield x next+ else skip next+ Skip s -> skip $ TakeEndBySeqKRInit1 s mba offset+ Stop -> do+ if withSep+ then return Stop+ else do+ let rb = RingArray+ { ringContents = mba+ , ringSize = offset+ , ringHead = 0+ }+ in skip $ TakeEndBySeqKRDone offset rb++ stepOuter gst (TakeEndBySeqKRLoop st mba rh cksum) = do+ res <- step (adaptState gst) st+ let rb = RingArray+ { ringContents = mba+ , ringSize = patBytes+ , ringHead = rh+ }+ case res of+ Yield x s -> do+ (rb1, old) <- liftIO (RB.replace rb x)+ let cksum1 = deltaCksum cksum old x+ let rh1 = ringHead rb1+ next =+ if cksum1 /= patHash+ then TakeEndBySeqKRLoop s mba rh1 cksum1+ else TakeEndBySeqKRCheck s mba rh1+ if withSep+ then yield x next+ else yield old next+ Skip s -> skip $ TakeEndBySeqKRLoop s mba rh cksum+ Stop -> do+ if withSep+ then return Stop+ else skip $ TakeEndBySeqKRDone patBytes rb++ stepOuter _ (TakeEndBySeqKRCheck st mba rh) = do+ let rb = RingArray+ { ringContents = mba+ , ringSize = patBytes+ , ringHead = rh+ }+ matches <- liftIO $ RB.eqArray rb patArr+ if matches+ then return Stop+ else skip $ TakeEndBySeqKRLoop st mba rh patHash++ stepOuter _ (TakeEndBySeqKRDone 0 _) = return Stop+ stepOuter _ (TakeEndBySeqKRDone len rb) = do+ assert (len >= 0) (return ())+ old <- RB.unsafeGetHead rb+ let rb1 = RB.moveForward rb+ yield old $ TakeEndBySeqKRDone (len - SIZE_OF(a)) rb1++-- | Take the stream until the supplied sequence is encountered. Take the+-- sequence as well and stop.+--+-- Usage:+--+-- >>> f pat xs = Stream.toList $ Stream.takeEndBySeq (Array.fromList pat) $ Stream.fromList xs+-- >>> f "fgh" "abcdefghijk"+-- "abcdefgh"+-- >>> f "lmn" "abcdefghijk"+-- "abcdefghijk"+-- >>> f "" "abcdefghijk"+-- ""+--+{-# INLINE takeEndBySeq #-}+takeEndBySeq+ :: forall m a. (MonadIO m, Unbox a, Enum a, Eq a)+ => Array a+ -> Stream m a+ -> Stream m a+takeEndBySeq = takeEndBySeqWith True++-- | Take the stream until the supplied sequence is encountered. Do not take+-- the sequence.+--+-- Usage:+--+-- >>> f pat xs = Stream.toList $ Stream.takeEndBySeq_ (Array.fromList pat) $ Stream.fromList xs+-- >>> f "fgh" "abcdefghijk"+-- "abcde"+-- >>> f "lmn" "abcdefghijk"+-- "abcdefghijk"+-- >>> f "" "abcdefghijk"+-- ""+--+{-# INLINE takeEndBySeq_ #-}+takeEndBySeq_+ :: forall m a. (MonadIO m, Unbox a, Enum a, Eq a)+ => Array a+ -> Stream m a+ -> Stream m a+takeEndBySeq_ = takeEndBySeqWith False++{-+-- 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+ }+-}++-- XXX using "fs" as the last arg in Constructors may simplify the code a bit,+-- because we can use the constructor directly without having to create "jump"+-- functions.+{-# ANN type SplitOnSeqState Fuse #-}+data SplitOnSeqState mba rb rh ck w fs s b x =+ SplitOnSeqInit+ | SplitOnSeqYield b (SplitOnSeqState mba rb rh ck w fs s b x)+ | SplitOnSeqDone++ | SplitOnSeqEmpty !fs s++ | SplitOnSeqSingle0 !fs s x+ | SplitOnSeqSingle !fs s x++ | SplitOnSeqWordInit0 !fs s+ | SplitOnSeqWordInit Int Word !fs s+ | SplitOnSeqWordLoop !w s !fs+ | SplitOnSeqWordDone Int !fs !w++ | SplitOnSeqKRInit0 Int !fs s mba+ | SplitOnSeqKRInit Int !fs s mba+ | SplitOnSeqKRLoop fs s mba !rh !ck+ | SplitOnSeqKRCheck fs s mba !rh+ | SplitOnSeqKRDone Int !fs rb++ | SplitOnSeqReinit (fs -> SplitOnSeqState mba rb rh ck w fs s b x)++-- XXX Need to fix empty stream split behavior++-- | Like 'splitSepBy_' but splits the stream on a sequence of elements rather than+-- a single element. Parses a sequence of tokens separated by an infixed+-- separator e.g. @a;b;c@ is parsed as @a@, @b@, @c@. If the pattern is empty+-- then each element is a match, thus the fold is finalized on each element.+--+-- >>> splitSepBy p xs = Stream.fold Fold.toList $ Stream.splitSepBySeq_ (Array.fromList p) Fold.toList (Stream.fromList xs)+--+-- >>> splitSepBy "" ""+-- []+--+-- >>> splitSepBy "" "a...b"+-- ["a",".",".",".","b"]+--+-- >>> splitSepBy ".." ""+-- []+--+-- >>> splitSepBy ".." "a...b"+-- ["a",".b"]+--+-- >>> splitSepBy ".." "abc"+-- ["abc"]+--+-- >>> splitSepBy ".." ".."+-- ["",""]+--+-- >>> splitSepBy "." ".a"+-- ["","a"]+--+-- >>> splitSepBy "." "a."+-- ["a",""]+--+-- Uses Rabin-Karp algorithm for substring search.+--+{-# INLINE_NORMAL splitSepBySeq_ #-}+splitSepBySeq_, splitOnSeq+ :: forall m a b. (MonadIO m, Unbox a, Enum a, Eq a)+ => Array a+ -> Fold m a b+ -> Stream m a+ -> Stream m b+splitSepBySeq_ patArr (Fold fstep initial _ final) (Stream step state) =+ Stream stepOuter SplitOnSeqInit++ where++ patLen = A.length patArr+ patBytes = A.byteLength patArr+ maxIndex = patLen - 1+ maxOffset = patBytes - SIZE_OF(a)+ elemBits = SIZE_OF(a) * 8++ -- For word pattern case+ wordMask :: Word+ wordMask = (1 `shiftL` (elemBits * patLen)) - 1++ elemMask :: Word+ elemMask = (1 `shiftL` elemBits) - 1++ wordPat :: Word+ wordPat = wordMask .&. A.foldl' addToWord 0 patArr++ addToWord wd a = (wd `shiftL` elemBits) .|. fromIntegral (fromEnum a)++ -- For Rabin-Karp search+ 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++ skip = return . Skip++ nextAfterInit nextGen stepRes =+ case stepRes of+ FL.Partial s -> nextGen s+ FL.Done b -> SplitOnSeqYield b (SplitOnSeqReinit nextGen)++ {-# INLINE yieldReinit #-}+ yieldReinit nextGen fs =+ initial >>= skip . SplitOnSeqYield fs . nextAfterInit nextGen++ {-# INLINE_LATE stepOuter #-}+ stepOuter _ SplitOnSeqInit = do+ res <- initial+ case res of+ FL.Partial acc+ | patLen == 0 ->+ return $ Skip $ SplitOnSeqEmpty acc state+ | patLen == 1 -> do+ pat <- liftIO $ A.unsafeGetIndexIO 0 patArr+ return $ Skip $ SplitOnSeqSingle0 acc state pat+ | SIZE_OF(a) * patLen <= sizeOf (Proxy :: Proxy Word) ->+ return $ Skip $ SplitOnSeqWordInit0 acc state+ | otherwise -> do+ (MutArray mba _ _ _) :: MutArray a <-+ liftIO $ MutArray.emptyOf patLen+ skip $ SplitOnSeqKRInit0 0 acc state mba+ FL.Done b -> skip $ SplitOnSeqYield b SplitOnSeqInit++ stepOuter _ (SplitOnSeqYield x next) = return $ Yield x next++ ---------------------------+ -- Checkpoint+ ---------------------------++ stepOuter _ (SplitOnSeqReinit nextGen) =+ initial >>= skip . nextAfterInit nextGen++ ---------------------------+ -- Empty pattern+ ---------------------------++ stepOuter gst (SplitOnSeqEmpty acc st) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ r <- fstep acc x+ b1 <-+ case r of+ FL.Partial acc1 -> final acc1+ FL.Done b -> return b+ let jump c = SplitOnSeqEmpty c s+ in yieldReinit jump b1+ Skip s -> skip (SplitOnSeqEmpty acc s)+ Stop -> final acc >> return Stop++ -----------------+ -- Done+ -----------------++ stepOuter _ SplitOnSeqDone = return Stop++ -----------------+ -- Single Pattern+ -----------------++ stepOuter gst (SplitOnSeqSingle0 fs st pat) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ -- XXX This code block is duplicated in SplitOnSeqSingle state+ let jump c = SplitOnSeqSingle c s pat+ if pat == x+ then final fs >>= yieldReinit jump+ else do+ r <- fstep fs x+ case r of+ FL.Partial fs1 ->+ pure $ Skip $ SplitOnSeqSingle fs1 s pat+ FL.Done b -> yieldReinit jump b+ Skip s -> pure $ Skip $ SplitOnSeqSingle0 fs s pat+ Stop -> final fs >> pure Stop++ stepOuter gst (SplitOnSeqSingle fs0 st0 pat) = do+ go SPEC fs0 st0++ where++ -- The local loop increases allocations by 6% but improves CPU+ -- performance by 14%.+ go !_ !fs !st = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ let jump c = SplitOnSeqSingle c s pat+ if pat == x+ then final fs >>= yieldReinit jump+ else do+ r <- fstep fs x+ case r of+ FL.Partial fs1 -> go SPEC fs1 s+ FL.Done b -> yieldReinit jump b+ Skip s -> go SPEC fs s+ Stop -> do+ r <- final fs+ return $ Skip $ SplitOnSeqYield r SplitOnSeqDone++ ---------------------------+ -- Short Pattern - Shift Or+ ---------------------------++ -- Note: We fill the matching buffer before we emit anything, in case it+ -- matches and we have to drop it. Though we could be more eager in+ -- emitting as soon as we know that the pattern cannot match. But still the+ -- worst case will remain the same, in case a match is going to happen we+ -- will have to delay until the very end.++ stepOuter _ (SplitOnSeqWordDone 0 fs _) = do+ r <- final fs+ skip $ SplitOnSeqYield r SplitOnSeqDone+ stepOuter _ (SplitOnSeqWordDone n fs wrd) = do+ let old = elemMask .&. (wrd `shiftR` (elemBits * (n - 1)))+ r <- fstep fs (toEnum $ fromIntegral old)+ case r of+ FL.Partial fs1 -> skip $ SplitOnSeqWordDone (n - 1) fs1 wrd+ FL.Done b -> do+ let jump c = SplitOnSeqWordDone (n - 1) c wrd+ yieldReinit jump b++ stepOuter gst (SplitOnSeqWordInit0 fs st) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s ->+ let wrd1 = addToWord 0 x+ in pure $ Skip $ SplitOnSeqWordInit 1 wrd1 fs s+ Skip s -> pure $ Skip $ SplitOnSeqWordInit0 fs s+ Stop -> final fs >> pure Stop++ stepOuter gst (SplitOnSeqWordInit idx0 wrd0 fs st0) =+ go SPEC idx0 wrd0 st0++ where++ {-# INLINE go #-}+ go !_ !idx !wrd !st = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ let wrd1 = addToWord wrd x+ if idx == maxIndex+ then do+ if wrd1 .&. wordMask == wordPat+ then do+ let jump c = SplitOnSeqWordInit 0 0 c s+ final fs >>= yieldReinit jump+ else skip $ SplitOnSeqWordLoop wrd1 s fs+ else go SPEC (idx + 1) wrd1 s+ Skip s -> go SPEC idx wrd s+ Stop -> do+ if idx /= 0+ then skip $ SplitOnSeqWordDone idx fs wrd+ else do+ r <- final fs+ skip $ SplitOnSeqYield r SplitOnSeqDone++ stepOuter gst (SplitOnSeqWordLoop wrd0 st0 fs0) =+ go SPEC wrd0 st0 fs0++ where++ -- This loop does not affect allocations but it improves the CPU+ -- performance signifcantly compared to looping using state.+ {-# INLINE go #-}+ go !_ !wrd !st !fs = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ let jump c = SplitOnSeqWordInit 0 0 c s+ wrd1 = addToWord wrd x+ old = (wordMask .&. wrd)+ `shiftR` (elemBits * (patLen - 1))+ r <- fstep fs (toEnum $ fromIntegral old)+ case r of+ FL.Partial fs1 -> do+ if wrd1 .&. wordMask == wordPat+ then final fs1 >>= yieldReinit jump+ else go SPEC wrd1 s fs1+ FL.Done b -> yieldReinit jump b+ Skip s -> go SPEC wrd s fs+ Stop -> skip $ SplitOnSeqWordDone patLen fs wrd++ -------------------------------+ -- General Pattern - Karp Rabin+ -------------------------------++ -- XXX Document this pattern for writing efficient code. Loop around only+ -- required elements in the recursive loop, build the structures being+ -- manipulated locally e.g. we are passing only mba, here and build an+ -- array using patLen and arrStart from the surrounding context.++ stepOuter gst (SplitOnSeqKRInit0 offset fs st mba) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ liftIO $ pokeAt offset mba x+ skip $ SplitOnSeqKRInit (offset + SIZE_OF(a)) fs s mba+ Skip s -> skip $ SplitOnSeqKRInit0 offset fs s mba+ Stop -> final fs >> pure Stop++ stepOuter gst (SplitOnSeqKRInit offset fs st mba) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ liftIO $ pokeAt offset mba x+ if offset == maxOffset+ then do+ let arr :: Array a = Array+ { arrContents = mba+ , arrStart = 0+ , arrEnd = patBytes+ }+ let ringHash = A.foldl' addCksum 0 arr+ if ringHash == patHash && A.byteEq arr patArr+ then skip $ SplitOnSeqKRCheck fs s mba 0+ else skip $ SplitOnSeqKRLoop fs s mba 0 ringHash+ else skip $ SplitOnSeqKRInit (offset + SIZE_OF(a)) fs s mba+ Skip s -> skip $ SplitOnSeqKRInit offset fs s mba+ Stop -> do+ let rb = RingArray+ { ringContents = mba+ , ringSize = offset+ , ringHead = 0+ }+ skip $ SplitOnSeqKRDone offset fs rb++ -- XXX The recursive "go" is more efficient than the state based recursion+ -- code commented out below. Perhaps its more efficient because of+ -- factoring out "mba" outside the loop.+ --+ stepOuter gst (SplitOnSeqKRLoop fs0 st0 mba rh0 cksum0) =+ go SPEC fs0 st0 rh0 cksum0++ where++ go !_ !fs !st !rh !cksum = do+ res <- step (adaptState gst) st+ let rb = RingArray+ { ringContents = mba+ , ringSize = patBytes+ , ringHead = rh+ }+ case res of+ Yield x s -> do+ (rb1, old) <- liftIO (RB.replace rb x)+ r <- fstep fs old+ case r of+ FL.Partial fs1 -> do+ let cksum1 = deltaCksum cksum old x+ let rh1 = ringHead rb1+ if cksum1 == patHash+ then skip $ SplitOnSeqKRCheck fs1 s mba rh1+ else go SPEC fs1 s rh1 cksum1+ FL.Done b -> do+ -- XXX the old code looks wrong as we are resetting+ -- the ring head but the ring still has old+ -- elements as we are not resetting the size.+ let jump c = SplitOnSeqKRInit 0 c s mba+ yieldReinit jump b+ Skip s -> go SPEC fs s rh cksum+ Stop -> skip $ SplitOnSeqKRDone patBytes fs rb++ -- XXX The following code is 5 times slower compared to the recursive loop+ -- based code above. Need to investigate why. One possibility is that the+ -- go loop above does not thread around the ring buffer (rb). This code may+ -- be causing the state to bloat and getting allocated on each iteration.+ -- We can check the cmm/asm code to confirm. If so a good GHC solution to+ -- such problem is needed. One way to avoid this could be to use unboxed+ -- mutable state?+ {-+ stepOuter gst (SplitOnSeqKRLoop fs st rb rh cksum) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ old <- liftIO $ peek rh+ let cksum1 = deltaCksum cksum old x+ fs1 <- fstep fs old+ if (cksum1 == patHash)+ then do+ r <- done fs1+ skip $ SplitOnSeqYield r $ SplitOnSeqKRInit 0 s rb rh+ else do+ rh1 <- liftIO (RB.unsafeInsert rb rh x)+ skip $ SplitOnSeqKRLoop fs1 s rb rh1 cksum1+ Skip s -> skip $ SplitOnSeqKRLoop fs s rb rh cksum+ Stop -> skip $ SplitOnSeqKRDone patLen fs rb rh+ -}++ stepOuter _ (SplitOnSeqKRCheck fs st mba rh) = do+ let rb = RingArray+ { ringContents = mba+ , ringSize = patBytes+ , ringHead = rh+ }+ res <- liftIO $ RB.eqArray rb patArr+ if res+ then do+ r <- final fs+ let jump c = SplitOnSeqKRInit 0 c st mba+ yieldReinit jump r+ else skip $ SplitOnSeqKRLoop fs st mba rh patHash++ stepOuter _ (SplitOnSeqKRDone 0 fs _) = do+ r <- final fs+ skip $ SplitOnSeqYield r SplitOnSeqDone+ stepOuter _ (SplitOnSeqKRDone len fs rb) = do+ assert (len >= 0) (return ())+ old <- RB.unsafeGetHead rb+ let rb1 = RB.moveForward rb+ r <- fstep fs old+ case r of+ FL.Partial fs1 -> skip $ SplitOnSeqKRDone (len - SIZE_OF(a)) fs1 rb1+ FL.Done b -> do+ let jump c = SplitOnSeqKRDone (len - SIZE_OF(a)) c rb1+ yieldReinit jump b++RENAME(splitOnSeq,splitSepBySeq_)++{-# ANN type SplitOnSuffixSeqState Fuse #-}+data SplitOnSuffixSeqState mba rb rh ck w fs s b x =+ SplitOnSuffixSeqInit+ | SplitOnSuffixSeqYield b (SplitOnSuffixSeqState mba rb rh ck w fs s b x)+ | SplitOnSuffixSeqDone++ | SplitOnSuffixSeqEmpty !fs s++ | SplitOnSuffixSeqSingleInit !fs s x+ | SplitOnSuffixSeqSingle !fs s x++ | SplitOnSuffixSeqWordInit !fs s+ | SplitOnSuffixSeqWordLoop !w s !fs+ | SplitOnSuffixSeqWordDone Int !fs !w++ | SplitOnSuffixSeqKRInit !fs s mba+ | SplitOnSuffixSeqKRInit1 !fs s mba+ | SplitOnSuffixSeqKRLoop fs s mba !rh !ck+ | SplitOnSuffixSeqKRCheck fs s mba !rh+ | SplitOnSuffixSeqKRDone Int !fs rb++ | SplitOnSuffixSeqReinit+ (fs -> SplitOnSuffixSeqState mba rb rh ck w fs s b x)++-- | @splitOnSuffixSeq withSep pat fld input@ splits the input using @pat@ as a+-- suffixed separator, the resulting split segments are fed to the fold @fld@.+-- If @withSep@ is True then the separator sequence is also suffixed with the+-- split segments.+--+-- /Internal/+{-# INLINE_NORMAL splitOnSuffixSeq #-}+splitOnSuffixSeq+ :: forall m a b. (MonadIO m, Unbox a, Enum a, Eq a)+ => Bool+ -> Array a+ -> Fold m a b+ -> Stream m a+ -> Stream m b+splitOnSuffixSeq withSep patArr (Fold fstep initial _ final) (Stream step state) =+ Stream stepOuter SplitOnSuffixSeqInit++ where++ patLen = A.length patArr+ patBytes = A.byteLength patArr+ maxIndex = patLen - 1+ maxOffset = patBytes - SIZE_OF(a)+ elemBits = SIZE_OF(a) * 8++ -- For word pattern case+ wordMask :: Word+ wordMask = (1 `shiftL` (elemBits * patLen)) - 1++ elemMask :: Word+ elemMask = (1 `shiftL` elemBits) - 1++ wordPat :: Word+ wordPat = wordMask .&. A.foldl' addToWord 0 patArr++ addToWord wd a = (wd `shiftL` elemBits) .|. fromIntegral (fromEnum a)++ nextAfterInit nextGen stepRes =+ case stepRes of+ FL.Partial s -> nextGen s+ FL.Done b ->+ SplitOnSuffixSeqYield b (SplitOnSuffixSeqReinit nextGen)++ {-# INLINE yieldReinit #-}+ yieldReinit nextGen fs =+ initial >>= skip . SplitOnSuffixSeqYield fs . nextAfterInit nextGen++ -- For single element pattern case+ {-# INLINE processYieldSingle #-}+ processYieldSingle pat x s fs = do+ let jump c = SplitOnSuffixSeqSingleInit c s pat+ if pat == x+ then do+ r <- if withSep then fstep fs x else return $ FL.Partial fs+ b1 <-+ case r of+ FL.Partial fs1 -> final fs1+ FL.Done b -> return b+ yieldReinit jump b1+ else do+ r <- fstep fs x+ case r of+ FL.Partial fs1 -> skip $ SplitOnSuffixSeqSingle fs1 s pat+ FL.Done b -> yieldReinit jump b++ -- For Rabin-Karp search+ 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++ skip = return . Skip++ {-# INLINE_LATE stepOuter #-}+ stepOuter _ SplitOnSuffixSeqInit = do+ res <- initial+ case res of+ FL.Partial fs+ | patLen == 0 ->+ skip $ SplitOnSuffixSeqEmpty fs state+ | patLen == 1 -> do+ pat <- liftIO $ A.unsafeGetIndexIO 0 patArr+ skip $ SplitOnSuffixSeqSingleInit fs state pat+ | SIZE_OF(a) * patLen <= sizeOf (Proxy :: Proxy Word) ->+ skip $ SplitOnSuffixSeqWordInit fs state+ | otherwise -> do+ (MutArray mba _ _ _) :: MutArray a <-+ liftIO $ MutArray.emptyOf patLen+ skip $ SplitOnSuffixSeqKRInit fs state mba+ FL.Done fb -> skip $ SplitOnSuffixSeqYield fb SplitOnSuffixSeqInit++ stepOuter _ (SplitOnSuffixSeqYield x next) = return $ Yield x next++ ---------------------------+ -- Reinit+ ---------------------------++ stepOuter _ (SplitOnSuffixSeqReinit nextGen) =+ initial >>= skip . nextAfterInit nextGen++ ---------------------------+ -- Empty pattern+ ---------------------------++ stepOuter gst (SplitOnSuffixSeqEmpty acc st) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ let jump c = SplitOnSuffixSeqEmpty c s+ r <- fstep acc x+ b1 <-+ case r of+ FL.Partial fs -> final fs+ FL.Done b -> return b+ yieldReinit jump b1+ Skip s -> skip (SplitOnSuffixSeqEmpty acc s)+ Stop -> final acc >> return Stop++ -----------------+ -- Done+ -----------------++ stepOuter _ SplitOnSuffixSeqDone = return Stop++ -----------------+ -- Single Pattern+ -----------------++ stepOuter gst (SplitOnSuffixSeqSingleInit fs st pat) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> processYieldSingle pat x s fs+ Skip s -> skip $ SplitOnSuffixSeqSingleInit fs s pat+ Stop -> final fs >> return Stop++ stepOuter gst (SplitOnSuffixSeqSingle fs st pat) = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> processYieldSingle pat x s fs+ Skip s -> skip $ SplitOnSuffixSeqSingle fs s pat+ Stop -> do+ r <- final fs+ skip $ SplitOnSuffixSeqYield r SplitOnSuffixSeqDone++ ---------------------------+ -- Short Pattern - Shift Or+ ---------------------------++ stepOuter _ (SplitOnSuffixSeqWordDone 0 fs _) = do+ r <- final fs+ skip $ SplitOnSuffixSeqYield r SplitOnSuffixSeqDone+ stepOuter _ (SplitOnSuffixSeqWordDone n fs wrd) = do+ let old = elemMask .&. (wrd `shiftR` (elemBits * (n - 1)))+ r <- fstep fs (toEnum $ fromIntegral old)+ case r of+ FL.Partial fs1 -> skip $ SplitOnSuffixSeqWordDone (n - 1) fs1 wrd+ FL.Done b -> do+ let jump c = SplitOnSuffixSeqWordDone (n - 1) c wrd+ yieldReinit jump b++ stepOuter gst (SplitOnSuffixSeqWordInit fs0 st0) = do+ res <- step (adaptState gst) st0+ case res of+ Yield x s -> do+ let wrd = addToWord 0 x+ r <- if withSep then fstep fs0 x else return $ FL.Partial fs0+ case r of+ FL.Partial fs1 -> go SPEC 1 wrd s fs1+ FL.Done b -> do+ let jump c = SplitOnSuffixSeqWordInit c s+ yieldReinit jump b+ Skip s -> skip (SplitOnSuffixSeqWordInit fs0 s)+ Stop -> final fs0 >> return Stop++ where++ {-# INLINE go #-}+ go !_ !idx !wrd !st !fs = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ let jump c = SplitOnSuffixSeqWordInit c s+ let wrd1 = addToWord wrd x+ r <- if withSep then fstep fs x else return $ FL.Partial fs+ case r of+ FL.Partial fs1+ | idx /= maxIndex ->+ go SPEC (idx + 1) wrd1 s fs1+ | wrd1 .&. wordMask /= wordPat ->+ skip $ SplitOnSuffixSeqWordLoop wrd1 s fs1+ | otherwise ->+ final fs1 >>= yieldReinit jump+ FL.Done b -> yieldReinit jump b+ Skip s -> go SPEC idx wrd s fs+ Stop ->+ if withSep+ then do+ r <- final fs+ skip $ SplitOnSuffixSeqYield r SplitOnSuffixSeqDone+ else skip $ SplitOnSuffixSeqWordDone idx fs wrd++ stepOuter gst (SplitOnSuffixSeqWordLoop wrd0 st0 fs0) =+ go SPEC wrd0 st0 fs0++ where++ {-# INLINE go #-}+ go !_ !wrd !st !fs = do+ res <- step (adaptState gst) st+ case res of+ Yield x s -> do+ let jump c = SplitOnSuffixSeqWordInit c s+ wrd1 = addToWord wrd x+ old = (wordMask .&. wrd)+ `shiftR` (elemBits * (patLen - 1))+ r <-+ if withSep+ then fstep fs x+ else fstep fs (toEnum $ fromIntegral old)+ case r of+ FL.Partial fs1 ->+ if wrd1 .&. wordMask == wordPat+ then final fs1 >>= yieldReinit jump+ else go SPEC wrd1 s fs1+ FL.Done b -> yieldReinit jump b+ Skip s -> go SPEC wrd s fs+ Stop ->+ if withSep+ then do+ r <- final fs+ skip $ SplitOnSuffixSeqYield r SplitOnSuffixSeqDone+ else skip $ SplitOnSuffixSeqWordDone patLen fs wrd++ -------------------------------+ -- General Pattern - Karp Rabin+ -------------------------------++ stepOuter gst (SplitOnSuffixSeqKRInit fs st0 mba) = do+ res <- step (adaptState gst) st0+ case res of+ Yield x s -> do+ liftIO $ pokeAt 0 mba x+ r <- if withSep then fstep fs x else return $ FL.Partial fs+ case r of+ FL.Partial fs1 ->+ skip $ SplitOnSuffixSeqKRInit1 fs1 s mba+ FL.Done b -> do+ let jump c = SplitOnSuffixSeqKRInit c s mba+ yieldReinit jump b+ Skip s -> skip $ SplitOnSuffixSeqKRInit fs s mba+ Stop -> final fs >> return Stop++ stepOuter gst (SplitOnSuffixSeqKRInit1 fs0 st0 mba) = do+ go SPEC (SIZE_OF(a)) st0 fs0++ where++ go !_ !offset st !fs = do+ res <- step (adaptState gst) st+ let arr :: Array a = Array+ { arrContents = mba+ , arrStart = 0+ , arrEnd = patBytes+ }+ case res of+ Yield x s -> do+ liftIO $ pokeAt offset mba x+ r <- if withSep then fstep fs x else return $ FL.Partial fs+ let ringHash = A.foldl' addCksum 0 arr+ case r of+ FL.Partial fs1+ | offset /= maxOffset ->+ go SPEC (offset + SIZE_OF(a)) s fs1+ | ringHash == patHash ->+ skip $ SplitOnSuffixSeqKRCheck fs1 s mba 0+ | otherwise ->+ skip $ SplitOnSuffixSeqKRLoop+ fs1 s mba 0 ringHash+ FL.Done b -> do+ let jump c = SplitOnSuffixSeqKRInit c s mba+ yieldReinit jump b+ Skip s -> go SPEC offset s fs+ Stop -> do+ -- do not issue a blank segment when we end at pattern+ if offset == maxOffset && A.byteEq arr patArr+ then final fs >> return Stop+ else if withSep+ then do+ r <- final fs+ skip $ SplitOnSuffixSeqYield r SplitOnSuffixSeqDone+ else do+ let rb = RingArray+ { ringContents = mba+ , ringSize = offset+ , ringHead = 0+ }+ in skip $ SplitOnSuffixSeqKRDone offset fs rb++ stepOuter gst (SplitOnSuffixSeqKRLoop fs0 st0 mba rh0 cksum0) =+ go SPEC fs0 st0 rh0 cksum0++ where++ go !_ !fs !st !rh !cksum = do+ res <- step (adaptState gst) st+ let rb = RingArray+ { ringContents = mba+ , ringSize = patBytes+ , ringHead = rh+ }+ case res of+ Yield x s -> do+ (rb1, old) <- liftIO (RB.replace rb x)+ let cksum1 = deltaCksum cksum old x+ let rh1 = ringHead rb1+ r <- if withSep then fstep fs x else fstep fs old+ case r of+ FL.Partial fs1 ->+ if cksum1 /= patHash+ then go SPEC fs1 s rh1 cksum1+ else skip $ SplitOnSuffixSeqKRCheck fs1 s mba rh1+ FL.Done b -> do+ let jump c = SplitOnSuffixSeqKRInit c s mba+ yieldReinit jump b+ Skip s -> go SPEC fs s rh cksum+ Stop -> do+ if withSep+ then do+ r <- final fs+ skip $ SplitOnSuffixSeqYield r SplitOnSuffixSeqDone+ else skip $ SplitOnSuffixSeqKRDone patBytes fs rb++ stepOuter _ (SplitOnSuffixSeqKRCheck fs st mba rh) = do+ let rb = RingArray+ { ringContents = mba+ , ringSize = patBytes+ , ringHead = rh+ }+ matches <- liftIO $ RB.eqArray rb patArr+ if matches+ then do+ r <- final fs+ let jump c = SplitOnSuffixSeqKRInit c st mba+ yieldReinit jump r+ else skip $ SplitOnSuffixSeqKRLoop fs st mba rh patHash++ stepOuter _ (SplitOnSuffixSeqKRDone 0 fs _) = do+ r <- final fs+ skip $ SplitOnSuffixSeqYield r SplitOnSuffixSeqDone+ stepOuter _ (SplitOnSuffixSeqKRDone len fs rb) = do+ assert (len >= 0) (return ())+ old <- RB.unsafeGetHead rb+ let rb1 = RB.moveForward rb+ r <- fstep fs old+ case r of+ FL.Partial fs1 ->+ skip $ SplitOnSuffixSeqKRDone (len - SIZE_OF(a)) fs1 rb1+ FL.Done b -> do+ let jump c = SplitOnSuffixSeqKRDone (len - SIZE_OF(a)) c rb1+ yieldReinit jump b++-- | Parses a sequence of tokens suffixed by a separator e.g. @a;b;c;@ is+-- parsed as @a;@, @b;@, @c;@. If the pattern is empty the input stream is+-- returned as it is.+--+-- Equivalent to the following:+--+-- >>> splitEndBySeq pat f = Stream.foldMany (Fold.takeEndBySeq pat f)+--+-- Usage:+--+-- >>> f p = Stream.splitEndBySeq (Array.fromList p) Fold.toList+-- >>> splitEndBy p xs = Stream.fold Fold.toList $ f p (Stream.fromList xs)+--+-- >>> splitEndBy "" ""+-- []+--+-- >>> splitEndBy "" "a...b"+-- ["a",".",".",".","b"]+--+-- >>> splitEndBy ".." ""+-- []+--+--+-- >>> splitEndBy ".." "a...b"+-- ["a..",".b"]+--+--+-- >>> splitEndBy ".." "abc"+-- ["abc"]+--+-- >>> splitEndBy ".." ".."+-- [".."]+--+-- >>> splitEndBy "." ".a"+-- [".","a"]+--+-- >>> splitEndBy "." "a."+-- ["a."]+--+-- Uses Rabin-Karp algorithm for substring search.+--+{-# INLINE_NORMAL splitEndBySeq #-}+splitEndBySeq+ :: forall m a b. (MonadIO m, Unbox a, Enum a, Eq a)+ => Array a+ -> Fold m a b+ -> Stream m a+ -> Stream m b+splitEndBySeq = splitOnSuffixSeq True++-- | Like 'splitEndBySeq' but drops the separators and returns only the tokens.+--+-- Equivalent to the following:+--+-- >>> splitEndBySeq_ pat f = Stream.foldMany (Fold.takeEndBySeq_ pat f)+--+-- Usage:+--+-- >>> f p = Stream.splitEndBySeq_ (Array.fromList p) Fold.toList+-- >>> splitEndBy_ p xs = Stream.fold Fold.toList $ f p (Stream.fromList xs)+--+-- >>> splitEndBy_ "" ""+-- []+--+-- >>> splitEndBy_ "" "a...b"+-- ["a",".",".",".","b"]+--+-- >>> splitEndBy_ ".." ""+-- []+--+-- >>> splitEndBy_ ".." "a...b"+-- ["a",".b"]+--+-- >>> splitEndBy_ ".." "abc"+-- ["abc"]+--+-- >>> splitEndBy_ ".." ".."+-- [""]+--+-- >>> splitEndBy_ "." ".a"+-- ["","a"]+--+-- >>> splitEndBy_ "." "a."+-- ["a"]+--+-- Uses Rabin-Karp algorithm for substring search.+--+{-# INLINE_NORMAL splitEndBySeq_ #-}+splitEndBySeq_+ :: forall m a b. (MonadIO m, Unbox a, Enum a, Eq a)+ => Array a+ -> Fold m a b+ -> Stream m a+ -> Stream m b+splitEndBySeq_ = splitOnSuffixSeq False++-- Implement this as a fold or a parser instead.+-- This can be implemented easily using Rabin Karp++-- | Split post any one of the given patterns.+--+-- /Unimplemented/+{-# INLINE splitEndBySeqOneOf #-}+splitEndBySeqOneOf :: -- (Monad m, Unboxed a, Integral a) =>+ [Array a] -> Fold m a b -> Stream m a -> Stream m b+splitEndBySeqOneOf _subseq _f _m = undefined++-- | Split on a prefixed separator element, dropping the separator. The+-- supplied 'Fold' is applied on the split segments.+--+-- @+-- > splitOnPrefix' p xs = Stream.toList $ Stream.splitOnPrefix p (Fold.toList) (Stream.fromList xs)+-- > splitOnPrefix' (== '.') ".a.b"+-- ["a","b"]+-- @+--+-- An empty stream results in an empty output stream:+-- @+-- > splitOnPrefix' (== '.') ""+-- []+-- @+--+-- An empty segment consisting of only a prefix is folded to the default output+-- of the fold:+--+-- @+-- > splitOnPrefix' (== '.') "."+-- [""]+--+-- > splitOnPrefix' (== '.') ".a.b."+-- ["a","b",""]+--+-- > splitOnPrefix' (== '.') ".a..b"+-- ["a","","b"]+--+-- @+--+-- A prefix is optional at the beginning of the stream:+--+-- @+-- > splitOnPrefix' (== '.') "a"+-- ["a"]+--+-- > splitOnPrefix' (== '.') "a.b"+-- ["a","b"]+-- @+--+-- 'splitOnPrefix' is an inverse of 'intercalatePrefix' with a single element:+--+-- > Stream.intercalatePrefix (Stream.fromPure '.') Unfold.fromList . Stream.splitOnPrefix (== '.') Fold.toList === id+--+-- Assuming the input stream does not contain the separator:+--+-- > Stream.splitOnPrefix (== '.') Fold.toList . Stream.intercalatePrefix (Stream.fromPure '.') Unfold.fromList === id+--+-- /Unimplemented/+{-# INLINE splitBeginBy_ #-}+splitBeginBy_ :: -- (MonadCatch m) =>+ (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+splitBeginBy_ _predicate _f = undefined+ -- parseMany (Parser.sliceBeginBy predicate f)++-- 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]+-- > [[],[]]++-- This can be implemented easily using Rabin Karp+-- | Split on any one of the given patterns.+--+-- /Unimplemented/+--+{-# INLINE splitSepBySeqOneOf #-}+splitSepBySeqOneOf :: -- (Monad m, Unboxed a, Integral a) =>+ [Array a] -> Fold m a b -> Stream m a -> Stream m b+splitSepBySeqOneOf _subseq _f _m =+ undefined -- D.fromStreamD $ D.splitOnAny f subseq (D.toStreamD m)++------------------------------------------------------------------------------+-- Nested Container Transformation+------------------------------------------------------------------------------++{-# ANN type SplitState Fuse #-}+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+ => (f a -> Bool) -- isEmpty?+ -> (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 isEmpty 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 isEmpty buf+ 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++------------------------------------------------------------------------------+-- Trimming+------------------------------------------------------------------------------++-- | Drop prefix from the input stream if present.+--+-- Space: @O(1)@+--+-- See also stripPrefix.+--+-- /Unimplemented/+{-# INLINE dropPrefix #-}+dropPrefix ::+ -- (Monad m, Eq a) =>+ Stream m a -> Stream m a -> Stream m a+dropPrefix = error "Not implemented yet!"++-- | Drop all matching infix from the input stream if present. Infix stream+-- may be consumed multiple times.+--+-- Space: @O(n)@ where n is the length of the infix.+--+-- See also stripInfix.+--+-- /Unimplemented/+{-# INLINE dropInfix #-}+dropInfix ::+ -- (Monad m, Eq a) =>+ Stream m a -> Stream m a -> Stream m a+dropInfix = error "Not implemented yet!"++-- | Drop suffix from the input stream if present. Suffix stream may be+-- consumed multiple times.+--+-- Space: @O(n)@ where n is the length of the suffix.+--+-- See also stripSuffix.+--+-- /Unimplemented/+{-# INLINE dropSuffix #-}+dropSuffix ::+ -- (Monad m, Eq a) =>+ Stream m a -> Stream m a -> Stream m a+dropSuffix = error "Not implemented yet!"
− src/Streamly/Internal/Data/Stream/Reduce.hs
@@ -1,444 +0,0 @@--- |--- Module : Streamly.Internal.Data.Stream.Reduce--- Copyright : (c) 2017 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ Reduce streams by streams, folds or parsers.--module Streamly.Internal.Data.Stream.Reduce- (- -- * Reduce By Streams- dropPrefix- , dropInfix- , dropSuffix-- -- * Reduce By Folds- -- |- -- Reduce a stream by folding or parsing chunks of the stream. Functions- -- generally ending in these shapes:- --- -- @- -- f (Fold m a b) -> Stream m a -> Stream m b- -- f (Parser a m b) -> Stream m a -> Stream m b- -- @-- -- ** Generic Folding- -- | Apply folds on a stream.- , foldMany- , foldManyPost- , refoldMany- , foldSequence- , foldIterateM- , refoldIterateM- , reduceIterateBfs-- -- ** Chunking- -- | Element unaware grouping.- , chunksOf-- -- ** Splitting- -- XXX Implement these as folds or parsers instead.- , splitOnSuffixSeqAny- , splitOnPrefix- , splitOnAny-- -- * Reduce By Parsers- -- ** Generic Parsing- -- | Apply parsers on a stream.- , parseMany- , parseManyD- , parseManyTill- , parseSequence- , parseIterate-- )-where--import Control.Monad.IO.Class (MonadIO(..))-import Streamly.Internal.Data.Array.Type (Array)-import Streamly.Internal.Data.Fold.Type (Fold (..))-import Streamly.Internal.Data.Parser (Parser (..))-import Streamly.Internal.Data.Parser.ParserD (ParseError)-import Streamly.Internal.Data.Refold.Type (Refold (..))-import Streamly.Internal.Data.Stream.Bottom (foldManyPost)-import Streamly.Internal.Data.Stream.Type (Stream, fromStreamD, toStreamD)-import Streamly.Internal.Data.Unboxed (Unbox)--import qualified Streamly.Internal.Data.Array.Type as Array-import qualified Streamly.Internal.Data.Parser.ParserD as ParserD-import qualified Streamly.Internal.Data.Stream.StreamD as D--import Prelude hiding (concatMap, map)---- $setup--- >>> :m--- >>> import Prelude hiding (zipWith, concatMap, concat)--- >>> import Streamly.Internal.Data.Stream as Stream--- >>> import qualified Streamly.Data.Fold as Fold--- >>> import qualified Streamly.Internal.Data.Fold as Fold--- >>> import qualified Streamly.Internal.Data.Unfold as Unfold--- >>> import qualified Streamly.Internal.Data.Parser as Parser--- >>> import qualified Streamly.Data.Array as Array----------------------------------------------------------------------------------- Trimming----------------------------------------------------------------------------------- | Drop prefix from the input stream if present.------ Space: @O(1)@------ /Unimplemented/-{-# INLINE dropPrefix #-}-dropPrefix ::- -- (Monad m, Eq a) =>- Stream m a -> Stream m a -> Stream m a-dropPrefix = error "Not implemented yet!"---- | Drop all matching infix from the input stream if present. Infix stream--- may be consumed multiple times.------ Space: @O(n)@ where n is the length of the infix.------ /Unimplemented/-{-# INLINE dropInfix #-}-dropInfix ::- -- (Monad m, Eq a) =>- Stream m a -> Stream m a -> Stream m a-dropInfix = error "Not implemented yet!"---- | Drop suffix from the input stream if present. Suffix stream may be--- consumed multiple times.------ Space: @O(n)@ where n is the length of the suffix.------ /Unimplemented/-{-# INLINE dropSuffix #-}-dropSuffix ::- -- (Monad m, Eq a) =>- Stream m a -> Stream m a -> Stream m a-dropSuffix = error "Not implemented yet!"----------------------------------------------------------------------------------- Folding----------------------------------------------------------------------------------- | Apply a 'Fold' repeatedly on a stream and emit the results in the--- output stream. Unlike 'foldManyPost' it evaluates the fold after the stream,--- therefore, an empty input stream results in an empty output stream.------ Definition:------ >>> foldMany f = Stream.parseMany (Parser.fromFold f)------ Example, empty stream:------ >>> f = Fold.take 2 Fold.sum--- >>> fmany = Stream.fold Fold.toList . Stream.foldMany f--- >>> fmany $ Stream.fromList []--- []------ Example, last fold empty:------ >>> fmany $ Stream.fromList [1..4]--- [3,7]------ Example, last fold non-empty:------ >>> fmany $ Stream.fromList [1..5]--- [3,7,5]------ Note that using a closed fold e.g. @Fold.take 0@, would result in an--- infinite stream on a non-empty input stream.----{-# INLINE foldMany #-}-foldMany- :: Monad m- => Fold m a b- -> Stream m a- -> Stream m b-foldMany f m = fromStreamD $ D.foldMany f (toStreamD m)---- | Like 'foldMany' but using the 'Refold' type instead of 'Fold'.------ /Pre-release/-{-# INLINE refoldMany #-}-refoldMany :: Monad m =>- Refold m c a b -> m c -> Stream m a -> Stream m b-refoldMany f action = fromStreamD . D.refoldMany f action . toStreamD---- | Apply a stream of folds to an input stream and emit the results in the--- output stream.------ /Unimplemented/----{-# INLINE foldSequence #-}-foldSequence- :: -- Monad m =>- Stream m (Fold m a b)- -> Stream m a- -> Stream m b-foldSequence _f _m = undefined---- | Iterate a fold generator on a stream. The initial value @b@ is used to--- generate the first fold, the fold is applied on the stream and the result of--- the fold is used to generate the next fold and so on.------ >>> import Data.Monoid (Sum(..))--- >>> f x = return (Fold.take 2 (Fold.sconcat x))--- >>> s = fmap Sum $ Stream.fromList [1..10]--- >>> Stream.fold Fold.toList $ fmap getSum $ Stream.foldIterateM f (pure 0) s--- [3,10,21,36,55,55]------ This is the streaming equivalent of monad like sequenced application of--- folds where next fold is dependent on the previous fold.------ /Pre-release/----{-# INLINE foldIterateM #-}-foldIterateM ::- Monad m => (b -> m (Fold m a b)) -> m b -> Stream m a -> Stream m b-foldIterateM f i m = fromStreamD $ D.foldIterateM f i (toStreamD m)---- | Like 'foldIterateM' but using the 'Refold' type instead. This could be--- much more efficient due to stream fusion.------ /Internal/-{-# INLINE refoldIterateM #-}-refoldIterateM :: Monad m =>- Refold m b a b -> m b -> Stream m a -> Stream m b-refoldIterateM c i m = fromStreamD $ D.refoldIterateM c i (toStreamD m)---- | Binary BFS style reduce, folds a level entirely using the supplied fold--- function, collecting the outputs as next level of the tree, then repeats the--- same process on the next level. The last elements of a previously folded--- level are folded first.-{-# INLINE reduceIterateBfs #-}-reduceIterateBfs :: Monad m =>- (a -> a -> m a) -> Stream m a -> m (Maybe a)-reduceIterateBfs f stream = D.reduceIterateBfs f (toStreamD stream)----------------------------------------------------------------------------------- Splitting----------------------------------------------------------------------------------- Implement this as a fold or a parser instead.--- This can be implemented easily using Rabin Karp--- | Split post any one of the given patterns.------ /Unimplemented/-{-# INLINE splitOnSuffixSeqAny #-}-splitOnSuffixSeqAny :: -- (Monad m, Unboxed a, Integral a) =>- [Array a] -> Fold m a b -> Stream m a -> Stream m b-splitOnSuffixSeqAny _subseq _f _m = undefined- -- D.fromStreamD $ D.splitPostAny f subseq (D.toStreamD m)---- | Split on a prefixed separator element, dropping the separator. The--- supplied 'Fold' is applied on the split segments.------ @--- > splitOnPrefix' p xs = Stream.toList $ Stream.splitOnPrefix p (Fold.toList) (Stream.fromList xs)--- > splitOnPrefix' (== '.') ".a.b"--- ["a","b"]--- @------ An empty stream results in an empty output stream:--- @--- > splitOnPrefix' (== '.') ""--- []--- @------ An empty segment consisting of only a prefix is folded to the default output--- of the fold:------ @--- > splitOnPrefix' (== '.') "."--- [""]------ > splitOnPrefix' (== '.') ".a.b."--- ["a","b",""]------ > splitOnPrefix' (== '.') ".a..b"--- ["a","","b"]------ @------ A prefix is optional at the beginning of the stream:------ @--- > splitOnPrefix' (== '.') "a"--- ["a"]------ > splitOnPrefix' (== '.') "a.b"--- ["a","b"]--- @------ 'splitOnPrefix' is an inverse of 'intercalatePrefix' with a single element:------ > Stream.intercalatePrefix (Stream.fromPure '.') Unfold.fromList . Stream.splitOnPrefix (== '.') Fold.toList === id------ Assuming the input stream does not contain the separator:------ > Stream.splitOnPrefix (== '.') Fold.toList . Stream.intercalatePrefix (Stream.fromPure '.') Unfold.fromList === id------ /Unimplemented/-{-# INLINE splitOnPrefix #-}-splitOnPrefix :: -- (IsStream t, MonadCatch m) =>- (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b-splitOnPrefix _predicate _f = undefined- -- parseMany (Parser.sliceBeginBy predicate f)---- 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]--- > [[],[]]---- This can be implemented easily using Rabin Karp--- | Split on any one of the given patterns.------ /Unimplemented/----{-# INLINE splitOnAny #-}-splitOnAny :: -- (Monad m, Unboxed a, Integral a) =>- [Array a] -> Fold m a b -> Stream m a -> Stream m b-splitOnAny _subseq _f _m =- undefined -- D.fromStreamD $ D.splitOnAny f subseq (D.toStreamD m)----------------------------------------------------------------------------------- Parsing----------------------------------------------------------------------------------- | Apply a 'Parser' repeatedly on a stream and emit the parsed values in the--- output stream.------ Example:------ >>> s = Stream.fromList [1..10]--- >>> parser = Parser.takeBetween 0 2 Fold.sum--- >>> Stream.fold Fold.toList $ Stream.parseMany parser s--- [Right 3,Right 7,Right 11,Right 15,Right 19]------ This is the streaming equivalent of the 'Streamly.Data.Parser.many' parse--- combinator.------ Known Issues: When the parser fails there is no way to get the remaining--- stream.----{-# INLINE parseMany #-}-parseMany- :: Monad m- => Parser a m b- -> Stream m a- -> Stream m (Either ParseError b)-parseMany p m =- fromStreamD $ D.parseManyD p (toStreamD m)---- | Same as parseMany but for StreamD streams.------ /Internal/----{-# INLINE parseManyD #-}-parseManyD- :: Monad m- => ParserD.Parser a m b- -> Stream m a- -> Stream m (Either ParseError b)-parseManyD p m =- fromStreamD $ D.parseManyD p (toStreamD m)---- | Apply a stream of parsers to an input stream and emit the results in the--- output stream.------ /Unimplemented/----{-# INLINE parseSequence #-}-parseSequence- :: -- Monad m =>- Stream m (Parser a m b)- -> Stream m a- -> Stream m b-parseSequence _f _m = undefined---- XXX Change the parser arguments' order---- | @parseManyTill collect test stream@ tries the parser @test@ on the input,--- if @test@ fails it backtracks and tries @collect@, after @collect@ succeeds--- @test@ is tried again and so on. The parser stops when @test@ succeeds. The--- output of @test@ is discarded and the output of @collect@ is emitted in the--- output stream. The parser fails if @collect@ fails.------ /Unimplemented/----{-# INLINE parseManyTill #-}-parseManyTill ::- -- MonadThrow m =>- Parser a m b- -> Parser a m x- -> t m a- -> t m b-parseManyTill = undefined---- | Iterate a parser generating function on a stream. The initial value @b@ is--- used to generate the first parser, the parser is applied on the stream and--- the result is used to generate the next parser and so on.------ >>> import Data.Monoid (Sum(..))--- >>> s = Stream.fromList [1..10]--- >>> Stream.fold Fold.toList $ fmap getSum $ Stream.catRights $ Stream.parseIterate (\b -> Parser.takeBetween 0 2 (Fold.sconcat b)) (Sum 0) $ fmap Sum s--- [3,10,21,36,55,55]------ This is the streaming equivalent of monad like sequenced application of--- parsers where next parser is dependent on the previous parser.------ /Pre-release/----{-# INLINE parseIterate #-}-parseIterate- :: Monad m- => (b -> Parser a m b)- -> b- -> Stream m a- -> Stream m (Either ParseError b)-parseIterate f i m = fromStreamD $- D.parseIterateD f i (toStreamD m)----------------------------------------------------------------------------------- Chunking----------------------------------------------------------------------------------- | @chunksOf n stream@ groups the elements in the input stream into arrays of--- @n@ elements each.------ Same as the following but may be more efficient:------ >>> chunksOf n = Stream.foldMany (Array.writeN n)------ /Pre-release/-{-# INLINE chunksOf #-}-chunksOf :: (MonadIO m, Unbox a)- => Int -> Stream m a -> Stream m (Array a)-chunksOf n = fromStreamD . Array.chunksOf n . toStreamD
+ src/Streamly/Internal/Data/Stream/Step.hs view
@@ -0,0 +1,39 @@+-- |+-- Module : Streamly.Internal.Data.Stream.Step+-- Copyright : (c) 2018 Composewell Technologies+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC++module Streamly.Internal.Data.Stream.Step+ (+ -- * The stream type+ Step (..)+ )+where++import Fusion.Plugin.Types (Fuse(..))++-- | 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++{-+fromPure :: Monad m => a -> s -> m (Step s a)+fromPure a = return . Yield a++skip :: Monad m => s -> m (Step s a)+skip = return . Skip++stop :: Monad m => m (Step s a)+stop = return Stop+-}
src/Streamly/Internal/Data/Stream/StreamD.hs view
@@ -5,38 +5,12 @@ -- Maintainer : streamly@composewell.com -- Stability : experimental -- Portability : GHC------ Direct style re-implementation of CPS stream in--- "Streamly.Internal.Data.Stream.StreamK". 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--- @ module Streamly.Internal.Data.Stream.StreamD+{-# DEPRECATED "Please use \"Streamly.Internal.Data.Stream\" instead." #-} (- module Streamly.Internal.Data.Stream.StreamD.Type- , module Streamly.Internal.Data.Stream.StreamD.Generate- , module Streamly.Internal.Data.Stream.StreamD.Eliminate- , module Streamly.Internal.Data.Stream.StreamD.Exception- , module Streamly.Internal.Data.Stream.StreamD.Lift- , module Streamly.Internal.Data.Stream.StreamD.Transformer- , module Streamly.Internal.Data.Stream.StreamD.Nesting- , module Streamly.Internal.Data.Stream.StreamD.Transform- , module Streamly.Internal.Data.Stream.StreamD.Top- , module Streamly.Internal.Data.Stream.StreamD.Container+ module Streamly.Internal.Data.Stream ) where -import Streamly.Internal.Data.Stream.StreamD.Type-import Streamly.Internal.Data.Stream.StreamD.Generate-import Streamly.Internal.Data.Stream.StreamD.Eliminate-import Streamly.Internal.Data.Stream.StreamD.Exception-import Streamly.Internal.Data.Stream.StreamD.Lift-import Streamly.Internal.Data.Stream.StreamD.Transformer-import Streamly.Internal.Data.Stream.StreamD.Nesting-import Streamly.Internal.Data.Stream.StreamD.Transform-import Streamly.Internal.Data.Stream.StreamD.Top-import Streamly.Internal.Data.Stream.StreamD.Container+import Streamly.Internal.Data.Stream
− src/Streamly/Internal/Data/Stream/StreamD/Container.hs
@@ -1,302 +0,0 @@-{-# LANGUAGE CPP #-}--- |--- Module : Streamly.Internal.Data.Stream.StreamD.Container--- Copyright : (c) 2019 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ Stream operations that require transformers or containers like Set or Map.--module Streamly.Internal.Data.Stream.StreamD.Container- (- nub-- -- * Joins for unconstrained types- , joinLeftGeneric- , joinOuterGeneric-- -- * Joins with Ord constraint- , joinInner- , joinLeft- , joinOuter- )-where--#include "inline.hs"--import Control.Monad.IO.Class (MonadIO)-import Control.Monad.Trans.State.Strict (get, put)-import Data.Function ((&))-import Data.Maybe (isJust)-import Streamly.Internal.Data.Stream.StreamD.Step (Step(..))-import Streamly.Internal.Data.Stream.StreamD.Type- (Stream(..), mkCross, unCross)--import qualified Data.Map.Strict as Map-import qualified Data.Set as Set-import qualified Streamly.Data.Fold as Fold-import qualified Streamly.Internal.Data.Array.Generic as Array-import qualified Streamly.Internal.Data.Array.Mut.Type as MA-import qualified Streamly.Internal.Data.Stream.StreamD.Type as Stream-import qualified Streamly.Internal.Data.Stream.StreamD.Nesting as Stream-import qualified Streamly.Internal.Data.Stream.StreamD.Generate as Stream-import qualified Streamly.Internal.Data.Stream.StreamD.Transform as Stream-import qualified Streamly.Internal.Data.Stream.StreamD.Transformer as Stream--#include "DocTestDataStream.hs"---- | The memory used is proportional to the number of unique elements in the--- stream. If we want to limit the memory we can just use "take" to limit the--- uniq elements in the stream.-{-# INLINE_NORMAL nub #-}-nub :: (Monad m, Ord a) => Stream m a -> Stream m a-nub (Stream step1 state1) = Stream step (Set.empty, state1)-- where-- step gst (set, st) = do- r <- step1 gst st- return- $ case r of- Yield x s ->- if Set.member x set- then Skip (set, s)- else Yield x (Set.insert x set, s)- Skip s -> Skip (set, s)- Stop -> Stop---- XXX Generate error if a duplicate insertion is attempted?-toMap :: (Monad m, Ord k) => Stream m (k, v) -> m (Map.Map k v)-toMap =- let f = Fold.foldl' (\kv (k, b) -> Map.insert k b kv) Map.empty- in Stream.fold f---- If the second stream is too big it can be partitioned based on hashes and--- then we can process one parition at a time.------ XXX An IntMap may be faster when the keys are Int.--- XXX Use hashmap instead of map?------ | Like 'joinInner' but uses a 'Map' for efficiency.------ If the input streams have duplicate keys, the behavior is undefined.------ For space efficiency use the smaller stream as the second stream.------ Space: O(n)------ Time: O(m + n)------ /Pre-release/-{-# INLINE joinInner #-}-joinInner :: (Monad m, Ord k) =>- Stream m (k, a) -> Stream m (k, b) -> Stream m (k, a, b)-joinInner s1 s2 =- Stream.concatEffect $ do- km <- toMap s2- pure $ Stream.mapMaybe (joinAB km) s1-- where-- joinAB kvm (k, a) =- case k `Map.lookup` kvm of- Just b -> Just (k, a, b)- Nothing -> Nothing---- XXX We can do this concurrently.--- XXX If the second stream is sorted and passed as an Array or a seek capable--- stream then we could use binary search if we have an Ord instance or--- Ordering returning function. The time complexity would then become (m x log--- n).---- XXX Check performance of StreamD vs StreamK---- | Like 'joinInner' but emit @(a, Just b)@, and additionally, for those @a@'s--- that are not equal to any @b@ emit @(a, Nothing)@.------ The second stream is evaluated multiple times. If the stream is a--- consume-once stream then the caller should cache it in an 'Data.Array.Array'--- before calling this function. Caching may also improve performance if the--- stream is expensive to evaluate.------ >>> joinRightGeneric eq = flip (Stream.joinLeftGeneric eq)------ Space: O(n) assuming the second stream is cached in memory.------ Time: O(m x n)------ /Unimplemented/-{-# INLINE joinLeftGeneric #-}-joinLeftGeneric :: Monad m =>- (a -> b -> Bool) -> Stream m a -> Stream m b -> Stream m (a, Maybe b)-joinLeftGeneric eq s1 s2 = Stream.evalStateT (return False) $ unCross $ do- a <- mkCross (Stream.liftInner s1)- -- XXX should we use StreamD monad here?- -- XXX Is there a better way to perform some action at the end of a loop- -- iteration?- mkCross (Stream.fromEffect $ put False)- let final = Stream.concatEffect $ do- r <- get- if r- then pure Stream.nil- else pure (Stream.fromPure Nothing)- b <- mkCross (fmap Just (Stream.liftInner s2) `Stream.append` final)- case b of- Just b1 ->- if a `eq` b1- then do- mkCross (Stream.fromEffect $ put True)- return (a, Just b1)- else mkCross Stream.nil- Nothing -> return (a, Nothing)---- XXX rename to joinLeftOrd?---- | A more efficient 'joinLeft' using a hashmap for efficiency.------ Space: O(n)------ Time: O(m + n)------ /Pre-release/-{-# INLINE joinLeft #-}-joinLeft :: (Ord k, Monad m) =>- Stream m (k, a) -> Stream m (k, b) -> Stream m (k, a, Maybe b)-joinLeft s1 s2 =- Stream.concatEffect $ do- km <- toMap s2- return $ fmap (joinAB km) s1-- where-- joinAB km (k, a) =- case k `Map.lookup` km of- Just b -> (k, a, Just b)- Nothing -> (k, a, Nothing)---- XXX We can do this concurrently.---- XXX Check performance of StreamD vs StreamK---- | Like 'joinLeft' but emits a @(Just a, Just b)@. Like 'joinLeft', for those--- @a@'s that are not equal to any @b@ emit @(Just a, Nothing)@, but--- additionally, for those @b@'s that are not equal to any @a@ emit @(Nothing,--- Just b)@.------ For space efficiency use the smaller stream as the second stream.------ Space: O(n)------ Time: O(m x n)------ /Pre-release/-{-# INLINE joinOuterGeneric #-}-joinOuterGeneric :: MonadIO m =>- (a -> b -> Bool)- -> Stream m a- -> Stream m b- -> Stream m (Maybe a, Maybe b)-joinOuterGeneric eq s1 s =- Stream.concatEffect $ do- inputArr <- Array.fromStream s- let len = Array.length inputArr- foundArr <-- Stream.fold- (MA.writeN len)- (Stream.fromList (Prelude.replicate len False))- return $ go inputArr foundArr `Stream.append` leftOver inputArr foundArr-- where-- leftOver inputArr foundArr =- let stream1 = Array.read inputArr- stream2 = Stream.unfold MA.reader foundArr- in Stream.filter- isJust- ( Stream.zipWith (\x y ->- if y- then Nothing- else Just (Nothing, Just x)- ) stream1 stream2- ) & Stream.catMaybes-- evalState = Stream.evalStateT (return False) . unCross-- go inputArr foundArr = evalState $ do- a <- mkCross (Stream.liftInner s1)- -- XXX should we use StreamD monad here?- -- XXX Is there a better way to perform some action at the end of a loop- -- iteration?- mkCross (Stream.fromEffect $ put False)- let final = Stream.concatEffect $ do- r <- get- if r- then pure Stream.nil- else pure (Stream.fromPure Nothing)- (i, b) <-- let stream = Array.read inputArr- in mkCross- (Stream.indexed $ fmap Just (Stream.liftInner stream) `Stream.append` final)-- case b of- Just b1 ->- if a `eq` b1- then do- mkCross (Stream.fromEffect $ put True)- MA.putIndex i foundArr True- return (Just a, Just b1)- else mkCross Stream.nil- Nothing -> return (Just a, Nothing)---- Put the b's that have been paired, in another hash or mutate the hash to set--- a flag. At the end go through @Stream m b@ and find those that are not in that--- hash to return (Nothing, b).---- | Like 'joinOuter' but uses a 'Map' for efficiency.------ Space: O(m + n)------ Time: O(m + n)------ /Pre-release/-{-# INLINE joinOuter #-}-joinOuter ::- (Ord k, MonadIO m) =>- Stream m (k, a) -> Stream m (k, b) -> Stream m (k, Maybe a, Maybe b)-joinOuter s1 s2 =- Stream.concatEffect $ do- km1 <- kvFold s1- km2 <- kvFold s2-- -- XXX Not sure if toList/fromList would fuse optimally. We may have to- -- create a fused Map.toStream function.- let res1 = fmap (joinAB km2)- $ Stream.fromList $ Map.toList km1- where- joinAB km (k, a) =- case k `Map.lookup` km of- Just b -> (k, Just a, Just b)- Nothing -> (k, Just a, Nothing)-- -- XXX We can take advantage of the lookups in the first pass above to- -- reduce the number of lookups in this pass. If we keep mutable cells- -- in the second Map, we can flag it in the first pass and not do any- -- lookup in the second pass if it is flagged.- let res2 = Stream.mapMaybe (joinAB km1)- $ Stream.fromList $ Map.toList km2- where- joinAB km (k, b) =- case k `Map.lookup` km of- Just _ -> Nothing- Nothing -> Just (k, Nothing, Just b)-- return $ Stream.append res1 res2-- where-- -- XXX Generate error if a duplicate insertion is attempted?- kvFold =- let f = Fold.foldl' (\kv (k, b) -> Map.insert k b kv) Map.empty- in Stream.fold f
− src/Streamly/Internal/Data/Stream/StreamD/Eliminate.hs
@@ -1,833 +0,0 @@-{-# LANGUAGE CPP #-}--- |--- Module : Streamly.Internal.Data.Stream.StreamD.Eliminate--- Copyright : (c) 2018 Composewell Technologies--- (c) Roman Leshchinskiy 2008-2010--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC---- A few functions in this module have been adapted from the vector package--- (c) Roman Leshchinskiy.----module Streamly.Internal.Data.Stream.StreamD.Eliminate- (- -- * Running a 'Fold'- fold-- -- -- * Running a 'Parser'- , parse- , parseD- , parseBreak- , parseBreakD-- -- * Stream Deconstruction- , uncons-- -- * Right Folds- , foldrM- , foldr- , foldrMx- , foldr1-- -- * Left Folds- , foldlM'- , foldl'- , foldlMx'- , foldlx'-- -- * Specific Fold Functions- , drain- , mapM_ -- Map and Fold- , null- , head- , headElse- , tail- , last- , elem- , notElem- , all- , any- , maximum- , maximumBy- , minimum- , minimumBy- , lookup- , findM- , find- , (!!)- , the-- -- * To containers- , toList- , toListRev-- -- * Multi-Stream Folds- -- ** Comparisons- -- | These should probably be expressed using zipping operations.- , eqBy- , cmpBy-- -- ** Substreams- -- | These should probably be expressed using parsers.- , isPrefixOf- , isInfixOf- , isSuffixOf- , isSuffixOfUnbox- , isSubsequenceOf- , stripPrefix- , stripSuffix- , stripSuffixUnbox- )-where--#include "inline.hs"--import Control.Exception (assert)-import Control.Monad.IO.Class (MonadIO(..))-import Foreign.Storable (Storable)-import GHC.Exts (SpecConstrAnnotation(..))-import GHC.Types (SPEC(..))-import Streamly.Internal.Data.Parser (ParseError(..))-import Streamly.Internal.Data.SVar.Type (defState)-import Streamly.Internal.Data.Unboxed (Unbox)--import Streamly.Internal.Data.Maybe.Strict (Maybe'(..))--import qualified Streamly.Internal.Data.Array.Type as Array-import qualified Streamly.Internal.Data.Fold as Fold-import qualified Streamly.Internal.Data.Parser as PR-import qualified Streamly.Internal.Data.Parser.ParserD as PRD-import qualified Streamly.Internal.Data.Stream.StreamD.Generate as StreamD-import qualified Streamly.Internal.Data.Stream.StreamD.Nesting as Nesting-import qualified Streamly.Internal.Data.Stream.StreamD.Transform as StreamD--import Prelude hiding- ( all, any, elem, foldr, foldr1, head, last, lookup, mapM, mapM_- , maximum, minimum, notElem, null, splitAt, tail, (!!))-import Streamly.Internal.Data.Stream.StreamD.Type--#include "DocTestDataStream.hs"----------------------------------------------------------------------------------- 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)----------------------------------------------------------------------------------- Parsers----------------------------------------------------------------------------------- Inlined definition. Without the inline "serially/parser/take" benchmark--- degrades and parseMany does not fuse. Even using "inline" at the callsite--- does not help.-{-# INLINE splitAt #-}-splitAt :: Int -> [a] -> ([a],[a])-splitAt n ls- | n <= 0 = ([], ls)- | otherwise = splitAt' n ls- where- splitAt' :: Int -> [a] -> ([a], [a])- splitAt' _ [] = ([], [])- splitAt' 1 (x:xs) = ([x], xs)- splitAt' m (x:xs) = (x:xs', xs'')- where- (xs', xs'') = splitAt' (m - 1) xs---- GHC parser does not accept {-# ANN type [] NoSpecConstr #-}, so we need--- to make a newtype.-{-# ANN type List NoSpecConstr #-}-newtype List a = List {getList :: [a]}---- | Run a 'Parse' over a stream.-{-# INLINE_NORMAL parseD #-}-parseD- :: Monad m- => PRD.Parser a m b- -> Stream m a- -> m (Either ParseError b)-parseD parser strm = do- (b, _) <- parseBreakD parser strm- return b---- | Parse a stream using the supplied 'Parser'.------ Parsers (See "Streamly.Internal.Data.Parser") are more powerful folds that--- add backtracking and error functionality to terminating folds. Unlike folds,--- parsers may not always result in a valid output, they may result in an--- error. For example:------ >>> Stream.parse (Parser.takeEQ 1 Fold.drain) Stream.nil--- Left (ParseError "takeEQ: Expecting exactly 1 elements, input terminated on 0")------ Note: @parse p@ is not the same as @head . parseMany p@ on an empty stream.----{-# INLINE [3] parse #-}-parse :: Monad m => PR.Parser a m b -> Stream m a -> m (Either ParseError b)-parse = parseD---- XXX It may be a good idea to use constant sized chunks for backtracking. We--- can take a byte stream but when we have to backtrack we create constant--- sized chunks. We maintain one forward list and one backward list of constant--- sized chunks, and a last backtracking offset. That way we just need lists of--- contents and no need to maintain start/end pointers for individual arrays,--- reducing bookkeeping work.---- | Run a 'Parse' over a stream and return rest of the Stream.-{-# INLINE_NORMAL parseBreakD #-}-parseBreakD- :: Monad m- => PRD.Parser a m b- -> Stream m a- -> m (Either ParseError b, Stream m a)-parseBreakD (PRD.Parser pstep initial extract) stream@(Stream step state) = do- res <- initial- case res of- PRD.IPartial s -> go SPEC state (List []) s- PRD.IDone b -> return (Right b, stream)- PRD.IError err -> return (Left (ParseError err), stream)-- where-- -- "buf" contains last few items in the stream that we may have to- -- backtrack to.- --- -- XXX currently we are using a dumb list based approach for backtracking- -- buffer. This can be replaced by a sliding/ring buffer using Data.Array.- -- That will allow us more efficient random back and forth movement.- go !_ st buf !pst = do- r <- step defState st- case r of- Yield x s -> do- pRes <- pstep pst x- case pRes of- PR.Partial 0 pst1 -> go SPEC s (List []) pst1- PR.Partial 1 pst1 -> go1 SPEC s x pst1- PR.Partial n pst1 -> do- assert (n <= length (x:getList buf)) (return ())- let src0 = Prelude.take n (x:getList buf)- src = Prelude.reverse src0- gobuf SPEC s (List []) (List src) pst1- PR.Continue 0 pst1 -> go SPEC s (List (x:getList buf)) pst1- PR.Continue 1 pst1 -> gobuf SPEC s buf (List [x]) pst1- PR.Continue n pst1 -> do- assert (n <= length (x:getList buf)) (return ())- let (src0, buf1) = splitAt n (x:getList buf)- src = Prelude.reverse src0- gobuf SPEC s (List buf1) (List src) pst1- PR.Done 0 b -> return (Right b, Stream step s)- PR.Done n b -> do- assert (n <= length (x:getList buf)) (return ())- let src0 = Prelude.take n (x:getList buf)- src = Prelude.reverse src0- -- XXX This would make it quadratic. We should probably- -- use StreamK if we have to append many times.- return- ( Right b,- Nesting.append (fromList src) (Stream step s))- PR.Error err ->- return (Left (ParseError err), Stream step s)- Skip s -> go SPEC s buf pst- Stop -> goStop SPEC buf pst-- go1 _ s x !pst = do- pRes <- pstep pst x- case pRes of- PR.Partial 0 pst1 ->- go SPEC s (List []) pst1- PR.Partial 1 pst1 -> do- go1 SPEC s x pst1- PR.Partial n _ ->- error $ "parseBreak: parser bug, go1: Partial n = " ++ show n- PR.Continue 0 pst1 ->- go SPEC s (List [x]) pst1- PR.Continue 1 pst1 ->- go1 SPEC s x pst1- PR.Continue n _ -> do- error $ "parseBreak: parser bug, go1: Continue n = " ++ show n- PR.Done 0 b -> do- return (Right b, Stream step s)- PR.Done 1 b -> do- return (Right b, StreamD.cons x (Stream step s))- PR.Done n _ -> do- error $ "parseBreak: parser bug, go1: Done n = " ++ show n- PR.Error err ->- return- ( Left (ParseError err)- , Nesting.append (fromPure x) (Stream step s)- )-- gobuf !_ s buf (List []) !pst = go SPEC s buf pst- gobuf !_ s buf (List (x:xs)) !pst = do- pRes <- pstep pst x- case pRes of- PR.Partial 0 pst1 ->- gobuf SPEC s (List []) (List xs) pst1- PR.Partial n pst1 -> do- assert (n <= length (x:getList buf)) (return ())- let src0 = Prelude.take n (x:getList buf)- src = Prelude.reverse src0 ++ xs- gobuf SPEC s (List []) (List src) pst1- PR.Continue 0 pst1 ->- gobuf SPEC s (List (x:getList buf)) (List xs) pst1- PR.Continue 1 pst1 ->- gobuf SPEC s buf (List (x:xs)) pst1- PR.Continue n pst1 -> do- assert (n <= length (x:getList buf)) (return ())- let (src0, buf1) = splitAt n (x:getList buf)- src = Prelude.reverse src0 ++ xs- gobuf SPEC s (List buf1) (List src) pst1- PR.Done n b -> do- assert (n <= length (x:getList buf)) (return ())- let src0 = Prelude.take n (x:getList buf)- src = Prelude.reverse src0- return (Right b, Nesting.append (fromList src) (Stream step s))- PR.Error err ->- return- ( Left (ParseError err)- , Nesting.append (fromList (x:xs)) (Stream step s)- )-- -- This is simplified gobuf- goExtract !_ buf (List []) !pst = goStop SPEC buf pst- goExtract !_ buf (List (x:xs)) !pst = do- pRes <- pstep pst x- case pRes of- PR.Partial 0 pst1 ->- goExtract SPEC (List []) (List xs) pst1- PR.Partial n pst1 -> do- assert (n <= length (x:getList buf)) (return ())- let src0 = Prelude.take n (x:getList buf)- src = Prelude.reverse src0 ++ xs- goExtract SPEC (List []) (List src) pst1- PR.Continue 0 pst1 ->- goExtract SPEC (List (x:getList buf)) (List xs) pst1- PR.Continue 1 pst1 ->- goExtract SPEC buf (List (x:xs)) pst1- PR.Continue n pst1 -> do- assert (n <= length (x:getList buf)) (return ())- let (src0, buf1) = splitAt n (x:getList buf)- src = Prelude.reverse src0 ++ xs- goExtract SPEC (List buf1) (List src) pst1- PR.Done n b -> do- assert (n <= length (x:getList buf)) (return ())- let src0 = Prelude.take n (x:getList buf)- src = Prelude.reverse src0- return (Right b, fromList src)- PR.Error err -> return (Left (ParseError err), fromList (x:xs))-- -- This is simplified goExtract- -- XXX Use SPEC?- {-# INLINE goStop #-}- goStop _ buf pst = do- pRes <- extract pst- case pRes of- PR.Partial _ _ -> error "Bug: parseBreak: Partial in extract"- PR.Continue 0 pst1 -> goStop SPEC buf pst1- PR.Continue n pst1 -> do- assert (n <= length (getList buf)) (return ())- let (src0, buf1) = splitAt n (getList buf)- src = Prelude.reverse src0- goExtract SPEC (List buf1) (List src) pst1- PR.Done 0 b -> return (Right b, StreamD.nil)- PR.Done n b -> do- assert (n <= length (getList buf)) (return ())- let src0 = Prelude.take n (getList buf)- src = Prelude.reverse src0- return (Right b, fromList src)- PR.Error err ->- return (Left (ParseError err), StreamD.nil)---- | Parse a stream using the supplied 'Parser'.----{-# INLINE parseBreak #-}-parseBreak :: Monad m => PR.Parser a m b -> Stream m a -> m (Either ParseError b, Stream m a)-parseBreak = parseBreakD----------------------------------------------------------------------------------- Specialized Folds----------------------------------------------------------------------------------- benchmark after dropping 1 item from stream or using unfolds-{-# INLINE_NORMAL null #-}-null :: Monad m => Stream m a -> m Bool-#ifdef USE_FOLDS_EVERYWHERE-null = fold Fold.null-#else-null = foldrM (\_ _ -> return False) (return True)-#endif--{-# INLINE_NORMAL head #-}-head :: Monad m => Stream m a -> m (Maybe a)-#ifdef USE_FOLDS_EVERYWHERE-head = fold Fold.one-#else-head = foldrM (\x _ -> return (Just x)) (return Nothing)-#endif--{-# INLINE_NORMAL headElse #-}-headElse :: Monad m => a -> Stream m a -> m a-headElse a = foldrM (\x _ -> return x) (return a)---- 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 SPEC state- where- go !_ st = do- r <- step defState st- case r of- Yield _ s -> return (Just $ Stream step s)- Skip s -> go SPEC s- Stop -> return Nothing---- XXX will it fuse? need custom impl?-{-# INLINE_NORMAL last #-}-last :: Monad m => Stream m a -> m (Maybe a)-#ifdef USE_FOLDS_EVERYWHERE-last = fold Fold.last-#else-last = foldl' (\_ y -> Just y) Nothing-#endif---- XXX Use the foldrM based impl instead-{-# INLINE_NORMAL elem #-}-elem :: (Monad m, Eq a) => a -> Stream m a -> m Bool-#ifdef USE_FOLDS_EVERYWHERE-elem e = fold (Fold.elem e)-#else--- elem e m = foldrM (\x xs -> if x == e then return True else xs) (return False) m-elem e (Stream step state) = go SPEC state- where- go !_ st = do- r <- step defState st- case r of- Yield x s- | x == e -> return True- | otherwise -> go SPEC s- Skip s -> go SPEC s- Stop -> return False-#endif--{-# 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-#ifdef USE_FOLDS_EVERYWHERE-all p = fold (Fold.all p)-#else--- all p m = foldrM (\x xs -> if p x then xs else return False) (return True) m-all p (Stream step state) = go SPEC state- where- go !_ st = do- r <- step defState st- case r of- Yield x s- | p x -> go SPEC s- | otherwise -> return False- Skip s -> go SPEC s- Stop -> return True-#endif--{-# INLINE_NORMAL any #-}-any :: Monad m => (a -> Bool) -> Stream m a -> m Bool-#ifdef USE_FOLDS_EVERYWHERE-any p = fold (Fold.any p)-#else--- any p m = foldrM (\x xs -> if p x then return True else xs) (return False) m-any p (Stream step state) = go SPEC state- where- go !_ st = do- r <- step defState st- case r of- Yield x s- | p x -> return True- | otherwise -> go SPEC s- Skip s -> go SPEC s- Stop -> return False-#endif--{-# INLINE_NORMAL maximum #-}-maximum :: (Monad m, Ord a) => Stream m a -> m (Maybe a)-#ifdef USE_FOLDS_EVERYWHERE-maximum = fold Fold.maximum-#else-maximum (Stream step state) = go SPEC Nothing' state- where- go !_ Nothing' st = do- r <- step defState st- case r of- Yield x s -> go SPEC (Just' x) s- Skip s -> go SPEC Nothing' s- Stop -> return Nothing- go !_ (Just' acc) st = do- r <- step defState st- case r of- Yield x s- | acc <= x -> go SPEC (Just' x) s- | otherwise -> go SPEC (Just' acc) s- Skip s -> go SPEC (Just' acc) s- Stop -> return (Just acc)-#endif--{-# INLINE_NORMAL maximumBy #-}-maximumBy :: Monad m => (a -> a -> Ordering) -> Stream m a -> m (Maybe a)-#ifdef USE_FOLDS_EVERYWHERE-maximumBy cmp = fold (Fold.maximumBy cmp)-#else-maximumBy cmp (Stream step state) = go SPEC Nothing' state- where- go !_ Nothing' st = do- r <- step defState st- case r of- Yield x s -> go SPEC (Just' x) s- Skip s -> go SPEC 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 SPEC (Just' acc) s- _ -> go SPEC (Just' x) s- Skip s -> go SPEC (Just' acc) s- Stop -> return (Just acc)-#endif--{-# INLINE_NORMAL minimum #-}-minimum :: (Monad m, Ord a) => Stream m a -> m (Maybe a)-#ifdef USE_FOLDS_EVERYWHERE-minimum = fold Fold.minimum-#else-minimum (Stream step state) = go SPEC Nothing' state-- where-- go !_ Nothing' st = do- r <- step defState st- case r of- Yield x s -> go SPEC (Just' x) s- Skip s -> go SPEC Nothing' s- Stop -> return Nothing- go !_ (Just' acc) st = do- r <- step defState st- case r of- Yield x s- | acc <= x -> go SPEC (Just' acc) s- | otherwise -> go SPEC (Just' x) s- Skip s -> go SPEC (Just' acc) s- Stop -> return (Just acc)-#endif--{-# INLINE_NORMAL minimumBy #-}-minimumBy :: Monad m => (a -> a -> Ordering) -> Stream m a -> m (Maybe a)-#ifdef USE_FOLDS_EVERYWHERE-minimumBy cmp = fold (Fold.minimumBy cmp)-#else-minimumBy cmp (Stream step state) = go SPEC Nothing' state-- where-- go !_ Nothing' st = do- r <- step defState st- case r of- Yield x s -> go SPEC (Just' x) s- Skip s -> go SPEC 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 SPEC (Just' x) s- _ -> go SPEC (Just' acc) s- Skip s -> go SPEC (Just' acc) s- Stop -> return (Just acc)-#endif--{-# INLINE_NORMAL (!!) #-}-(!!) :: (Monad m) => Stream m a -> Int -> m (Maybe a)-#ifdef USE_FOLDS_EVERYWHERE-stream !! i = fold (Fold.index i) stream-#else-(Stream step state) !! i = go SPEC 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 SPEC (n - 1) s- Skip s -> go SPEC n s- Stop -> return Nothing-#endif--{-# INLINE_NORMAL lookup #-}-lookup :: (Monad m, Eq a) => a -> Stream m (a, b) -> m (Maybe b)-#ifdef USE_FOLDS_EVERYWHERE-lookup e = fold (Fold.lookup e)-#else-lookup e = foldrM (\(a, b) xs -> if e == a then return (Just b) else xs)- (return Nothing)-#endif--{-# INLINE_NORMAL findM #-}-findM :: Monad m => (a -> m Bool) -> Stream m a -> m (Maybe a)-#ifdef USE_FOLDS_EVERYWHERE-findM p = fold (Fold.findM p)-#else-findM p = foldrM (\x xs -> p x >>= \r -> if r then return (Just x) else xs)- (return Nothing)-#endif--{-# INLINE find #-}-find :: Monad m => (a -> Bool) -> Stream m a -> m (Maybe a)-find p = findM (return . p)--{-# INLINE toListRev #-}-toListRev :: Monad m => Stream m a -> m [a]-#ifdef USE_FOLDS_EVERYWHERE-toListRev = fold Fold.toListRev-#else-toListRev = foldl' (flip (:)) []-#endif----------------------------------------------------------------------------------- Transformation comprehensions---------------------------------------------------------------------------------{-# INLINE_NORMAL the #-}-the :: (Eq a, Monad m) => Stream m a -> m (Maybe a)-#ifdef USE_FOLDS_EVERYWHERE-the = fold Fold.the-#else-the (Stream step state) = go SPEC state- where- go !_ st = do- r <- step defState st- case r of- Yield x s -> go' SPEC x s- Skip s -> go SPEC s- Stop -> return Nothing- go' !_ n st = do- r <- step defState st- case r of- Yield x s | x == n -> go' SPEC n s- | otherwise -> return Nothing- Skip s -> go' SPEC n s- Stop -> return (Just n)-#endif----------------------------------------------------------------------------------- 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 ()-#ifdef USE_FOLDS_EVERYWHERE-mapM_ f = fold (Fold.drainBy f)-#else-mapM_ m = drain . mapM m-#endif----------------------------------------------------------------------------------- Multi-stream folds----------------------------------------------------------------------------------- | Returns 'True' if the first stream is the same as or a prefix of the--- second. A stream is a prefix of itself.------ >>> Stream.isPrefixOf (Stream.fromList "hello") (Stream.fromList "hello" :: Stream IO Char)--- True----{-# INLINE_NORMAL isPrefixOf #-}-isPrefixOf :: (Monad m, Eq a) => Stream m a -> Stream m a -> m Bool-isPrefixOf (Stream stepa ta) (Stream stepb tb) = go SPEC Nothing' ta tb-- where-- go !_ Nothing' sa sb = do- r <- stepa defState sa- case r of- Yield x sa' -> go SPEC (Just' x) sa' sb- Skip sa' -> go SPEC Nothing' sa' sb- Stop -> return True-- go !_ (Just' x) sa sb = do- r <- stepb defState sb- case r of- Yield y sb' ->- if x == y- then go SPEC Nothing' sa sb'- else return False- Skip sb' -> go SPEC (Just' x) sa sb'- Stop -> return False---- | 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.------ >>> Stream.isSubsequenceOf (Stream.fromList "hlo") (Stream.fromList "hello" :: Stream IO Char)--- True----{-# INLINE_NORMAL isSubsequenceOf #-}-isSubsequenceOf :: (Monad m, Eq a) => Stream m a -> Stream m a -> m Bool-isSubsequenceOf (Stream stepa ta) (Stream stepb tb) = go SPEC Nothing' ta tb-- where-- go !_ Nothing' sa sb = do- r <- stepa defState sa- case r of- Yield x sa' -> go SPEC (Just' x) sa' sb- Skip sa' -> go SPEC Nothing' sa' sb- Stop -> return True-- go !_ (Just' x) sa sb = do- r <- stepb defState sb- case r of- Yield y sb' ->- if x == y- then go SPEC Nothing' sa sb'- else go SPEC (Just' x) sa sb'- Skip sb' -> go SPEC (Just' x) sa sb'- Stop -> return False---- | @stripPrefix prefix input@ strips the @prefix@ stream from the @input@--- stream if it is a prefix of input. Returns 'Nothing' if the input does not--- start with the given prefix, stripped input otherwise. Returns @Just nil@--- when the prefix is the same as the input stream.------ Space: @O(1)@----{-# INLINE_NORMAL stripPrefix #-}-stripPrefix- :: (Monad m, Eq a)- => Stream m a -> Stream m a -> m (Maybe (Stream m a))-stripPrefix (Stream stepa ta) (Stream stepb tb) = go SPEC Nothing' ta tb-- where-- go !_ Nothing' sa sb = do- r <- stepa defState sa- case r of- Yield x sa' -> go SPEC (Just' x) sa' sb- Skip sa' -> go SPEC Nothing' sa' sb- Stop -> return $ Just (Stream stepb sb)-- go !_ (Just' x) sa sb = do- r <- stepb defState sb- case r of- Yield y sb' ->- if x == y- then go SPEC Nothing' sa sb'- else return Nothing- Skip sb' -> go SPEC (Just' x) sa sb'- Stop -> return Nothing---- | Returns 'True' if the first stream is an infix of the second. A stream is--- considered an infix of itself.------ >>> s = Stream.fromList "hello" :: Stream IO Char--- >>> Stream.isInfixOf s s--- True------ Space: @O(n)@ worst case where @n@ is the length of the infix.------ /Pre-release/------ /Requires 'Storable' constraint/----{-# INLINE isInfixOf #-}-isInfixOf :: (MonadIO m, Eq a, Enum a, Storable a, Unbox a)- => Stream m a -> Stream m a -> m Bool-isInfixOf infx stream = do- arr <- fold Array.write infx- -- XXX can use breakOnSeq instead (when available)- r <- null $ StreamD.drop 1 $ Nesting.splitOnSeq arr Fold.drain stream- return (not r)---- Note: isPrefixOf uses the prefix stream only once. In contrast, isSuffixOf--- may use the suffix stream many times. To run in optimal memory we do not--- want to buffer the suffix stream in memory therefore we need an ability to--- clone (or consume it multiple times) the suffix stream without any side--- effects so that multiple potential suffix matches can proceed in parallel--- without buffering the suffix stream. For example, we may create the suffix--- stream from a file handle, however, if we evaluate the stream multiple--- times, once for each match, we will need a different file handle each time--- which may exhaust the file descriptors. Instead, we want to share the same--- underlying file descriptor, use pread on it to generate the stream and clone--- the stream for each match. Therefore the suffix stream should be built in--- such a way that it can be consumed multiple times without any problems.---- XXX Can be implemented with better space/time complexity.--- Space: @O(n)@ worst case where @n@ is the length of the suffix.---- | Returns 'True' if the first stream is a suffix of the second. A stream is--- considered a suffix of itself.------ >>> Stream.isSuffixOf (Stream.fromList "hello") (Stream.fromList "hello" :: Stream IO Char)--- True------ Space: @O(n)@, buffers entire input stream and the suffix.------ /Pre-release/------ /Suboptimal/ - Help wanted.----{-# INLINE isSuffixOf #-}-isSuffixOf :: (Monad m, Eq a) => Stream m a -> Stream m a -> m Bool-isSuffixOf suffix stream =- StreamD.reverse suffix `isPrefixOf` StreamD.reverse stream---- | Much faster than 'isSuffixOf'.-{-# INLINE isSuffixOfUnbox #-}-isSuffixOfUnbox :: (MonadIO m, Eq a, Unbox a) =>- Stream m a -> Stream m a -> m Bool-isSuffixOfUnbox suffix stream =- StreamD.reverseUnbox suffix `isPrefixOf` StreamD.reverseUnbox stream---- | Drops the given suffix from a stream. Returns 'Nothing' if the stream does--- not end with the given suffix. Returns @Just nil@ when the suffix is the--- same as the stream.------ It may be more efficient to convert the stream to an Array and use--- stripSuffix on that especially if the elements have a Storable or Prim--- instance.------ See also "Streamly.Internal.Data.Stream.Reduce.dropSuffix".------ Space: @O(n)@, buffers the entire input stream as well as the suffix------ /Pre-release/-{-# INLINE stripSuffix #-}-stripSuffix- :: (Monad m, Eq a)- => Stream m a -> Stream m a -> m (Maybe (Stream m a))-stripSuffix m1 m2 =- fmap StreamD.reverse- <$> stripPrefix (StreamD.reverse m1) (StreamD.reverse m2)---- | Much faster than 'stripSuffix'.-{-# INLINE stripSuffixUnbox #-}-stripSuffixUnbox- :: (MonadIO m, Eq a, Unbox a)- => Stream m a -> Stream m a -> m (Maybe (Stream m a))-stripSuffixUnbox m1 m2 =- fmap StreamD.reverseUnbox- <$> stripPrefix (StreamD.reverseUnbox m1) (StreamD.reverseUnbox m2)
− src/Streamly/Internal/Data/Stream/StreamD/Exception.hs
@@ -1,479 +0,0 @@-{-# LANGUAGE CPP #-}--- |--- Module : Streamly.Internal.Data.Stream.StreamD.Exception--- Copyright : (c) 2020 Composewell Technologies and Contributors--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC--module Streamly.Internal.Data.Stream.StreamD.Exception- (- gbracket_- , gbracket- , before- , afterUnsafe- , afterIO- , bracketUnsafe- , bracketIO3- , bracketIO- , onException- , finallyUnsafe- , finallyIO- , ghandle- , handle- )-where--#include "inline.hs"--import Control.Monad.IO.Class (MonadIO(..))-import Control.Exception (Exception, SomeException, mask_)-import Control.Monad.Catch (MonadCatch)-import GHC.Exts (inline)-import Streamly.Internal.Data.IOFinalizer- (newIOFinalizer, runIOFinalizer, clearingIOFinalizer)--import qualified Control.Monad.Catch as MC--import Streamly.Internal.Data.Stream.StreamD.Type--#include "DocTestDataStream.hs"--data GbracketState s1 s2 v- = GBracketInit- | GBracketNormal s1 v- | GBracketException s2---- | Like 'gbracket' but with following differences:------ * alloc action @m c@ runs with async exceptions enabled--- * cleanup action @c -> m d@ won't run if the stream is garbage collected--- after partial evaluation.------ /Inhibits stream fusion/------ /Pre-release/----{-# INLINE_NORMAL gbracket_ #-}-gbracket_- :: Monad m- => m c -- ^ before- -> (c -> m d) -- ^ after, on normal stop- -> (c -> e -> Stream m b -> Stream m b) -- ^ on exception- -> (forall s. m s -> m (Either e s)) -- ^ try (exception handling)- -> (c -> Stream m b) -- ^ stream generator- -> Stream m b-gbracket_ bef aft onExc ftry action =- Stream step GBracketInit-- where-- {-# INLINE_LATE step #-}- step _ GBracketInit = do- r <- bef- return $ Skip $ GBracketNormal (action r) r-- step gst (GBracketNormal (UnStream step1 st) v) = do- res <- ftry $ 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- -- XXX Do not handle async exceptions, just rethrow them.- Left e ->- return- $ Skip (GBracketException (onExc v e (UnStream step1 st)))- 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--data GbracketIOState s1 s2 v wref- = GBracketIOInit- | GBracketIONormal s1 v wref- | GBracketIOException s2---- | Run the alloc action @m c@ with async exceptions disabled but keeping--- blocking operations interruptible (see 'Control.Exception.mask'). Use the--- output @c@ as input to @c -> Stream m b@ to generate an output stream. When--- generating the stream use the supplied @try@ operation @forall s. m s -> m--- (Either e s)@ to catch synchronous exceptions. If an exception occurs run--- the exception handler @c -> e -> Stream m b -> m (Stream m b)@. Note that--- 'gbracket' does not rethrow the exception, it has to be done by the--- exception handler if desired.------ The cleanup action @c -> m d@, runs whenever the stream ends normally, due--- to a sync or async exception or if it gets garbage collected after a partial--- lazy evaluation. See 'bracket' for the semantics of the cleanup action.------ 'gbracket' can express all other exception handling combinators.------ /Inhibits stream fusion/------ /Pre-release/-{-# INLINE_NORMAL gbracket #-}-gbracket- :: MonadIO m- => IO c -- ^ before- -> (c -> IO d1) -- ^ on normal stop- -> (c -> e -> Stream m b -> IO (Stream m b)) -- ^ on exception- -> (c -> IO d2) -- ^ on GC without normal stop or exception- -> (forall s. m s -> m (Either e s)) -- ^ try (exception handling)- -> (c -> Stream m b) -- ^ stream generator- -> Stream m b-gbracket bef aft onExc onGC ftry action =- 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- -- We mask asynchronous exceptions to make the execution- -- of 'bef' and the registration of 'aft' atomic.- -- A similar thing is done in the resourcet package: https://git.io/JvKV3- -- Tutorial: https://markkarpov.com/tutorial/exceptions.html- (r, ref) <- liftIO $ mask_ $ do- r <- bef- ref <- newIOFinalizer (onGC r)- return (r, ref)- return $ Skip $ GBracketIONormal (action r) r ref-- step gst (GBracketIONormal (UnStream step1 st) v ref) = do- res <- ftry $ 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 ->- liftIO (clearingIOFinalizer ref (aft v)) >> return Stop- -- XXX Do not handle async exceptions, just rethrow them.- Left e -> do- -- Clearing of finalizer and running of exception handler must- -- be atomic wrt async exceptions. Otherwise if we have cleared- -- the finalizer and have not run the exception handler then we- -- may leak the resource.- stream <-- liftIO (clearingIOFinalizer ref (onExc v e (UnStream step1 st)))- return $ Skip (GBracketIOException stream)- 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 the action @m b@ before the stream yields its first element.------ Same as the following but more efficient due to fusion:------ >>> before action xs = Stream.nilM action <> xs--- >>> before action xs = Stream.concatMap (const xs) (Stream.fromEffect action)----{-# 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---- | Like 'after', with following differences:------ * action @m b@ won't run if the stream is garbage collected--- after partial evaluation.--- * Monad @m@ does not require any other constraints.--- * has slightly better performance than 'after'.------ Same as the following, but with stream fusion:------ >>> afterUnsafe action xs = xs <> Stream.nilM action------ /Pre-release/----{-# INLINE_NORMAL afterUnsafe #-}-afterUnsafe :: Monad m => m b -> Stream m a -> Stream m a-afterUnsafe 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---- | Run the action @IO b@ whenever the stream is evaluated to completion, or--- if it is garbage collected after a partial lazy evaluation.------ The semantics of the action @IO b@ are similar to the semantics of cleanup--- action in 'bracketIO'.------ /See also 'afterUnsafe'/----{-# INLINE_NORMAL afterIO #-}-afterIO :: MonadIO m- => IO b -> Stream m a -> Stream m a-afterIO action (Stream step state) = Stream step' Nothing-- where-- {-# INLINE_LATE step' #-}- step' _ Nothing = do- ref <- liftIO $ newIOFinalizer 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- runIOFinalizer ref- return Stop---- XXX For high performance error checks in busy streams we may need another--- Error constructor in step.---- | Run the action @m b@ if the stream evaluation is aborted due to an--- exception. The exception is not caught, simply rethrown.------ /Inhibits stream fusion/----{-# INLINE_NORMAL onException #-}-onException :: MonadCatch m => m b -> Stream m a -> Stream m a-onException action stream =- gbracket_- (return ()) -- before- return -- after- (\_ (e :: MC.SomeException) _ -> nilM (action >> MC.throwM e))- (inline MC.try)- (const stream)--{-# 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---- | Like 'bracket' but with following differences:------ * alloc action @m b@ runs with async exceptions enabled--- * cleanup action @b -> m c@ won't run if the stream is garbage collected--- after partial evaluation.--- * has slightly better performance than 'bracketIO'.------ /Inhibits stream fusion/------ /Pre-release/----{-# INLINE_NORMAL bracketUnsafe #-}-bracketUnsafe :: MonadCatch m- => m b -> (b -> m c) -> (b -> Stream m a) -> Stream m a-bracketUnsafe bef aft =- gbracket_- bef- aft- (\a (e :: SomeException) _ -> nilM (aft a >> MC.throwM e))- (inline MC.try)---- For a use case of this see the "streamly-process" package. It needs to kill--- the process in case of exception or garbage collection, but waits for the--- process to terminate in normal cases.---- | Like 'bracketIO' but can use 3 separate cleanup actions depending on the--- mode of termination:------ 1. When the stream stops normally--- 2. When the stream is garbage collected--- 3. When the stream encounters an exception------ @bracketIO3 before onStop onGC onException action@ runs @action@ using the--- result of @before@. If the stream stops, @onStop@ action is executed, if the--- stream is abandoned @onGC@ is executed, if the stream encounters an--- exception @onException@ is executed.------ /Inhibits stream fusion/------ /Pre-release/-{-# INLINE_NORMAL bracketIO3 #-}-bracketIO3 :: (MonadIO m, MonadCatch m) =>- IO b- -> (b -> IO c)- -> (b -> IO d)- -> (b -> IO e)- -> (b -> Stream m a)- -> Stream m a-bracketIO3 bef aft onExc onGC =- gbracket- bef- aft- (\a (e :: SomeException) _ -> onExc a >> return (nilM (MC.throwM e)))- onGC- (inline MC.try)---- | Run the alloc action @IO b@ with async exceptions disabled but keeping--- blocking operations interruptible (see 'Control.Exception.mask'). Use the--- output @b@ as input to @b -> Stream m a@ to generate an output stream.------ @b@ is usually a resource under the IO monad, e.g. a file handle, that--- requires a cleanup after use. The cleanup action @b -> IO c@, runs whenever--- the stream ends normally, due to a sync or async exception or if it gets--- garbage collected after a partial lazy evaluation.------ 'bracketIO' only guarantees that the cleanup action runs, and it runs with--- async exceptions enabled. The action must ensure that it can successfully--- cleanup the resource in the face of sync or async exceptions.------ When the stream ends normally or on a sync exception, cleanup action runs--- immediately in the current thread context, whereas in other cases it runs in--- the GC context, therefore, cleanup may be delayed until the GC gets to run.------ /See also: 'bracketUnsafe'/------ /Inhibits stream fusion/----{-# INLINE bracketIO #-}-bracketIO :: (MonadIO m, MonadCatch m)- => IO b -> (b -> IO c) -> (b -> Stream m a) -> Stream m a-bracketIO bef aft = bracketIO3 bef aft aft aft--data BracketState s v = BracketInit | BracketRun s v---- | Alternate (custom) implementation of 'bracket'.----{-# 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 <- inline 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---- | Like 'finally' with following differences:------ * action @m b@ won't run if the stream is garbage collected--- after partial evaluation.--- * has slightly better performance than 'finallyIO'.------ /Inhibits stream fusion/------ /Pre-release/----{-# INLINE finallyUnsafe #-}-finallyUnsafe :: MonadCatch m => m b -> Stream m a -> Stream m a-finallyUnsafe action xs = bracketUnsafe (return ()) (const action) (const xs)---- | Run the action @IO b@ whenever the stream stream stops normally, aborts--- due to an exception or if it is garbage collected after a partial lazy--- evaluation.------ The semantics of running the action @IO b@ are similar to the cleanup action--- semantics described in 'bracketIO'.------ >>> finallyIO release = Stream.bracketIO (return ()) (const release)------ /See also 'finallyUnsafe'/------ /Inhibits stream fusion/----{-# INLINE finallyIO #-}-finallyIO :: (MonadIO m, MonadCatch m) => IO b -> Stream m a -> Stream m a-finallyIO action xs = bracketIO3 (return ()) act act act (const xs)- where act _ = action---- | Like 'handle' but the exception handler is also provided with the stream--- that generated the exception as input. The exception handler can thus--- re-evaluate the stream to retry the action that failed. The exception--- handler can again call 'ghandle' on it to retry the action multiple times.------ This is highly experimental. In a stream of actions we can map the stream--- with a retry combinator to retry each action on failure.------ /Inhibits stream fusion/------ /Pre-release/----{-# INLINE_NORMAL ghandle #-}-ghandle :: (MonadCatch m, Exception e)- => (e -> Stream m a -> Stream m a) -> Stream m a -> Stream m a-ghandle f stream =- gbracket_ (return ()) return (const f) (inline MC.try) (const stream)---- | When evaluating a stream if an exception occurs, stream evaluation aborts--- and the specified exception handler is run with the exception as argument.------ /Inhibits stream fusion/----{-# INLINE_NORMAL handle #-}-handle :: (MonadCatch m, Exception e)- => (e -> Stream m a) -> Stream m a -> Stream m a-handle f stream =- gbracket_ (return ()) return (\_ e _ -> f e) (inline MC.try) (const stream)---- | Alternate (custom) implementation of 'handle'.----{-# 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 <- inline 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
− src/Streamly/Internal/Data/Stream/StreamD/Generate.hs
@@ -1,1205 +0,0 @@-{-# LANGUAGE CPP #-}--- |--- Module : Streamly.Internal.Data.Stream.StreamD.Generate--- Copyright : (c) 2020 Composewell Technologies and Contributors--- (c) Roman Leshchinskiy 2008-2010--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------- A few combinators in this module have been adapted from the vector package--- (c) Roman Leshchinskiy. See the notes in specific combinators.----module Streamly.Internal.Data.Stream.StreamD.Generate- (- -- * Primitives- nil- , nilM- , cons- , consM-- -- * From 'Unfold'- , unfold-- -- * Unfolding- , unfoldr- , unfoldrM-- -- * From Values- , fromPure- , fromEffect- , repeat- , repeatM- , replicate- , replicateM-- -- * Enumeration- -- ** Enumerating 'Num' Types- , enumerateFromStepNum- , enumerateFromNum- , enumerateFromThenNum-- -- ** 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-- -- ** Enumerable Type Class- , Enumerable(..)-- -- * Time Enumeration- , times- , timesWith- , absTimes- , absTimesWith- , relTimes- , relTimesWith- , durations- , timeout-- -- * From Generators- -- | Generate a monadic stream from a seed.- , fromIndices- , fromIndicesM- , generate- , generateM-- -- * Iteration- , iterate- , iterateM-- -- * From Containers- -- | Transform an input structure into a stream.-- , fromList- , fromListM- , fromFoldable- , fromFoldableM-- -- * From Pointers- , fromPtr- , fromPtrN- , fromByteStr#-- -- * Conversions- , fromStreamK- , toStreamK- )-where--#include "inline.hs"-#include "ArrayMacros.h"--import Control.Monad.IO.Class (MonadIO(..))-import Data.Functor.Identity (Identity(..))-import Foreign.Ptr (Ptr, plusPtr)-import Foreign.Storable (Storable (peek), sizeOf)-import GHC.Exts (Addr#, Ptr (Ptr))-import Streamly.Internal.Data.Time.Clock- (Clock(Monotonic), asyncClock, readClock)-import Streamly.Internal.Data.Time.Units- (toAbsTime, AbsTime, toRelTime64, RelTime64, addToAbsTime64)--#ifdef USE_UNFOLDS_EVERYWHERE-import qualified Streamly.Internal.Data.Unfold as Unfold-import qualified Streamly.Internal.Data.Unfold.Enumeration as Unfold-#endif--import Data.Fixed-import Data.Int-import Data.Ratio-import Data.Word-import Numeric.Natural-import Prelude hiding (iterate, repeat, replicate, take, takeWhile)-import Streamly.Internal.Data.Stream.StreamD.Type--#include "DocTestDataStream.hs"----------------------------------------------------------------------------------- Primitives----------------------------------------------------------------------------------- XXX implement in terms of nilM?---- | A stream that terminates without producing any output or side effect.------ >>> Stream.fold Fold.toList Stream.nil--- []----{-# INLINE_NORMAL nil #-}-nil :: Applicative m => Stream m a-nil = Stream (\_ _ -> pure Stop) ()---- XXX implement in terms of consM?--- cons x = consM (return x)---- | Fuse a pure value at the head of an existing stream::------ >>> s = 1 `Stream.cons` Stream.fromList [2,3]--- >>> Stream.fold Fold.toList s--- [1,2,3]------ This function should not be used to dynamically construct a stream. If a--- stream is constructed by successive use of this function it would take--- O(n^2) time to consume the stream.------ This function should only be used to statically fuse an element with a--- stream. Do not use this recursively or where it cannot be inlined.------ See "Streamly.Data.StreamK" for a 'cons' that can be used to--- construct a stream recursively.------ Definition:------ >>> cons x xs = return x `Stream.consM` xs----{-# INLINE_NORMAL cons #-}-cons :: Applicative m => a -> Stream m a -> Stream m a-cons x (Stream step state) = Stream step1 Nothing- where- {-# INLINE_LATE step1 #-}- step1 _ Nothing = pure $ Yield x (Just state)- step1 gst (Just st) = do- (\case- Yield a s -> Yield a (Just s)- Skip s -> Skip (Just s)- Stop -> Stop) <$> step gst st----------------------------------------------------------------------------------- Unfolding----------------------------------------------------------------------------------- Adapted from vector package---- | 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 > 2--- then return Nothing--- else return (Just (b, b + 1))--- in Stream.fold Fold.toList $ Stream.unfoldrM f 0--- :}--- [0,1,2]----{-# INLINE_NORMAL unfoldrM #-}-unfoldrM :: Monad m => (s -> m (Maybe (a, s))) -> s -> Stream m a-#ifdef USE_UNFOLDS_EVERYWHERE-unfoldrM next = unfold (Unfold.unfoldrM next)-#else-unfoldrM next = Stream step- where- {-# INLINE_LATE step #-}- step _ st = do- r <- next st- return $ case r of- Just (x, s) -> Yield x s- Nothing -> Stop-#endif---- |--- >>> :{--- unfoldr step s =--- case step s of--- Nothing -> Stream.nil--- Just (a, b) -> a `Stream.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 > 2--- then Nothing--- else Just (b, b + 1)--- in Stream.fold Fold.toList $ Stream.unfoldr f 0--- :}--- [0,1,2]----{-# INLINE_LATE unfoldr #-}-unfoldr :: Monad m => (s -> Maybe (a, s)) -> s -> Stream m a-unfoldr f = unfoldrM (return . f)----------------------------------------------------------------------------------- From values----------------------------------------------------------------------------------- |--- >>> repeatM = Stream.sequence . Stream.repeat--- >>> repeatM = fix . Stream.consM--- >>> repeatM = cycle1 . Stream.fromEffect------ Generate a stream by repeatedly executing a monadic action forever.------ >>> :{--- repeatAction =--- Stream.repeatM (threadDelay 1000000 >> print 1)--- & Stream.take 10--- & Stream.fold Fold.drain--- :}----{-# INLINE_NORMAL repeatM #-}-repeatM :: Monad m => m a -> Stream m a-#ifdef USE_UNFOLDS_EVERYWHERE-repeatM = unfold Unfold.repeatM-#else-repeatM x = Stream (\_ _ -> x >>= \r -> return $ Yield r ()) ()-#endif---- |--- Generate an infinite stream by repeating a pure value.------ >>> repeat x = Stream.repeatM (pure x)----{-# INLINE_NORMAL repeat #-}-repeat :: Monad m => a -> Stream m a-#ifdef USE_UNFOLDS_EVERYWHERE-repeat x = repeatM (pure x)-#else-repeat x = Stream (\_ _ -> return $ Yield x ()) ()-#endif---- Adapted from the vector package---- |--- >>> replicateM n = Stream.sequence . Stream.replicate n------ Generate a stream by performing a monadic action @n@ times.-{-# INLINE_NORMAL replicateM #-}-replicateM :: Monad m => Int -> m a -> Stream m a-#ifdef USE_UNFOLDS_EVERYWHERE-replicateM n p = unfold Unfold.replicateM (n, p)-#else-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)-#endif---- |--- >>> replicate n = Stream.take n . Stream.repeat--- >>> replicate n x = Stream.replicateM n (pure x)------ Generate a stream of length @n@ by repeating a value @n@ times.----{-# INLINE_NORMAL replicate #-}-replicate :: Monad m => Int -> a -> Stream m a-replicate n x = replicateM n (return x)----------------------------------------------------------------------------------- Enumeration of Num----------------------------------------------------------------------------------- | 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-#ifdef USE_UNFOLDS_EVERYWHERE-enumerateFromStepNum from stride =- unfold Unfold.enumerateFromStepNum (from, stride)-#else-enumerateFromStepNum from stride = Stream step 0- where- {-# INLINE_LATE step #-}- step _ !i = return $ (Yield $! (from + i * stride)) $! (i + 1)-#endif--{-# INLINE_NORMAL enumerateFromNum #-}-enumerateFromNum :: (Monad m, Num a) => a -> Stream m a-enumerateFromNum from = enumerateFromStepNum from 1--{-# INLINE_NORMAL enumerateFromThenNum #-}-enumerateFromThenNum :: (Monad m, Num a) => a -> a -> Stream m a-enumerateFromThenNum from next = enumerateFromStepNum from (next - from)----------------------------------------------------------------------------------- Enumeration of Integrals---------------------------------------------------------------------------------#ifndef USE_UNFOLDS_EVERYWHERE-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-#endif---- XXX This can perhaps be simplified and written in terms of--- enumeratFromStepIntegral as we have done in unfolds.---- | 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@.------ >>> Stream.fold Fold.toList $ Stream.enumerateFromThenToIntegral 0 2 6--- [0,2,4,6]------ >>> Stream.fold Fold.toList $ Stream.enumerateFromThenToIntegral 0 (-2) (-6)--- [0,-2,-4,-6]----{-# INLINE_NORMAL enumerateFromThenToIntegral #-}-enumerateFromThenToIntegral- :: (Monad m, Integral a)- => a -> a -> a -> Stream m a-#ifdef USE_UNFOLDS_EVERYWHERE-enumerateFromThenToIntegral from next to =- unfold Unfold.enumerateFromThenToIntegral (from, next, to)-#else-enumerateFromThenToIntegral from next to- | next >= from = enumerateFromThenToIntegralUp from next to- | otherwise = enumerateFromThenToIntegralDn from next to-#endif---- | 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.------ >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFromThenIntegral (0 :: Int) 2--- [0,2,4,6]------ >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFromThenIntegral (0 :: Int) (-2)--- [0,-2,-4,-6]----{-# INLINE_NORMAL enumerateFromThenIntegral #-}-enumerateFromThenIntegral- :: (Monad m, Integral a, Bounded a)- => a -> a -> Stream m a-#ifdef USE_UNFOLDS_EVERYWHERE-enumerateFromThenIntegral from next =- unfold Unfold.enumerateFromThenIntegralBounded (from, next)-#else-enumerateFromThenIntegral from next =- if next > from- then enumerateFromThenToIntegralUp from next maxBound- else enumerateFromThenToIntegralDn from next minBound-#endif---- | @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.------ >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFromStepIntegral 0 2--- [0,2,4,6]------ >>> Stream.fold Fold.toList $ Stream.take 3 $ Stream.enumerateFromStepIntegral 0 (-2)--- [0,-2,-4]----{-# INLINE_NORMAL enumerateFromStepIntegral #-}-enumerateFromStepIntegral :: (Integral a, Monad m) => a -> a -> Stream m a-#ifdef USE_UNFOLDS_EVERYWHERE-enumerateFromStepIntegral from stride =- unfold Unfold.enumerateFromStepIntegral (from, stride)-#else-enumerateFromStepIntegral from stride =- from `seq` stride `seq` Stream step from- where- {-# INLINE_LATE step #-}- step _ !x = return $ Yield x $! (x + stride)-#endif---- | 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@.------ >>> Stream.fold Fold.toList $ Stream.enumerateFromToIntegral 0 4--- [0,1,2,3,4]----{-# INLINE enumerateFromToIntegral #-}-enumerateFromToIntegral :: (Monad m, Integral a) => a -> a -> Stream m a-enumerateFromToIntegral from to =- takeWhile (<= to) $ enumerateFromStepIntegral from 1---- | 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.------ >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFromIntegral (0 :: Int)--- [0,1,2,3]----{-# INLINE enumerateFromIntegral #-}-enumerateFromIntegral :: (Monad m, Integral a, Bounded a) => a -> Stream m a-enumerateFromIntegral from = enumerateFromToIntegral from maxBound----------------------------------------------------------------------------------- Enumeration of Fractionals----------------------------------------------------------------------------------- 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.---- 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:------ >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFromFractional 1.1--- [1.1,2.1,3.1,4.1]----{-# INLINE enumerateFromFractional #-}-enumerateFromFractional :: (Monad m, Fractional a) => a -> Stream m a-enumerateFromFractional = enumerateFromNum---- | 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:------ >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFromThenFractional 1.1 2.1--- [1.1,2.1,3.1,4.1]------ >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFromThenFractional 1.1 (-2.1)--- [1.1,-2.1,-5.300000000000001,-8.500000000000002]----{-# INLINE enumerateFromThenFractional #-}-enumerateFromThenFractional- :: (Monad m, Fractional a)- => a -> a -> Stream m a-enumerateFromThenFractional = enumerateFromThenNum---- | 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:------ >>> Stream.fold Fold.toList $ Stream.enumerateFromToFractional 1.1 4--- [1.1,2.1,3.1,4.1]------ >>> Stream.fold Fold.toList $ Stream.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.----{-# 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---- | 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:------ >>> Stream.fold Fold.toList $ Stream.enumerateFromThenToFractional 0.1 2 6--- [0.1,2.0,3.9,5.799999999999999]------ >>> Stream.fold Fold.toList $ Stream.enumerateFromThenToFractional 0.1 (-2) (-6)--- [0.1,-2.0,-4.1000000000000005,-6.200000000000001]----{-# INLINE_NORMAL enumerateFromThenToFractional #-}-enumerateFromThenToFractional- :: (Monad m, Fractional a, Ord a)- => a -> a -> a -> Stream m a-enumerateFromThenToFractional from next to =- takeWhile predicate $ enumerateFromThenFractional from next- where- mid = (next - from) / 2- predicate | next >= from = (<= to + mid)- | otherwise = (>= to + mid)------------------------------------------------------------------------------------ Enumeration of Enum types not larger than Int-------------------------------------------------------------------------------------- | 'enumerateFromTo' for 'Enum' types not larger than 'Int'.----{-# INLINE enumerateFromToSmall #-}-enumerateFromToSmall :: (Monad m, Enum a) => a -> a -> Stream m a-enumerateFromToSmall from to =- fmap toEnum- $ enumerateFromToIntegral (fromEnum from) (fromEnum to)---- | 'enumerateFromThenTo' for 'Enum' types not larger than 'Int'.----{-# INLINE enumerateFromThenToSmall #-}-enumerateFromThenToSmall :: (Monad m, Enum a)- => a -> a -> a -> Stream m a-enumerateFromThenToSmall from next to =- fmap 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.----{-# INLINE enumerateFromThenSmallBounded #-}-enumerateFromThenSmallBounded :: (Monad m, Enumerable a, Bounded a)- => a -> a -> Stream m a-enumerateFromThenSmallBounded from next =- if fromEnum next >= fromEnum from- then enumerateFromThenTo from next maxBound- else 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.----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'.- --- -- >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFrom (0 :: Int)- -- [0,1,2,3]- --- -- For 'Fractional' types, enumeration is numerically stable. However, no- -- overflow or underflow checks are performed.- --- -- >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFrom 1.1- -- [1.1,2.1,3.1,4.1]- --- enumerateFrom :: (Monad m) => a -> Stream 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.- --- -- >>> Stream.fold Fold.toList $ Stream.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.- --- -- >>> Stream.fold Fold.toList $ Stream.enumerateFromTo 1.1 4- -- [1.1,2.1,3.1,4.1]- --- -- >>> Stream.fold Fold.toList $ Stream.enumerateFromTo 1.1 4.6- -- [1.1,2.1,3.1,4.1,5.1]- --- enumerateFromTo :: (Monad m) => a -> a -> Stream 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.- --- -- >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFromThen 0 2- -- [0,2,4,6]- --- -- >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.enumerateFromThen 0 (-2)- -- [0,-2,-4,-6]- --- enumerateFromThen :: (Monad m) => a -> a -> Stream 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@.- --- -- >>> Stream.fold Fold.toList $ Stream.enumerateFromThenTo 0 2 6- -- [0,2,4,6]- --- -- >>> Stream.fold Fold.toList $ Stream.enumerateFromThenTo 0 (-2) (-6)- -- [0,-2,-4,-6]- --- enumerateFromThenTo :: (Monad m) => a -> a -> a -> Stream 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'----{-# INLINE enumerate #-}-enumerate :: (Monad m, Bounded a, Enumerable a) => Stream m a-enumerate = enumerateFrom minBound---- |--- >>> enumerateTo = Stream.enumerateFromTo minBound------ Enumerate a 'Bounded' type from its 'minBound' to specified value.----{-# INLINE enumerateTo #-}-enumerateTo :: (Monad m, Bounded a, Enumerable a) => a -> Stream m a-enumerateTo = enumerateFromTo minBound---- |--- >>> enumerateFromBounded from = Stream.enumerateFromTo from maxBound------ 'enumerateFrom' for 'Bounded' 'Enum' types.----{-# INLINE enumerateFromBounded #-}-enumerateFromBounded :: (Monad m, Enumerable a, Bounded a)- => a -> Stream 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)--instance Enumerable a => Enumerable (Identity a) where- {-# INLINE enumerateFrom #-}- enumerateFrom (Identity from) =- fmap Identity $ enumerateFrom from- {-# INLINE enumerateFromThen #-}- enumerateFromThen (Identity from) (Identity next) =- fmap Identity $ enumerateFromThen from next- {-# INLINE enumerateFromTo #-}- enumerateFromTo (Identity from) (Identity to) =- fmap Identity $ enumerateFromTo from to- {-# INLINE enumerateFromThenTo #-}- enumerateFromThenTo (Identity from) (Identity next) (Identity to) =- fmap Identity- $ enumerateFromThenTo from next to---- 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)--}---------------------------------------------------------------------------------- Time Enumeration----------------------------------------------------------------------------------- | @timesWith g@ returns a stream of time value tuples. The first component--- of the tuple is an absolute time reference (epoch) denoting the start of the--- stream and the second component is a time relative to the reference.------ The argument @g@ specifies the granularity of the relative time in seconds.--- A lower granularity clock gives higher precision but is more expensive in--- terms of CPU usage. Any granularity lower than 1 ms is treated as 1 ms.------ >>> import Control.Concurrent (threadDelay)--- >>> f = Fold.drainMapM (\x -> print x >> threadDelay 1000000)--- >>> Stream.fold f $ Stream.take 3 $ Stream.timesWith 0.01--- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))--- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))--- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))------ Note: This API is not safe on 32-bit machines.------ /Pre-release/----{-# INLINE_NORMAL timesWith #-}-timesWith :: MonadIO m => Double -> Stream m (AbsTime, RelTime64)-timesWith g = Stream step Nothing-- where-- {-# INLINE_LATE step #-}- step _ Nothing = do- clock <- liftIO $ asyncClock Monotonic g- a <- liftIO $ readClock clock- return $ Skip $ Just (clock, a)-- step _ s@(Just (clock, t0)) = do- a <- liftIO $ readClock clock- -- 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 t0, toRelTime64 (a - t0)) s---- | @absTimesWith 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. Any granularity lower than 1 ms is treated--- as 1 ms.------ >>> f = Fold.drainMapM print--- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.absTimesWith 0.01--- AbsTime (TimeSpec {sec = ..., nsec = ...})--- AbsTime (TimeSpec {sec = ..., nsec = ...})--- AbsTime (TimeSpec {sec = ..., nsec = ...})------ Note: This API is not safe on 32-bit machines.------ /Pre-release/----{-# INLINE absTimesWith #-}-absTimesWith :: MonadIO m => Double -> Stream m AbsTime-absTimesWith = fmap (uncurry addToAbsTime64) . timesWith---- | @relTimesWith g@ returns a stream of relative time values starting from 0,--- using a clock of granularity @g@ specified in seconds. A low granularity--- clock is more expensive in terms of CPU usage. Any granularity lower than 1--- ms is treated as 1 ms.------ >>> f = Fold.drainMapM print--- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.relTimesWith 0.01--- RelTime64 (NanoSecond64 ...)--- RelTime64 (NanoSecond64 ...)--- RelTime64 (NanoSecond64 ...)------ Note: This API is not safe on 32-bit machines.------ /Pre-release/----{-# INLINE relTimesWith #-}-relTimesWith :: MonadIO m => Double -> Stream m RelTime64-relTimesWith = fmap snd . timesWith---- | @times@ returns a stream of time value tuples with clock of 10 ms--- granularity. The first component of the tuple is an absolute time reference--- (epoch) denoting the start of the stream and the second component is a time--- relative to the reference.------ >>> f = Fold.drainMapM (\x -> print x >> threadDelay 1000000)--- >>> Stream.fold f $ Stream.take 3 $ Stream.times--- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))--- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))--- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))------ Note: This API is not safe on 32-bit machines.------ /Pre-release/----{-# INLINE times #-}-times :: MonadIO m => Stream m (AbsTime, RelTime64)-times = timesWith 0.01---- | @absTimes@ returns a stream of absolute timestamps using a clock of 10 ms--- granularity.------ >>> f = Fold.drainMapM print--- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.absTimes--- AbsTime (TimeSpec {sec = ..., nsec = ...})--- AbsTime (TimeSpec {sec = ..., nsec = ...})--- AbsTime (TimeSpec {sec = ..., nsec = ...})------ Note: This API is not safe on 32-bit machines.------ /Pre-release/----{-# INLINE absTimes #-}-absTimes :: MonadIO m => Stream m AbsTime-absTimes = fmap (uncurry addToAbsTime64) times---- | @relTimes@ returns a stream of relative time values starting from 0,--- using a clock of granularity 10 ms.------ >>> f = Fold.drainMapM print--- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.relTimes--- RelTime64 (NanoSecond64 ...)--- RelTime64 (NanoSecond64 ...)--- RelTime64 (NanoSecond64 ...)------ Note: This API is not safe on 32-bit machines.------ /Pre-release/----{-# INLINE relTimes #-}-relTimes :: MonadIO m => Stream m RelTime64-relTimes = fmap snd times---- | @durations g@ returns a stream of relative time values measuring the time--- elapsed since the immediate predecessor element of the stream was generated.--- The first element of the stream is always 0. @durations@ uses a clock of--- granularity @g@ specified in seconds. A low granularity clock is more--- expensive in terms of CPU usage. The minimum granularity is 1 millisecond.--- Durations lower than 1 ms will be 0.------ Note: This API is not safe on 32-bit machines.------ /Unimplemented/----{-# INLINE durations #-}-durations :: -- Monad m =>- Double -> t m RelTime64-durations = undefined---- | Generate a singleton event at or after the specified absolute time. Note--- that this is different from a threadDelay, a threadDelay starts from the--- time when the action is evaluated, whereas if we use AbsTime based timeout--- it will immediately expire if the action is evaluated too late.------ /Unimplemented/----{-# INLINE timeout #-}-timeout :: -- Monad m =>- AbsTime -> t m ()-timeout = undefined------------------------------------------------------------------------------------ From Generators----------------------------------------------------------------------------------{-# INLINE_NORMAL fromIndicesM #-}-fromIndicesM :: Monad m => (Int -> m a) -> Stream m a-#ifdef USE_UNFOLDS_EVERYWHERE-fromIndicesM gen = unfold (Unfold.fromIndicesM gen) 0-#else-fromIndicesM gen = Stream step 0- where- {-# INLINE_LATE step #-}- step _ i = do- x <- gen i- return $ Yield x (i + 1)-#endif--{-# INLINE fromIndices #-}-fromIndices :: Monad m => (Int -> a) -> Stream m a-fromIndices gen = fromIndicesM (return . gen)---- Adapted from the vector package-{-# 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)------------------------------------------------------------------------------------ Iteration------------------------------------------------------------------------------------ |--- >>> iterateM f m = m >>= \a -> return a `Stream.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.------ >>> :{--- Stream.iterateM (\x -> print x >> return (x + 1)) (return 0)--- & Stream.take 3--- & Stream.fold Fold.toList--- :}--- 0--- 1--- [0,1,2]----{-# INLINE_NORMAL iterateM #-}-iterateM :: Monad m => (a -> m a) -> m a -> Stream m a-#ifdef USE_UNFOLDS_EVERYWHERE-iterateM step = unfold (Unfold.iterateM step)-#else-iterateM step = Stream (\_ st -> st >>= \(!x) -> return $ Yield x (step x))-#endif---- |--- >>> iterate f x = x `Stream.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.------ >>> Stream.fold Fold.toList $ Stream.take 5 $ Stream.iterate (+1) 1--- [1,2,3,4,5]----{-# INLINE_NORMAL iterate #-}-iterate :: Monad m => (a -> a) -> a -> Stream m a-iterate step st = iterateM (return . step) (return st)------------------------------------------------------------------------------------ From containers------------------------------------------------------------------------------------ | Convert a list of monadic actions to a 'Stream'-{-# INLINE_LATE fromListM #-}-fromListM :: Monad m => [m a] -> Stream m a-#ifdef USE_UNFOLDS_EVERYWHERE-fromListM = unfold Unfold.fromListM-#else-fromListM = Stream step- where- {-# INLINE_LATE step #-}- step _ (m:ms) = m >>= \x -> return $ Yield x ms- step _ [] = return Stop-#endif---- |--- >>> fromFoldable = Prelude.foldr Stream.cons Stream.nil------ Construct a stream from a 'Foldable' containing pure values:------ /WARNING: O(n^2), suitable only for a small number of--- elements in the stream/----{-# INLINE fromFoldable #-}-fromFoldable :: (Monad m, Foldable f) => f a -> Stream m a-fromFoldable = Prelude.foldr cons nil---- |--- >>> fromFoldableM = Prelude.foldr Stream.consM Stream.nil------ Construct a stream from a 'Foldable' containing pure values:------ /WARNING: O(n^2), suitable only for a small number of--- elements in the stream/----{-# INLINE fromFoldableM #-}-fromFoldableM :: (Monad m, Foldable f) => f (m a) -> Stream m a-fromFoldableM = Prelude.foldr consM nil------------------------------------------------------------------------------------ From pointers------------------------------------------------------------------------------------ | Keep reading 'Storable' elements from 'Ptr' onwards.------ /Unsafe:/ The caller is responsible for safe addressing.------ /Pre-release/-{-# INLINE fromPtr #-}-fromPtr :: forall m a. (MonadIO m, Storable a) => Ptr a -> Stream m a-fromPtr = Stream step-- where-- {-# INLINE_LATE step #-}- step _ p = do- x <- liftIO $ peek p- return $ Yield x (PTR_NEXT(p, a))---- | Take @n@ 'Storable' elements starting from 'Ptr' onwards.------ >>> fromPtrN n = Stream.take n . Stream.fromPtr------ /Unsafe:/ The caller is responsible for safe addressing.------ /Pre-release/-{-# INLINE fromPtrN #-}-fromPtrN :: (MonadIO m, Storable a) => Int -> Ptr a -> Stream m a-fromPtrN n = take n . fromPtr---- | Read bytes from an 'Addr#' until a 0 byte is encountered, the 0 byte is--- not included in the stream.------ >>> :set -XMagicHash--- >>> fromByteStr# addr = Stream.takeWhile (/= 0) $ Stream.fromPtr $ Ptr addr------ /Unsafe:/ The caller is responsible for safe addressing.------ Note that this is completely safe when reading from Haskell string--- literals because they are guaranteed to be NULL terminated:------ >>> Stream.fold Fold.toList $ Stream.fromByteStr# "\1\2\3\0"#--- [1,2,3]----{-# INLINE fromByteStr# #-}-fromByteStr# :: MonadIO m => Addr# -> Stream m Word8-fromByteStr# addr =- takeWhile (/= 0) $ fromPtr $ Ptr addr
− src/Streamly/Internal/Data/Stream/StreamD/Lift.hs
@@ -1,129 +0,0 @@-{-# LANGUAGE CPP #-}--- |--- Module : Streamly.Internal.Data.Stream.StreamD.Lift--- Copyright : (c) 2018 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ Transform the underlying monad of a stream.--module Streamly.Internal.Data.Stream.StreamD.Lift- (- -- * Generalize Inner Monad- morphInner- , generalizeInner-- -- * Transform Inner Monad- , liftInnerWith- , runInnerWith- , runInnerWithState- )-where--#include "inline.hs"--import Data.Functor.Identity (Identity(..))-import Streamly.Internal.Data.SVar.Type (adaptState)--import Streamly.Internal.Data.Stream.StreamD.Type--#include "DocTestDataStream.hs"------------------------------------------------------------------------------------ Generalize Inner Monad------------------------------------------------------------------------------------ | Transform the inner monad of a stream using a natural transformation.------ Example, generalize the inner monad from Identity to any other:------ >>> generalizeInner = Stream.morphInner (return . runIdentity)------ Also known as hoist.----{-# INLINE_NORMAL morphInner #-}-morphInner :: Monad n => (forall x. m x -> n x) -> Stream m a -> Stream n a-morphInner 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---- | Generalize the inner monad of the stream from 'Identity' to any monad.------ Definition:------ >>> generalizeInner = Stream.morphInner (return . runIdentity)----{-# INLINE generalizeInner #-}-generalizeInner :: Monad m => Stream Identity a -> Stream m a-generalizeInner = morphInner (return . runIdentity)------------------------------------------------------------------------------------ Transform Inner Monad------------------------------------------------------------------------------------ | Lift the inner monad @m@ of a stream @Stream m a@ to @t m@ using the--- supplied lift function.----{-# INLINE_NORMAL liftInnerWith #-}-liftInnerWith :: (Monad (t m)) =>- (forall b. m b -> t m b) -> Stream m a -> Stream (t m) a-liftInnerWith lift (Stream step state) = Stream step1 state-- where-- {-# INLINE_LATE step1 #-}- step1 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---- | Evaluate the inner monad of a stream using the supplied runner function.----{-# INLINE_NORMAL runInnerWith #-}-runInnerWith :: Monad m =>- (forall b. t m b -> m b) -> Stream (t m) a -> Stream m a-runInnerWith run (Stream step state) = Stream step1 state-- where-- {-# INLINE_LATE step1 #-}- step1 gst st = do- r <- run $ step (adaptState gst) st- return $ case r of- Yield x s -> Yield x s- Skip s -> Skip s- Stop -> Stop---- | Evaluate the inner monad of a stream using the supplied stateful runner--- function and the initial state. The state returned by an invocation of the--- runner is supplied as input state to the next invocation.----{-# INLINE_NORMAL runInnerWithState #-}-runInnerWithState :: Monad m =>- (forall b. s -> t m b -> m (b, s))- -> m s- -> Stream (t m) a- -> Stream m (s, a)-runInnerWithState run initial (Stream step state) =- Stream step1 (state, initial)-- where-- {-# INLINE_LATE step1 #-}- step1 gst (st, action) = do- sv <- action- (r, !sv1) <- run sv (step (adaptState gst) st)- return $ case r of- Yield x s -> Yield (sv1, x) (s, return sv1)- Skip s -> Skip (s, return sv1)- Stop -> Stop
− src/Streamly/Internal/Data/Stream/StreamD/Nesting.hs
@@ -1,3111 +0,0 @@-{-# LANGUAGE CPP #-}--- |--- Module : Streamly.Internal.Data.Stream.StreamD.Nesting--- Copyright : (c) 2018 Composewell Technologies--- (c) Roman Leshchinskiy 2008-2010--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ This module contains transformations involving multiple streams, unfolds or--- folds. There are two types of transformations generational or eliminational.--- Generational transformations are like the "Generate" module but they--- generate a stream by combining streams instead of elements. Eliminational--- transformations are like the "Eliminate" module but they transform a stream--- by eliminating parts of the stream instead of eliminating the whole stream.------ These combinators involve transformation, generation, elimination so can be--- classified under any of those.------ Ultimately these operations should be supported by Unfolds, Pipes and Folds,--- and this module may become redundant.---- The zipWithM combinator in this module has been adapted from the vector--- package (c) Roman Leshchinskiy.----module Streamly.Internal.Data.Stream.StreamD.Nesting- (- -- * Generate- -- | Combining streams to generate streams.-- -- ** Combine Two Streams- -- | Functions ending in the shape:- --- -- @t m a -> t m a -> t m a@.-- -- *** Appending- -- | Append a stream after another. A special case of concatMap or- -- unfoldMany.- AppendState(..)- , append-- -- *** Interleaving- -- | Interleave elements from two streams alternately. A special case of- -- unfoldInterleave.- , InterleaveState(..)- , interleave- , interleaveMin- , interleaveFst- , interleaveFstSuffix-- -- *** Scheduling- -- | Execute streams alternately irrespective of whether they generate- -- elements or not. Note 'interleave' would execute a stream until it- -- yields an element. A special case of unfoldRoundRobin.- , roundRobin -- interleaveFair?/ParallelFair-- -- *** Zipping- -- | Zip corresponding elements of two streams.- , zipWith- , zipWithM-- -- *** Merging- -- | Interleave elements from two streams based on a condition.- , mergeBy- , mergeByM- , mergeMinBy- , mergeFstBy-- -- ** Combine N Streams- -- | Functions generally ending in these shapes:- --- -- @- -- concat: f (t m a) -> t m a- -- concatMap: (a -> t m b) -> t m a -> t m b- -- unfoldMany: Unfold m a b -> t m a -> t m b- -- @-- -- *** ConcatMap- -- | Generate streams by mapping a stream generator on each element of an- -- input stream, append the resulting streams and flatten.- , concatMap- , concatMapM-- -- *** ConcatUnfold- -- | Generate streams by using an unfold on each element of an input- -- stream, append the resulting streams and flatten. A special case of- -- gintercalate.- , unfoldMany- , ConcatUnfoldInterleaveState (..)- , unfoldInterleave- , unfoldRoundRobin-- -- *** Interpose- -- | Like unfoldMany but intersperses an effect between the streams. A- -- special case of gintercalate.- , interpose- , interposeM- , interposeSuffix- , interposeSuffixM-- -- *** Intercalate- -- | Like unfoldMany but intersperses streams from another source between- -- the streams from the first source.- , gintercalate- , gintercalateSuffix- , intercalate- , intercalateSuffix-- -- * Eliminate- -- | Folding and Parsing chunks of streams to eliminate nested streams.- -- Functions generally ending in these shapes:- --- -- @- -- f (Fold m a b) -> t m a -> t m b- -- f (Parser a m b) -> t m a -> t m b- -- @-- -- ** Folding- -- | Apply folds on a stream.- , foldMany- , refoldMany- , foldSequence- , foldIterateM- , refoldIterateM-- -- ** Parsing- -- | Parsing is opposite to flattening. 'parseMany' is dual to concatMap or- -- unfoldMany. concatMap generates a stream from single values in a- -- stream and flattens, parseMany does the opposite of flattening by- -- splitting the stream and then folds each such split to single value in- -- the output stream.- , parseMany- , parseManyD- , parseSequence- , parseManyTill- , parseIterate- , parseIterateD-- -- ** Grouping- -- | Group segments of a stream and fold. Special case of parsing.- , groupsOf- , groupsBy- , groupsRollingBy-- -- ** Splitting- -- | A special case of parsing.- , wordsBy- , splitOnSeq- , splitOnSuffixSeq- , sliceOnSuffix-- -- XXX Implement these as folds or parsers instead.- , splitOnSuffixSeqAny- , splitOnPrefix- , splitOnAny-- -- * Transform (Nested Containers)- -- | Opposite to compact in ArrayStream- , splitInnerBy- , splitInnerBySuffix- , intersectBySorted-- -- * Reduce By Streams- , dropPrefix- , dropInfix- , dropSuffix- )-where--#include "inline.hs"-#include "ArrayMacros.h"--import Control.Exception (assert)-import Control.Monad.IO.Class (MonadIO(..))-import Data.Bits (shiftR, shiftL, (.|.), (.&.))-import Data.Proxy (Proxy(..))-import Data.Word (Word32)-import Foreign.Storable (Storable, peek)-import Fusion.Plugin.Types (Fuse(..))-import GHC.Types (SPEC(..))--import Streamly.Internal.Data.Array.Type (Array(..))-import Streamly.Internal.Data.Fold.Step (Step(..))-import Streamly.Internal.Data.Fold.Type (Fold(..))-import Streamly.Internal.Data.Parser (ParseError(..))-import Streamly.Internal.Data.Refold.Type (Refold(..))-import Streamly.Internal.Data.SVar.Type (adaptState)-import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))-import Streamly.Internal.Data.Unboxed (Unbox, sizeOf)-import Streamly.Internal.Data.Unfold.Type (Unfold(..))--import qualified Streamly.Internal.Data.Array.Type as A-import qualified Streamly.Internal.Data.Fold as FL-import qualified Streamly.Internal.Data.Parser as PR-import qualified Streamly.Internal.Data.Parser.ParserD as PRD-import qualified Streamly.Internal.Data.Ring.Unboxed as RB--import Streamly.Internal.Data.Stream.StreamD.Transform- (intersperse, intersperseMSuffix)-import Streamly.Internal.Data.Stream.StreamD.Type--import Prelude hiding (concatMap, mapM, zipWith)--#include "DocTestDataStream.hs"----------------------------------------------------------------------------------- Appending---------------------------------------------------------------------------------data AppendState s1 s2 = AppendFirst s1 | AppendSecond s2---- | Fuses two streams sequentially, yielding all elements from the first--- stream, and then all elements from the second stream.------ >>> s1 = Stream.fromList [1,2]--- >>> s2 = Stream.fromList [3,4]--- >>> Stream.fold Fold.toList $ s1 `Stream.append` s2--- [1,2,3,4]------ This function should not be used to dynamically construct a stream. If a--- stream is constructed by successive use of this function it would take--- quadratic time complexity to consume the stream.------ This function should only be used to statically fuse a stream with another--- stream. Do not use this recursively or where it cannot be inlined.------ See "Streamly.Data.StreamK" for an 'append' that can be used to--- construct a stream recursively.----{-# 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----------------------------------------------------------------------------------- Interleaving---------------------------------------------------------------------------------data InterleaveState s1 s2 = InterleaveFirst s1 s2 | InterleaveSecond s1 s2- | InterleaveSecondOnly s2 | InterleaveFirstOnly s1---- | 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.------ When joining many streams in a left associative manner earlier streams will--- get exponential priority than the ones joining later. Because of exponential--- weighting it can be used with 'concatMapWith' even on a large number of--- streams.----{-# 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---- | Like `interleave` but stops interleaving as soon as any of the two streams--- stops.----{-# 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---- | 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--- >>> import Data.Functor.Identity (Identity)--- >>> Stream.interleaveFstSuffix "abc" ",,,," :: Stream Identity Char--- fromList "a,b,c,"--- >>> Stream.interleaveFstSuffix "abc" "," :: Stream Identity Char--- fromList "a,bc"------ 'interleaveFstSuffix' is a dual of 'interleaveFst'.------ Do not use dynamically.------ /Pre-release/-{-# INLINE_NORMAL interleaveFstSuffix #-}-interleaveFstSuffix :: Monad m => Stream m a -> Stream m a -> Stream m a-interleaveFstSuffix (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---- | 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--- >>> import Data.Functor.Identity (Identity)--- >>> Stream.interleaveFst "abc" ",,,," :: Stream Identity Char--- fromList "a,b,c"--- >>> Stream.interleaveFst "abc" "," :: Stream Identity Char--- fromList "a,bc"------ 'interleaveFst' is a dual of 'interleaveFstSuffix'.------ Do not use dynamically.------ /Pre-release/-{-# INLINE_NORMAL interleaveFst #-}-interleaveFst :: Monad m => Stream m a -> Stream m a -> Stream m a-interleaveFst (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----------------------------------------------------------------------------------- Scheduling----------------------------------------------------------------------------------- | 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 choose 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 dynamically.------ /Pre-release/-{-# 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----------------------------------------------------------------------------------- Merging----------------------------------------------------------------------------------- | Like 'mergeBy' but with a monadic comparison function.------ Merge two streams randomly:------ @--- > randomly _ _ = randomIO >>= \x -> return $ if x then LT else GT--- > Stream.toList $ Stream.mergeByM randomly (Stream.fromList [1,1,1,1]) (Stream.fromList [2,2,2,2])--- [2,1,2,2,2,1,1,1]--- @------ Merge two streams in a proportion of 2:1:------ >>> :{--- do--- let s1 = Stream.fromList [1,1,1,1,1,1]--- s2 = Stream.fromList [2,2,2]--- let proportionately m n = do--- ref <- newIORef $ cycle $ Prelude.concat [Prelude.replicate m LT, Prelude.replicate n GT]--- return $ \_ _ -> do--- r <- readIORef ref--- writeIORef ref $ Prelude.tail r--- return $ Prelude.head r--- f <- proportionately 2 1--- xs <- Stream.fold Fold.toList $ Stream.mergeByM f s1 s2--- print xs--- :}--- [1,1,2,1,1,2,1,1,2]----{-# 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---- | 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.------ >>> s1 = Stream.fromList [1,3,5]--- >>> s2 = Stream.fromList [2,4,6,8]--- >>> Stream.fold Fold.toList $ Stream.mergeBy compare s1 s2--- [1,2,3,4,5,6,8]----{-# 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)---- | Like 'mergeByM' but stops merging as soon as any of the two streams stops.------ /Unimplemented/-{-# INLINABLE mergeMinBy #-}-mergeMinBy :: -- Monad m =>- (a -> a -> m Ordering) -> Stream m a -> Stream m a -> Stream m a-mergeMinBy _f _m1 _m2 = undefined- -- fromStreamD $ D.mergeMinBy f (toStreamD m1) (toStreamD m2)---- | Like 'mergeByM' but stops merging as soon as the first stream stops.------ /Unimplemented/-{-# INLINABLE mergeFstBy #-}-mergeFstBy :: -- Monad m =>- (a -> a -> m Ordering) -> Stream m a -> Stream m a -> Stream m a-mergeFstBy _f _m1 _m2 = undefined- -- fromStreamK $ D.mergeFstBy f (toStreamD m1) (toStreamD m2)------------------------------------------------------------------------------------ Intersection of sorted streams------------------------------------------------------------------------------------ Assuming the streams are sorted in ascending order-{-# INLINE_NORMAL intersectBySorted #-}-intersectBySorted :: Monad m- => (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a-intersectBySorted cmp (Stream stepa ta) (Stream stepb tb) =- Stream step- ( ta -- left stream state- , tb -- right stream state- , Nothing -- left value- , Nothing -- right value- )-- where-- {-# INLINE_LATE step #-}- -- step 1, fetch the first value- step gst (sa, sb, Nothing, b) = do- r <- stepa gst sa- return $ case r of- Yield a sa' -> Skip (sa', sb, Just a, b) -- step 2/3- Skip sa' -> Skip (sa', sb, Nothing, b)- Stop -> Stop-- -- step 2, fetch the second value- step gst (sa, sb, a@(Just _), Nothing) = do- r <- stepb gst sb- return $ case r of- Yield b sb' -> Skip (sa, sb', a, Just b) -- step 3- Skip sb' -> Skip (sa, sb', a, Nothing)- Stop -> Stop-- -- step 3, compare the two values- step _ (sa, sb, Just a, Just b) = do- let res = cmp a b- return $ case res of- GT -> Skip (sa, sb, Just a, Nothing) -- step 2- LT -> Skip (sa, sb, Nothing, Just b) -- step 1- EQ -> Yield a (sa, sb, Nothing, Just b) -- step 1----------------------------------------------------------------------------------- Combine N Streams - unfoldMany---------------------------------------------------------------------------------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.------ XXX Instead of using "concatPairsWith wSerial" we can implement an N-way--- interleaving CPS combinator which behaves like unfoldInterleave. Instead--- of pairing up the streams we just need to go yielding one element from each--- stream and storing the remaining streams and then keep doing rounds through--- those in a round robin fashion. This would be much like wAsync.---- | This does not pair streams like mergeMapWith, instead, it goes through--- each stream one by one and yields one element from each stream. After it--- goes to the last stream it reverses the traversal to come back to the first--- stream yielding elements from each stream on its way back to the first--- stream and so on.------ >>> lists = Stream.fromList [[1,1],[2,2],[3,3],[4,4],[5,5]]--- >>> interleaved = Stream.unfoldInterleave Unfold.fromList lists--- >>> Stream.fold Fold.toList interleaved--- [1,2,3,4,5,5,4,3,2,1]------ Note that this is order of magnitude more efficient than "mergeMapWith--- interleave" because of fusion.----{-# INLINE_NORMAL unfoldInterleave #-}-unfoldInterleave :: Monad m => Unfold m a b -> Stream m a -> Stream m b-unfoldInterleave (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 unfoldInterleave this one switches streams on Skips.---- | 'unfoldInterleave' switches to the next stream whenever a value from a--- stream is yielded, it does not switch on a 'Skip'. So if a stream keeps--- skipping for long time other streams won't get a chance to run.--- 'unfoldRoundRobin' switches on Skip as well. So it basically schedules each--- stream fairly irrespective of whether it produces a value or not.----{-# INLINE_NORMAL unfoldRoundRobin #-}-unfoldRoundRobin :: Monad m => Unfold m a b -> Stream m a -> Stream m b-unfoldRoundRobin (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)----------------------------------------------------------------------------------- Combine N Streams - interpose---------------------------------------------------------------------------------{-# ANN type InterposeSuffixState Fuse #-}-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 interposeSuffixM #-}-interposeSuffixM- :: Monad m- => m c -> Unfold m b c -> Stream m b -> Stream m c-interposeSuffixM- 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)---- 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 = Stream.interposeSuffix '\n'------ /Pre-release/-{-# INLINE interposeSuffix #-}-interposeSuffix :: Monad m- => c -> Unfold m b c -> Stream m b -> Stream m c-interposeSuffix x = interposeSuffixM (return x)--{-# ANN type InterposeState Fuse #-}-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 interposeM #-}-interposeM :: Monad m => m c -> Unfold m b c -> Stream m b -> Stream m c-interposeM- 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)- -}---- > 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 = Stream.interpose ' '------ /Pre-release/-{-# INLINE interpose #-}-interpose :: Monad m- => c -> Unfold m b c -> Stream m b -> Stream m c-interpose x = interposeM (return x)----------------------------------------------------------------------------------- Combine N Streams - intercalate---------------------------------------------------------------------------------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---- | 'interleaveFstSuffix' followed by unfold and concat.------ /Pre-release/-{-# 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 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---- XXX we can swap the order of arguments to gintercalate so that the--- definition of unfoldMany 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.------ > unfoldMany unf str =--- > gintercalate unf str (UF.nilM (\_ -> return ())) (repeat ())---- | 'interleaveFst' followed by unfold and concat.------ /Pre-release/-{-# 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)- -}---- > intercalateSuffix unf seed str = gintercalateSuffix unf str unf (repeatM seed)---- | 'intersperseMSuffix' followed by unfold and concat.------ >>> intercalateSuffix u a = Stream.unfoldMany u . Stream.intersperseMSuffix a--- >>> intersperseMSuffix = Stream.intercalateSuffix Unfold.identity--- >>> unlines = Stream.intercalateSuffix Unfold.fromList "\n"------ >>> input = Stream.fromList ["abc", "def", "ghi"]--- >>> Stream.fold Fold.toList $ Stream.intercalateSuffix Unfold.fromList "\n" input--- "abc\ndef\nghi\n"----{-# INLINE intercalateSuffix #-}-intercalateSuffix :: Monad m- => Unfold m b c -> b -> Stream m b -> Stream m c-intercalateSuffix unf seed = unfoldMany unf . intersperseMSuffix (return seed)---- > intercalate unf seed str = gintercalate unf str unf (repeatM seed)---- | 'intersperse' followed by unfold and concat.------ >>> intercalate u a = Stream.unfoldMany u . Stream.intersperse a--- >>> intersperse = Stream.intercalate Unfold.identity--- >>> unwords = Stream.intercalate Unfold.fromList " "------ >>> input = Stream.fromList ["abc", "def", "ghi"]--- >>> Stream.fold Fold.toList $ Stream.intercalate Unfold.fromList " " input--- "abc def ghi"----{-# INLINE intercalate #-}-intercalate :: Monad m- => Unfold m b c -> b -> Stream m b -> Stream m c-intercalate unf seed str = unfoldMany unf $ intersperse seed str----------------------------------------------------------------------------------- Folding----------------------------------------------------------------------------------- | Apply a stream of folds to an input stream and emit the results in the--- output stream.------ /Unimplemented/----{-# INLINE foldSequence #-}-foldSequence- :: -- Monad m =>- Stream m (Fold m a b)- -> Stream m a- -> Stream m b-foldSequence _f _m = undefined--{-# ANN type FIterState Fuse #-}-data FIterState s f m a b- = FIterInit s f- | forall fs. FIterStream s (fs -> a -> m (FL.Step fs b)) fs (fs -> m b)- | FIterYield b (FIterState s f m a b)- | FIterStop---- | Iterate a fold generator on a stream. The initial value @b@ is used to--- generate the first fold, the fold is applied on the stream and the result of--- the fold is used to generate the next fold and so on.------ >>> import Data.Monoid (Sum(..))--- >>> f x = return (Fold.take 2 (Fold.sconcat x))--- >>> s = fmap Sum $ Stream.fromList [1..10]--- >>> Stream.fold Fold.toList $ fmap getSum $ Stream.foldIterateM f (pure 0) s--- [3,10,21,36,55,55]------ This is the streaming equivalent of monad like sequenced application of--- folds where next fold is dependent on the previous fold.------ /Pre-release/----{-# INLINE_NORMAL foldIterateM #-}-foldIterateM ::- Monad m => (b -> m (FL.Fold m a b)) -> m b -> Stream m a -> Stream m b-foldIterateM func seed0 (Stream step state) =- Stream stepOuter (FIterInit state seed0)-- where-- {-# INLINE iterStep #-}- iterStep from st fstep extract = do- res <- from- return- $ Skip- $ case res of- FL.Partial fs -> FIterStream st fstep fs extract- FL.Done fb -> FIterYield fb $ FIterInit st (return fb)-- {-# INLINE_LATE stepOuter #-}- stepOuter _ (FIterInit st seed) = do- (FL.Fold fstep initial extract) <- seed >>= func- iterStep initial st fstep extract- stepOuter gst (FIterStream st fstep fs extract) = do- r <- step (adaptState gst) st- case r of- Yield x s -> do- iterStep (fstep fs x) s fstep extract- Skip s -> return $ Skip $ FIterStream s fstep fs extract- Stop -> do- b <- extract fs- return $ Skip $ FIterYield b FIterStop- stepOuter _ (FIterYield a next) = return $ Yield a next- stepOuter _ FIterStop = return Stop--{-# ANN type CIterState Fuse #-}-data CIterState s f fs b- = CIterInit s f- | CIterConsume s fs- | CIterYield b (CIterState s f fs b)- | CIterStop---- | Like 'foldIterateM' but using the 'Refold' type instead. This could be--- much more efficient due to stream fusion.------ /Internal/-{-# INLINE_NORMAL refoldIterateM #-}-refoldIterateM ::- Monad m => Refold m b a b -> m b -> Stream m a -> Stream m b-refoldIterateM (Refold fstep finject fextract) initial (Stream step state) =- Stream stepOuter (CIterInit state initial)-- where-- {-# INLINE iterStep #-}- iterStep st action = do- res <- action- return- $ Skip- $ case res of- FL.Partial fs -> CIterConsume st fs- FL.Done fb -> CIterYield fb $ CIterInit st (return fb)-- {-# INLINE_LATE stepOuter #-}- stepOuter _ (CIterInit st action) = do- iterStep st (action >>= finject)- stepOuter gst (CIterConsume st fs) = do- r <- step (adaptState gst) st- case r of- Yield x s -> iterStep s (fstep fs x)- Skip s -> return $ Skip $ CIterConsume s fs- Stop -> do- b <- fextract fs- return $ Skip $ CIterYield b CIterStop- stepOuter _ (CIterYield a next) = return $ Yield a next- stepOuter _ CIterStop = return Stop---- "n" elements at the end are dropped by the fold.-{-# INLINE sliceBy #-}-sliceBy :: Monad m => Fold m a Int -> Int -> Refold m (Int, Int) a (Int, Int)-sliceBy (Fold step1 initial1 extract1) n = Refold step inject extract-- where-- inject (i, len) = do- r <- initial1- return $ case r of- Partial s -> Partial $ Tuple' (i + len + n) s- Done l -> Done (i, l)-- step (Tuple' i s) x = do- r <- step1 s x- return $ case r of- Partial s1 -> Partial $ Tuple' i s1- Done len -> Done (i, len)-- extract (Tuple' i s) = (i,) <$> extract1 s--{-# INLINE sliceOnSuffix #-}-sliceOnSuffix :: Monad m => (a -> Bool) -> Stream m a -> Stream m (Int, Int)-sliceOnSuffix predicate =- -- Scan the stream with the given refold- refoldIterateM- (sliceBy (FL.takeEndBy_ predicate FL.length) 1)- (return (-1, 0))----------------------------------------------------------------------------------- Parsing---------------------------------------------------------------------------------{-# ANN type ParseChunksState Fuse #-}-data ParseChunksState x inpBuf st pst =- ParseChunksInit inpBuf st- | ParseChunksInitBuf inpBuf- | ParseChunksInitLeftOver inpBuf- | ParseChunksStream st inpBuf !pst- | ParseChunksStop inpBuf !pst- | ParseChunksBuf inpBuf st inpBuf !pst- | ParseChunksExtract inpBuf inpBuf !pst- | ParseChunksYield x (ParseChunksState x inpBuf st pst)---- XXX return the remaining stream as part of the error.--- XXX This is in fact parseMany1 (a la foldMany1). Do we need a parseMany as--- well?-{-# INLINE_NORMAL parseManyD #-}-parseManyD- :: Monad m- => PRD.Parser a m b- -> Stream m a- -> Stream m (Either ParseError b)-parseManyD (PRD.Parser pstep initial extract) (Stream step state) =- Stream stepOuter (ParseChunksInit [] state)-- where-- {-# INLINE_LATE stepOuter #-}- -- Buffer is empty, get the first element from the stream, initialize the- -- fold and then go to stream processing loop.- stepOuter gst (ParseChunksInit [] st) = do- r <- step (adaptState gst) st- case r of- Yield x s -> do- res <- initial- case res of- PRD.IPartial ps ->- return $ Skip $ ParseChunksBuf [x] s [] ps- PRD.IDone pb ->- let next = ParseChunksInit [x] s- in return $ Skip $ ParseChunksYield (Right pb) next- PRD.IError err ->- return- $ Skip- $ ParseChunksYield- (Left (ParseError err))- (ParseChunksInitLeftOver [])- Skip s -> return $ Skip $ ParseChunksInit [] s- Stop -> return Stop-- -- Buffer is not empty, go to buffered processing loop- stepOuter _ (ParseChunksInit src st) = do- res <- initial- case res of- PRD.IPartial ps ->- return $ Skip $ ParseChunksBuf src st [] ps- PRD.IDone pb ->- let next = ParseChunksInit src st- in return $ Skip $ ParseChunksYield (Right pb) next- PRD.IError err ->- return- $ Skip- $ ParseChunksYield- (Left (ParseError err))- (ParseChunksInitLeftOver [])-- -- This is simplified ParseChunksInit- stepOuter _ (ParseChunksInitBuf src) = do- res <- initial- case res of- PRD.IPartial ps ->- return $ Skip $ ParseChunksExtract src [] ps- PRD.IDone pb ->- let next = ParseChunksInitBuf src- in return $ Skip $ ParseChunksYield (Right pb) next- PRD.IError err ->- return- $ Skip- $ ParseChunksYield- (Left (ParseError err))- (ParseChunksInitLeftOver [])-- -- XXX we just discard any leftover input at the end- stepOuter _ (ParseChunksInitLeftOver _) = return Stop-- -- Buffer is empty, process elements from the stream- stepOuter gst (ParseChunksStream st buf pst) = do- r <- step (adaptState gst) st- case r of- Yield x s -> do- pRes <- pstep pst x- case pRes of- PR.Partial 0 pst1 ->- return $ Skip $ ParseChunksStream s [] pst1- PR.Partial n pst1 -> do- assert (n <= length (x:buf)) (return ())- let src0 = Prelude.take n (x:buf)- src = Prelude.reverse src0- return $ Skip $ ParseChunksBuf src s [] pst1- PR.Continue 0 pst1 ->- return $ Skip $ ParseChunksStream s (x:buf) pst1- PR.Continue n pst1 -> do- assert (n <= length (x:buf)) (return ())- let (src0, buf1) = splitAt n (x:buf)- src = Prelude.reverse src0- return $ Skip $ ParseChunksBuf src s buf1 pst1- PR.Done 0 b -> do- return $ Skip $- ParseChunksYield (Right b) (ParseChunksInit [] s)- PR.Done n b -> do- assert (n <= length (x:buf)) (return ())- let src = Prelude.reverse (Prelude.take n (x:buf))- return $ Skip $- ParseChunksYield (Right b) (ParseChunksInit src s)- PR.Error err ->- return- $ Skip- $ ParseChunksYield- (Left (ParseError err))- (ParseChunksInitLeftOver [])- Skip s -> return $ Skip $ ParseChunksStream s buf pst- Stop -> return $ Skip $ ParseChunksStop buf pst-- -- go back to stream processing mode- stepOuter _ (ParseChunksBuf [] s buf pst) =- return $ Skip $ ParseChunksStream s buf pst-- -- buffered processing loop- stepOuter _ (ParseChunksBuf (x:xs) s buf pst) = do- pRes <- pstep pst x- case pRes of- PR.Partial 0 pst1 ->- return $ Skip $ ParseChunksBuf xs s [] pst1- PR.Partial n pst1 -> do- assert (n <= length (x:buf)) (return ())- let src0 = Prelude.take n (x:buf)- src = Prelude.reverse src0 ++ xs- return $ Skip $ ParseChunksBuf src s [] pst1- PR.Continue 0 pst1 ->- return $ Skip $ ParseChunksBuf xs s (x:buf) pst1- PR.Continue n pst1 -> do- assert (n <= length (x:buf)) (return ())- let (src0, buf1) = splitAt n (x:buf)- src = Prelude.reverse src0 ++ xs- return $ Skip $ ParseChunksBuf src s buf1 pst1- PR.Done 0 b ->- return- $ Skip- $ ParseChunksYield (Right b) (ParseChunksInit xs s)- PR.Done n b -> do- assert (n <= length (x:buf)) (return ())- let src = Prelude.reverse (Prelude.take n (x:buf)) ++ xs- return $ Skip- $ ParseChunksYield (Right b) (ParseChunksInit src s)- PR.Error err ->- return- $ Skip- $ ParseChunksYield- (Left (ParseError err))- (ParseChunksInitLeftOver [])-- -- This is simplified ParseChunksBuf- stepOuter _ (ParseChunksExtract [] buf pst) =- return $ Skip $ ParseChunksStop buf pst-- stepOuter _ (ParseChunksExtract (x:xs) buf pst) = do- pRes <- pstep pst x- case pRes of- PR.Partial 0 pst1 ->- return $ Skip $ ParseChunksExtract xs [] pst1- PR.Partial n pst1 -> do- assert (n <= length (x:buf)) (return ())- let src0 = Prelude.take n (x:buf)- src = Prelude.reverse src0 ++ xs- return $ Skip $ ParseChunksExtract src [] pst1- PR.Continue 0 pst1 ->- return $ Skip $ ParseChunksExtract xs (x:buf) pst1- PR.Continue n pst1 -> do- assert (n <= length (x:buf)) (return ())- let (src0, buf1) = splitAt n (x:buf)- src = Prelude.reverse src0 ++ xs- return $ Skip $ ParseChunksExtract src buf1 pst1- PR.Done 0 b ->- return- $ Skip- $ ParseChunksYield (Right b) (ParseChunksInitBuf xs)- PR.Done n b -> do- assert (n <= length (x:buf)) (return ())- let src = Prelude.reverse (Prelude.take n (x:buf)) ++ xs- return- $ Skip- $ ParseChunksYield (Right b) (ParseChunksInitBuf src)- PR.Error err ->- return- $ Skip- $ ParseChunksYield- (Left (ParseError err))- (ParseChunksInitLeftOver [])-- -- This is simplified ParseChunksExtract- stepOuter _ (ParseChunksStop buf pst) = do- pRes <- extract pst- case pRes of- PR.Partial _ _ -> error "Bug: parseMany: Partial in extract"- PR.Continue 0 pst1 ->- return $ Skip $ ParseChunksStop buf pst1- PR.Continue n pst1 -> do- assert (n <= length buf) (return ())- let (src0, buf1) = splitAt n buf- src = Prelude.reverse src0- return $ Skip $ ParseChunksExtract src buf1 pst1- PR.Done 0 b -> do- return $ Skip $- ParseChunksYield (Right b) (ParseChunksInitLeftOver [])- PR.Done n b -> do- assert (n <= length buf) (return ())- let src = Prelude.reverse (Prelude.take n buf)- return $ Skip $- ParseChunksYield (Right b) (ParseChunksInitBuf src)- PR.Error err ->- return- $ Skip- $ ParseChunksYield- (Left (ParseError err))- (ParseChunksInitLeftOver [])-- stepOuter _ (ParseChunksYield a next) = return $ Yield a next---- | Apply a 'Parser' repeatedly on a stream and emit the parsed values in the--- output stream.------ Example:------ >>> s = Stream.fromList [1..10]--- >>> parser = Parser.takeBetween 0 2 Fold.sum--- >>> Stream.fold Fold.toList $ Stream.parseMany parser s--- [Right 3,Right 7,Right 11,Right 15,Right 19]------ This is the streaming equivalent of the 'Streamly.Data.Parser.many' parse--- combinator.------ Known Issues: When the parser fails there is no way to get the remaining--- stream.----{-# INLINE parseMany #-}-parseMany- :: Monad m- => PR.Parser a m b- -> Stream m a- -> Stream m (Either ParseError b)-parseMany = parseManyD---- | Apply a stream of parsers to an input stream and emit the results in the--- output stream.------ /Unimplemented/----{-# INLINE parseSequence #-}-parseSequence- :: -- Monad m =>- Stream m (PR.Parser a m b)- -> Stream m a- -> Stream m b-parseSequence _f _m = undefined---- XXX Change the parser arguments' order---- | @parseManyTill collect test stream@ tries the parser @test@ on the input,--- if @test@ fails it backtracks and tries @collect@, after @collect@ succeeds--- @test@ is tried again and so on. The parser stops when @test@ succeeds. The--- output of @test@ is discarded and the output of @collect@ is emitted in the--- output stream. The parser fails if @collect@ fails.------ /Unimplemented/----{-# INLINE parseManyTill #-}-parseManyTill ::- -- MonadThrow m =>- PR.Parser a m b- -> PR.Parser a m x- -> Stream m a- -> Stream m b-parseManyTill = undefined--{-# ANN type ConcatParseState Fuse #-}-data ConcatParseState c b inpBuf st p m a =- ConcatParseInit inpBuf st p- | ConcatParseInitBuf inpBuf p- | ConcatParseInitLeftOver inpBuf- | forall s. ConcatParseStop- inpBuf (s -> a -> m (PRD.Step s b)) s (s -> m (PRD.Step s b))- | forall s. ConcatParseStream- st inpBuf (s -> a -> m (PRD.Step s b)) s (s -> m (PRD.Step s b))- | forall s. ConcatParseBuf- inpBuf st inpBuf (s -> a -> m (PRD.Step s b)) s (s -> m (PRD.Step s b))- | forall s. ConcatParseExtract- inpBuf inpBuf (s -> a -> m (PRD.Step s b)) s (s -> m (PRD.Step s b))- | ConcatParseYield c (ConcatParseState c b inpBuf st p m a)---- XXX Review the changes-{-# INLINE_NORMAL parseIterateD #-}-parseIterateD- :: Monad m- => (b -> PRD.Parser a m b)- -> b- -> Stream m a- -> Stream m (Either ParseError b)-parseIterateD func seed (Stream step state) =- Stream stepOuter (ConcatParseInit [] state (func seed))-- where-- {-# INLINE_LATE stepOuter #-}- -- Buffer is empty, go to stream processing loop- stepOuter _ (ConcatParseInit [] st (PRD.Parser pstep initial extract)) = do- res <- initial- case res of- PRD.IPartial ps ->- return $ Skip $ ConcatParseStream st [] pstep ps extract- PRD.IDone pb ->- let next = ConcatParseInit [] st (func pb)- in return $ Skip $ ConcatParseYield (Right pb) next- PRD.IError err ->- return- $ Skip- $ ConcatParseYield- (Left (ParseError err))- (ConcatParseInitLeftOver [])-- -- Buffer is not empty, go to buffered processing loop- stepOuter _ (ConcatParseInit src st- (PRD.Parser pstep initial extract)) = do- res <- initial- case res of- PRD.IPartial ps ->- return $ Skip $ ConcatParseBuf src st [] pstep ps extract- PRD.IDone pb ->- let next = ConcatParseInit src st (func pb)- in return $ Skip $ ConcatParseYield (Right pb) next- PRD.IError err ->- return- $ Skip- $ ConcatParseYield- (Left (ParseError err))- (ConcatParseInitLeftOver [])-- -- This is simplified ConcatParseInit- stepOuter _ (ConcatParseInitBuf src- (PRD.Parser pstep initial extract)) = do- res <- initial- case res of- PRD.IPartial ps ->- return $ Skip $ ConcatParseExtract src [] pstep ps extract- PRD.IDone pb ->- let next = ConcatParseInitBuf src (func pb)- in return $ Skip $ ConcatParseYield (Right pb) next- PRD.IError err ->- return- $ Skip- $ ConcatParseYield- (Left (ParseError err))- (ConcatParseInitLeftOver [])-- -- XXX we just discard any leftover input at the end- stepOuter _ (ConcatParseInitLeftOver _) = return Stop-- -- Buffer is empty process elements from the stream- stepOuter gst (ConcatParseStream st buf pstep pst extract) = do- r <- step (adaptState gst) st- case r of- Yield x s -> do- pRes <- pstep pst x- case pRes of- PR.Partial 0 pst1 ->- return $ Skip $ ConcatParseStream s [] pstep pst1 extract- PR.Partial n pst1 -> do- assert (n <= length (x:buf)) (return ())- let src0 = Prelude.take n (x:buf)- src = Prelude.reverse src0- return $ Skip $ ConcatParseBuf src s [] pstep pst1 extract- -- PR.Continue 0 pst1 ->- -- return $ Skip $ ConcatParseStream s (x:buf) pst1- PR.Continue n pst1 -> do- assert (n <= length (x:buf)) (return ())- let (src0, buf1) = splitAt n (x:buf)- src = Prelude.reverse src0- return $ Skip $ ConcatParseBuf src s buf1 pstep pst1 extract- -- XXX Specialize for Stop 0 common case?- PR.Done n b -> do- assert (n <= length (x:buf)) (return ())- let src = Prelude.reverse (Prelude.take n (x:buf))- return $ Skip $- ConcatParseYield (Right b) (ConcatParseInit src s (func b))- PR.Error err ->- return- $ Skip- $ ConcatParseYield- (Left (ParseError err))- (ConcatParseInitLeftOver [])- Skip s -> return $ Skip $ ConcatParseStream s buf pstep pst extract- Stop -> return $ Skip $ ConcatParseStop buf pstep pst extract-- -- go back to stream processing mode- stepOuter _ (ConcatParseBuf [] s buf pstep ps extract) =- return $ Skip $ ConcatParseStream s buf pstep ps extract-- -- buffered processing loop- stepOuter _ (ConcatParseBuf (x:xs) s buf pstep pst extract) = do- pRes <- pstep pst x- case pRes of- PR.Partial 0 pst1 ->- return $ Skip $ ConcatParseBuf xs s [] pstep pst1 extract- PR.Partial n pst1 -> do- assert (n <= length (x:buf)) (return ())- let src0 = Prelude.take n (x:buf)- src = Prelude.reverse src0 ++ xs- return $ Skip $ ConcatParseBuf src s [] pstep pst1 extract- -- PR.Continue 0 pst1 -> return $ Skip $ ConcatParseBuf xs s (x:buf) pst1- PR.Continue n pst1 -> do- assert (n <= length (x:buf)) (return ())- let (src0, buf1) = splitAt n (x:buf)- src = Prelude.reverse src0 ++ xs- return $ Skip $ ConcatParseBuf src s buf1 pstep pst1 extract- -- XXX Specialize for Stop 0 common case?- PR.Done n b -> do- assert (n <= length (x:buf)) (return ())- let src = Prelude.reverse (Prelude.take n (x:buf)) ++ xs- return $ Skip $ ConcatParseYield (Right b)- (ConcatParseInit src s (func b))- PR.Error err ->- return- $ Skip- $ ConcatParseYield- (Left (ParseError err))- (ConcatParseInitLeftOver [])-- -- This is simplified ConcatParseBuf- stepOuter _ (ConcatParseExtract [] buf pstep pst extract) =- return $ Skip $ ConcatParseStop buf pstep pst extract-- stepOuter _ (ConcatParseExtract (x:xs) buf pstep pst extract) = do- pRes <- pstep pst x- case pRes of- PR.Partial 0 pst1 ->- return $ Skip $ ConcatParseExtract xs [] pstep pst1 extract- PR.Partial n pst1 -> do- assert (n <= length (x:buf)) (return ())- let src0 = Prelude.take n (x:buf)- src = Prelude.reverse src0 ++ xs- return $ Skip $ ConcatParseExtract src [] pstep pst1 extract- PR.Continue 0 pst1 ->- return $ Skip $ ConcatParseExtract xs (x:buf) pstep pst1 extract- PR.Continue n pst1 -> do- assert (n <= length (x:buf)) (return ())- let (src0, buf1) = splitAt n (x:buf)- src = Prelude.reverse src0 ++ xs- return $ Skip $ ConcatParseExtract src buf1 pstep pst1 extract- PR.Done 0 b ->- return $ Skip $ ConcatParseYield (Right b) (ConcatParseInitBuf xs (func b))- PR.Done n b -> do- assert (n <= length (x:buf)) (return ())- let src = Prelude.reverse (Prelude.take n (x:buf)) ++ xs- return $ Skip $ ConcatParseYield (Right b) (ConcatParseInitBuf src (func b))- PR.Error err ->- return- $ Skip- $ ConcatParseYield- (Left (ParseError err))- (ConcatParseInitLeftOver [])-- -- This is simplified ConcatParseExtract- stepOuter _ (ConcatParseStop buf pstep pst extract) = do- pRes <- extract pst- case pRes of- PR.Partial _ _ -> error "Bug: parseIterate: Partial in extract"- PR.Continue 0 pst1 ->- return $ Skip $ ConcatParseStop buf pstep pst1 extract- PR.Continue n pst1 -> do- assert (n <= length buf) (return ())- let (src0, buf1) = splitAt n buf- src = Prelude.reverse src0- return $ Skip $ ConcatParseExtract src buf1 pstep pst1 extract- PR.Done 0 b -> do- return $ Skip $- ConcatParseYield (Right b) (ConcatParseInitLeftOver [])- PR.Done n b -> do- assert (n <= length buf) (return ())- let src = Prelude.reverse (Prelude.take n buf)- return $ Skip $- ConcatParseYield (Right b) (ConcatParseInitBuf src (func b))- PR.Error err ->- return- $ Skip- $ ConcatParseYield- (Left (ParseError err))- (ConcatParseInitLeftOver [])-- stepOuter _ (ConcatParseYield a next) = return $ Yield a next---- | Iterate a parser generating function on a stream. The initial value @b@ is--- used to generate the first parser, the parser is applied on the stream and--- the result is used to generate the next parser and so on.------ >>> import Data.Monoid (Sum(..))--- >>> s = Stream.fromList [1..10]--- >>> Stream.fold Fold.toList $ fmap getSum $ Stream.catRights $ Stream.parseIterate (\b -> Parser.takeBetween 0 2 (Fold.sconcat b)) (Sum 0) $ fmap Sum s--- [3,10,21,36,55,55]------ This is the streaming equivalent of monad like sequenced application of--- parsers where next parser is dependent on the previous parser.------ /Pre-release/----{-# INLINE parseIterate #-}-parseIterate- :: Monad m- => (b -> PR.Parser a m b)- -> b- -> Stream m a- -> Stream m (Either ParseError b)-parseIterate = parseIterateD----------------------------------------------------------------------------------- Grouping---------------------------------------------------------------------------------data GroupByState st fs a b- = GroupingInit st- | GroupingDo st !fs- | GroupingInitWith st !a- | GroupingDoWith st !fs !a- | GroupingYield !b (GroupByState st fs a b)- | GroupingDone--{-# INLINE_NORMAL groupsBy #-}-groupsBy :: Monad m- => (a -> a -> Bool)- -> Fold m a b- -> Stream m a- -> Stream m b-{--groupsBy eq fld = parseMany (PRD.groupBy eq fld)--}-groupsBy cmp (Fold fstep initial done) (Stream step state) =- Stream stepOuter (GroupingInit state)-- where-- {-# INLINE_LATE stepOuter #-}- stepOuter _ (GroupingInit st) = do- -- XXX Note that if the stream stops without yielding a single element- -- in the group we discard the "initial" effect.- res <- initial- return- $ case res of- FL.Partial s -> Skip $ GroupingDo st s- FL.Done b -> Yield b $ GroupingInit st- stepOuter gst (GroupingDo st fs) = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- r <- fstep fs x- case r of- FL.Partial fs1 -> go SPEC x s fs1- FL.Done b -> return $ Yield b (GroupingInit s)- Skip s -> return $ Skip $ GroupingDo s fs- 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- r <- fstep acc x- case r of- FL.Partial fs1 -> go SPEC prev s fs1- FL.Done b -> return $ Yield b (GroupingInit s)- else do- r <- done acc- return $ Yield r (GroupingInitWith s x)- Skip s -> go SPEC prev s acc- Stop -> done acc >>= \r -> return $ Yield r GroupingDone- stepOuter _ (GroupingInitWith st x) = do- res <- initial- return- $ case res of- FL.Partial s -> Skip $ GroupingDoWith st s x- FL.Done b -> Yield b $ GroupingInitWith st x- stepOuter gst (GroupingDoWith st fs prev) = do- res <- fstep fs prev- case res of- FL.Partial fs1 -> go SPEC st fs1- FL.Done b -> return $ Yield b (GroupingInit st)-- 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- r <- fstep acc x- case r of- FL.Partial fs1 -> go SPEC s fs1- FL.Done b -> return $ Yield b (GroupingInit s)- else do- r <- done acc- return $ Yield r (GroupingInitWith s x)- Skip s -> go SPEC s acc- Stop -> done acc >>= \r -> return $ Yield r GroupingDone- stepOuter _ (GroupingYield _ _) = error "groupsBy: Unreachable"- stepOuter _ GroupingDone = return Stop--{-# INLINE_NORMAL groupsRollingBy #-}-groupsRollingBy :: Monad m- => (a -> a -> Bool)- -> Fold m a b- -> Stream m a- -> Stream m b-{--groupsRollingBy eq fld = parseMany (PRD.groupByRolling eq fld)--}-groupsRollingBy cmp (Fold fstep initial done) (Stream step state) =- Stream stepOuter (GroupingInit state)-- where-- {-# INLINE_LATE stepOuter #-}- stepOuter _ (GroupingInit st) = do- -- XXX Note that if the stream stops without yielding a single element- -- in the group we discard the "initial" effect.- res <- initial- return- $ case res of- FL.Partial fs -> Skip $ GroupingDo st fs- FL.Done fb -> Yield fb $ GroupingInit st- stepOuter gst (GroupingDo st fs) = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- r <- fstep fs x- case r of- FL.Partial fs1 -> go SPEC x s fs1- FL.Done fb -> return $ Yield fb (GroupingInit s)- Skip s -> return $ Skip $ GroupingDo s fs- 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- r <- fstep acc x- case r of- FL.Partial fs1 -> go SPEC x s fs1- FL.Done b -> return $ Yield b (GroupingInit s)- else do- r <- done acc- return $ Yield r (GroupingInitWith s x)- Skip s -> go SPEC prev s acc- Stop -> done acc >>= \r -> return $ Yield r GroupingDone- stepOuter _ (GroupingInitWith st x) = do- res <- initial- return- $ case res of- FL.Partial s -> Skip $ GroupingDoWith st s x- FL.Done b -> Yield b $ GroupingInitWith st x- stepOuter gst (GroupingDoWith st fs previous) = do- res <- fstep fs previous- case res of- FL.Partial s -> go SPEC previous st s- FL.Done b -> return $ Yield b (GroupingInit st)-- where-- -- XXX GHC: groupsBy has one less parameter in this go loop and it- -- fuses. However, groupsRollingBy does not fuse, removing the prev- -- parameter makes it fuse. Something needs to be fixed in GHC. The- -- workaround for this is noted in the comments below.- go !_ prev !stt !acc = do- res <- step (adaptState gst) stt- case res of- Yield x s -> do- if cmp prev x- then do- r <- fstep acc x- case r of- FL.Partial fs1 -> go SPEC x s fs1- FL.Done b -> return $ Yield b (GroupingInit st)- else do- {-- r <- done acc- return $ Yield r (GroupingInitWith s x)- -}- -- The code above does not let groupBy fuse. We use the- -- alternative code below instead. Instead of jumping- -- to GroupingInitWith state, we unroll the code of- -- GroupingInitWith state here to help GHC with stream- -- fusion.- result <- initial- r <- done acc- return- $ Yield r- $ case result of- FL.Partial fsi -> GroupingDoWith s fsi x- FL.Done b -> GroupingYield b (GroupingInit s)- Skip s -> go SPEC prev s acc- Stop -> done acc >>= \r -> return $ Yield r GroupingDone- stepOuter _ (GroupingYield r next) = return $ Yield r next- stepOuter _ GroupingDone = return Stop----------------------------------------------------------------------------------- Splitting - by a predicate---------------------------------------------------------------------------------data WordsByState st fs b- = WordsByInit st- | WordsByDo st !fs- | WordsByDone- | WordsByYield !b (WordsByState st fs b)--{-# INLINE_NORMAL wordsBy #-}-wordsBy :: Monad m => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b-wordsBy predicate (Fold fstep initial done) (Stream step state) =- Stream stepOuter (WordsByInit state)-- where-- {-# INLINE_LATE stepOuter #-}- stepOuter _ (WordsByInit st) = do- res <- initial- return- $ case res of- FL.Partial s -> Skip $ WordsByDo st s- FL.Done b -> Yield b (WordsByInit st)-- stepOuter gst (WordsByDo st fs) = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- if predicate x- then do- resi <- initial- return- $ case resi of- FL.Partial fs1 -> Skip $ WordsByDo s fs1- FL.Done b -> Yield b (WordsByInit s)- else do- r <- fstep fs x- case r of- FL.Partial fs1 -> go SPEC s fs1- FL.Done b -> return $ Yield b (WordsByInit s)- Skip s -> return $ Skip $ WordsByDo s fs- Stop -> return Stop-- where-- go !_ stt !acc = do- res <- step (adaptState gst) stt- case res of- Yield x s -> do- if predicate x- then do- {-- r <- done acc- return $ Yield r (WordsByInit s)- -}- -- The above code does not fuse well. Need to check why- -- GHC is not able to simplify it well. Using the code- -- below, instead of jumping through the WordsByInit- -- state always, we directly go to WordsByDo state in- -- the common case of Partial.- resi <- initial- r <- done acc- return- $ Yield r- $ case resi of- FL.Partial fs1 -> WordsByDo s fs1- FL.Done b -> WordsByYield b (WordsByInit s)- else do- r <- fstep acc x- case r of- FL.Partial fs1 -> go SPEC s fs1- FL.Done b -> return $ Yield b (WordsByInit s)- Skip s -> go SPEC s acc- Stop -> done acc >>= \r -> return $ Yield r WordsByDone-- stepOuter _ WordsByDone = return Stop-- stepOuter _ (WordsByYield b next) = return $ Yield b next----------------------------------------------------------------------------------- Splitting on a sequence----------------------------------------------------------------------------------- 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- }--}---- XXX using "fs" as the last arg in Constructors may simplify the code a bit,--- because we can use the constructor directly without having to create "jump"--- functions.-{-# ANN type SplitOnSeqState Fuse #-}-data SplitOnSeqState rb rh ck w fs s b x =- SplitOnSeqInit- | SplitOnSeqYield b (SplitOnSeqState rb rh ck w fs s b x)- | SplitOnSeqDone-- | SplitOnSeqEmpty !fs s-- | SplitOnSeqSingle !fs s x-- | SplitOnSeqWordInit !fs s- | SplitOnSeqWordLoop !w s !fs- | SplitOnSeqWordDone Int !fs !w-- | SplitOnSeqKRInit Int !fs s rb !rh- | SplitOnSeqKRLoop fs s rb !rh !ck- | SplitOnSeqKRCheck fs s rb !rh- | SplitOnSeqKRDone Int !fs rb !rh-- | SplitOnSeqReinit (fs -> SplitOnSeqState rb rh ck w fs s b x)--{-# INLINE_NORMAL splitOnSeq #-}-splitOnSeq- :: forall m a b. (MonadIO m, Storable a, Unbox a, Enum a, Eq a)- => Array a- -> Fold m a b- -> Stream m a- -> Stream m b-splitOnSeq patArr (Fold fstep initial done) (Stream step state) =- Stream stepOuter SplitOnSeqInit-- where-- patLen = A.length patArr- maxIndex = patLen - 1- elemBits = SIZE_OF(a) * 8-- -- For word pattern case- wordMask :: Word- wordMask = (1 `shiftL` (elemBits * patLen)) - 1-- elemMask :: Word- elemMask = (1 `shiftL` elemBits) - 1-- wordPat :: Word- wordPat = wordMask .&. A.foldl' addToWord 0 patArr-- addToWord wd a = (wd `shiftL` elemBits) .|. fromIntegral (fromEnum a)-- -- For Rabin-Karp search- 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-- skip = return . Skip-- nextAfterInit nextGen stepRes =- case stepRes of- FL.Partial s -> nextGen s- FL.Done b -> SplitOnSeqYield b (SplitOnSeqReinit nextGen)-- {-# INLINE yieldProceed #-}- yieldProceed nextGen fs =- initial >>= skip . SplitOnSeqYield fs . nextAfterInit nextGen-- {-# INLINE_LATE stepOuter #-}- stepOuter _ SplitOnSeqInit = do- res <- initial- case res of- FL.Partial acc ->- if patLen == 0- then return $ Skip $ SplitOnSeqEmpty acc state- else if patLen == 1- then do- pat <- liftIO $ A.unsafeIndexIO 0 patArr- return $ Skip $ SplitOnSeqSingle acc state pat- else if SIZE_OF(a) * patLen- <= sizeOf (Proxy :: Proxy Word)- then return $ Skip $ SplitOnSeqWordInit acc state- else do- (rb, rhead) <- liftIO $ RB.new patLen- skip $ SplitOnSeqKRInit 0 acc state rb rhead- FL.Done b -> skip $ SplitOnSeqYield b SplitOnSeqInit-- stepOuter _ (SplitOnSeqYield x next) = return $ Yield x next-- ---------------------------- -- Checkpoint- ----------------------------- stepOuter _ (SplitOnSeqReinit nextGen) =- initial >>= skip . nextAfterInit nextGen-- ---------------------------- -- Empty pattern- ----------------------------- stepOuter gst (SplitOnSeqEmpty acc st) = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- r <- fstep acc x- b1 <-- case r of- FL.Partial acc1 -> done acc1- FL.Done b -> return b- let jump c = SplitOnSeqEmpty c s- in yieldProceed jump b1- Skip s -> skip (SplitOnSeqEmpty acc s)- Stop -> return Stop-- ------------------ -- Done- ------------------- stepOuter _ SplitOnSeqDone = return Stop-- ------------------ -- Single Pattern- ------------------- stepOuter gst (SplitOnSeqSingle fs st pat) = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- let jump c = SplitOnSeqSingle c s pat- if pat == x- then done fs >>= yieldProceed jump- else do- r <- fstep fs x- case r of- FL.Partial fs1 -> skip $ jump fs1- FL.Done b -> yieldProceed jump b- Skip s -> return $ Skip $ SplitOnSeqSingle fs s pat- Stop -> do- r <- done fs- return $ Skip $ SplitOnSeqYield r SplitOnSeqDone-- ---------------------------- -- Short Pattern - Shift Or- ----------------------------- stepOuter _ (SplitOnSeqWordDone 0 fs _) = do- r <- done fs- skip $ SplitOnSeqYield r SplitOnSeqDone- stepOuter _ (SplitOnSeqWordDone n fs wrd) = do- let old = elemMask .&. (wrd `shiftR` (elemBits * (n - 1)))- r <- fstep fs (toEnum $ fromIntegral old)- case r of- FL.Partial fs1 -> skip $ SplitOnSeqWordDone (n - 1) fs1 wrd- FL.Done b -> do- let jump c = SplitOnSeqWordDone (n - 1) c wrd- yieldProceed jump b-- stepOuter gst (SplitOnSeqWordInit fs st0) =- go SPEC 0 0 st0-- where-- {-# INLINE go #-}- go !_ !idx !wrd !st = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- let wrd1 = addToWord wrd x- if idx == maxIndex- then do- if wrd1 .&. wordMask == wordPat- then do- let jump c = SplitOnSeqWordInit c s- done fs >>= yieldProceed jump- else skip $ SplitOnSeqWordLoop wrd1 s fs- else go SPEC (idx + 1) wrd1 s- Skip s -> go SPEC idx wrd s- Stop -> do- if idx /= 0- then skip $ SplitOnSeqWordDone idx fs wrd- else do- r <- done fs- skip $ SplitOnSeqYield r SplitOnSeqDone-- stepOuter gst (SplitOnSeqWordLoop wrd0 st0 fs0) =- go SPEC wrd0 st0 fs0-- where-- {-# INLINE go #-}- go !_ !wrd !st !fs = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- let jump c = SplitOnSeqWordInit c s- wrd1 = addToWord wrd x- old = (wordMask .&. wrd)- `shiftR` (elemBits * (patLen - 1))- r <- fstep fs (toEnum $ fromIntegral old)- case r of- FL.Partial fs1 -> do- if wrd1 .&. wordMask == wordPat- then done fs1 >>= yieldProceed jump- else go SPEC wrd1 s fs1- FL.Done b -> yieldProceed jump b- Skip s -> go SPEC wrd s fs- Stop -> skip $ SplitOnSeqWordDone patLen fs wrd-- -------------------------------- -- General Pattern - Karp Rabin- --------------------------------- stepOuter gst (SplitOnSeqKRInit idx fs st rb rh) = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- rh1 <- liftIO $ RB.unsafeInsert rb rh x- if idx == maxIndex- then do- let fld = RB.unsafeFoldRing (RB.ringBound rb)- let !ringHash = fld addCksum 0 rb- if ringHash == patHash- then skip $ SplitOnSeqKRCheck fs s rb rh1- else skip $ SplitOnSeqKRLoop fs s rb rh1 ringHash- else skip $ SplitOnSeqKRInit (idx + 1) fs s rb rh1- Skip s -> skip $ SplitOnSeqKRInit idx fs s rb rh- Stop -> do- skip $ SplitOnSeqKRDone idx fs rb (RB.startOf rb)-- -- XXX The recursive "go" is more efficient than the state based recursion- -- code commented out below. Perhaps its more efficient because of- -- factoring out "rb" outside the loop.- --- stepOuter gst (SplitOnSeqKRLoop fs0 st0 rb rh0 cksum0) =- go SPEC fs0 st0 rh0 cksum0-- where-- go !_ !fs !st !rh !cksum = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- old <- liftIO $ peek rh- let cksum1 = deltaCksum cksum old x- r <- fstep fs old- case r of- FL.Partial fs1 -> do- rh1 <- liftIO (RB.unsafeInsert rb rh x)- if cksum1 == patHash- then skip $ SplitOnSeqKRCheck fs1 s rb rh1- else go SPEC fs1 s rh1 cksum1- FL.Done b -> do- let rst = RB.startOf rb- jump c = SplitOnSeqKRInit 0 c s rb rst- yieldProceed jump b- Skip s -> go SPEC fs s rh cksum- Stop -> skip $ SplitOnSeqKRDone patLen fs rb rh-- -- XXX The following code is 5 times slower compared to the recursive loop- -- based code above. Need to investigate why. One possibility is that the- -- go loop above does not thread around the ring buffer (rb). This code may- -- be causing the state to bloat and getting allocated on each iteration.- -- We can check the cmm/asm code to confirm. If so a good GHC solution to- -- such problem is needed. One way to avoid this could be to use unboxed- -- mutable state?- {-- stepOuter gst (SplitOnSeqKRLoop fs st rb rh cksum) = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- old <- liftIO $ peek rh- let cksum1 = deltaCksum cksum old x- fs1 <- fstep fs old- if (cksum1 == patHash)- then do- r <- done fs1- skip $ SplitOnSeqYield r $ SplitOnSeqKRInit 0 s rb rh- else do- rh1 <- liftIO (RB.unsafeInsert rb rh x)- skip $ SplitOnSeqKRLoop fs1 s rb rh1 cksum1- Skip s -> skip $ SplitOnSeqKRLoop fs s rb rh cksum- Stop -> skip $ SplitOnSeqKRDone patLen fs rb rh- -}-- stepOuter _ (SplitOnSeqKRCheck fs st rb rh) = do- if RB.unsafeEqArray rb rh patArr- then do- r <- done fs- let rst = RB.startOf rb- jump c = SplitOnSeqKRInit 0 c st rb rst- yieldProceed jump r- else skip $ SplitOnSeqKRLoop fs st rb rh patHash-- stepOuter _ (SplitOnSeqKRDone 0 fs _ _) = do- r <- done fs- skip $ SplitOnSeqYield r SplitOnSeqDone- stepOuter _ (SplitOnSeqKRDone n fs rb rh) = do- old <- liftIO $ peek rh- let rh1 = RB.advance rb rh- r <- fstep fs old- case r of- FL.Partial fs1 -> skip $ SplitOnSeqKRDone (n - 1) fs1 rb rh1- FL.Done b -> do- let jump c = SplitOnSeqKRDone (n - 1) c rb rh1- yieldProceed jump b--{-# ANN type SplitOnSuffixSeqState Fuse #-}-data SplitOnSuffixSeqState rb rh ck w fs s b x =- SplitOnSuffixSeqInit- | SplitOnSuffixSeqYield b (SplitOnSuffixSeqState rb rh ck w fs s b x)- | SplitOnSuffixSeqDone-- | SplitOnSuffixSeqEmpty !fs s-- | SplitOnSuffixSeqSingleInit !fs s x- | SplitOnSuffixSeqSingle !fs s x-- | SplitOnSuffixSeqWordInit !fs s- | SplitOnSuffixSeqWordLoop !w s !fs- | SplitOnSuffixSeqWordDone Int !fs !w-- | SplitOnSuffixSeqKRInit Int !fs s rb !rh- | SplitOnSuffixSeqKRInit1 !fs s rb !rh- | SplitOnSuffixSeqKRLoop fs s rb !rh !ck- | SplitOnSuffixSeqKRCheck fs s rb !rh- | SplitOnSuffixSeqKRDone Int !fs rb !rh-- | SplitOnSuffixSeqReinit- (fs -> SplitOnSuffixSeqState rb rh ck w fs s b x)--{-# INLINE_NORMAL splitOnSuffixSeq #-}-splitOnSuffixSeq- :: forall m a b. (MonadIO m, Storable a, Unbox a, Enum a, Eq a)- => Bool- -> Array a- -> Fold m a b- -> Stream m a- -> Stream m b-splitOnSuffixSeq withSep patArr (Fold fstep initial done) (Stream step state) =- Stream stepOuter SplitOnSuffixSeqInit-- where-- patLen = A.length patArr- maxIndex = patLen - 1- elemBits = SIZE_OF(a) * 8-- -- For word pattern case- wordMask :: Word- wordMask = (1 `shiftL` (elemBits * patLen)) - 1-- elemMask :: Word- elemMask = (1 `shiftL` elemBits) - 1-- wordPat :: Word- wordPat = wordMask .&. A.foldl' addToWord 0 patArr-- addToWord wd a = (wd `shiftL` elemBits) .|. fromIntegral (fromEnum a)-- nextAfterInit nextGen stepRes =- case stepRes of- FL.Partial s -> nextGen s- FL.Done b ->- SplitOnSuffixSeqYield b (SplitOnSuffixSeqReinit nextGen)-- {-# INLINE yieldProceed #-}- yieldProceed nextGen fs =- initial >>= skip . SplitOnSuffixSeqYield fs . nextAfterInit nextGen-- -- For single element pattern case- {-# INLINE processYieldSingle #-}- processYieldSingle pat x s fs = do- let jump c = SplitOnSuffixSeqSingleInit c s pat- if pat == x- then do- r <- if withSep then fstep fs x else return $ FL.Partial fs- b1 <-- case r of- FL.Partial fs1 -> done fs1- FL.Done b -> return b- yieldProceed jump b1- else do- r <- fstep fs x- case r of- FL.Partial fs1 -> skip $ SplitOnSuffixSeqSingle fs1 s pat- FL.Done b -> yieldProceed jump b-- -- For Rabin-Karp search- 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-- skip = return . Skip-- {-# INLINE_LATE stepOuter #-}- stepOuter _ SplitOnSuffixSeqInit = do- res <- initial- case res of- FL.Partial fs ->- if patLen == 0- then skip $ SplitOnSuffixSeqEmpty fs state- else if patLen == 1- then do- pat <- liftIO $ A.unsafeIndexIO 0 patArr- skip $ SplitOnSuffixSeqSingleInit fs state pat- else if SIZE_OF(a) * patLen- <= sizeOf (Proxy :: Proxy Word)- then skip $ SplitOnSuffixSeqWordInit fs state- else do- (rb, rhead) <- liftIO $ RB.new patLen- skip $ SplitOnSuffixSeqKRInit 0 fs state rb rhead- FL.Done fb -> skip $ SplitOnSuffixSeqYield fb SplitOnSuffixSeqInit-- stepOuter _ (SplitOnSuffixSeqYield x next) = return $ Yield x next-- ---------------------------- -- Reinit- ----------------------------- stepOuter _ (SplitOnSuffixSeqReinit nextGen) =- initial >>= skip . nextAfterInit nextGen-- ---------------------------- -- Empty pattern- ----------------------------- stepOuter gst (SplitOnSuffixSeqEmpty acc st) = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- let jump c = SplitOnSuffixSeqEmpty c s- r <- fstep acc x- b1 <-- case r of- FL.Partial fs -> done fs- FL.Done b -> return b- yieldProceed jump b1- Skip s -> skip (SplitOnSuffixSeqEmpty acc s)- Stop -> return Stop-- ------------------ -- Done- ------------------- stepOuter _ SplitOnSuffixSeqDone = return Stop-- ------------------ -- Single Pattern- ------------------- stepOuter gst (SplitOnSuffixSeqSingleInit fs st pat) = do- res <- step (adaptState gst) st- case res of- Yield x s -> processYieldSingle pat x s fs- Skip s -> skip $ SplitOnSuffixSeqSingleInit fs s pat- Stop -> return Stop-- stepOuter gst (SplitOnSuffixSeqSingle fs st pat) = do- res <- step (adaptState gst) st- case res of- Yield x s -> processYieldSingle pat x s fs- Skip s -> skip $ SplitOnSuffixSeqSingle fs s pat- Stop -> do- r <- done fs- skip $ SplitOnSuffixSeqYield r SplitOnSuffixSeqDone-- ---------------------------- -- Short Pattern - Shift Or- ----------------------------- stepOuter _ (SplitOnSuffixSeqWordDone 0 fs _) = do- r <- done fs- skip $ SplitOnSuffixSeqYield r SplitOnSuffixSeqDone- stepOuter _ (SplitOnSuffixSeqWordDone n fs wrd) = do- let old = elemMask .&. (wrd `shiftR` (elemBits * (n - 1)))- r <- fstep fs (toEnum $ fromIntegral old)- case r of- FL.Partial fs1 -> skip $ SplitOnSuffixSeqWordDone (n - 1) fs1 wrd- FL.Done b -> do- let jump c = SplitOnSuffixSeqWordDone (n - 1) c wrd- yieldProceed jump b-- stepOuter gst (SplitOnSuffixSeqWordInit fs0 st0) = do- res <- step (adaptState gst) st0- case res of- Yield x s -> do- let wrd = addToWord 0 x- r <- if withSep then fstep fs0 x else return $ FL.Partial fs0- case r of- FL.Partial fs1 -> go SPEC 1 wrd s fs1- FL.Done b -> do- let jump c = SplitOnSuffixSeqWordInit c s- yieldProceed jump b- Skip s -> skip (SplitOnSuffixSeqWordInit fs0 s)- Stop -> return Stop-- where-- {-# INLINE go #-}- go !_ !idx !wrd !st !fs = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- let jump c = SplitOnSuffixSeqWordInit c s- let wrd1 = addToWord wrd x- r <- if withSep then fstep fs x else return $ FL.Partial fs- case r of- FL.Partial fs1 ->- if idx /= maxIndex- then go SPEC (idx + 1) wrd1 s fs1- else if wrd1 .&. wordMask /= wordPat- then skip $ SplitOnSuffixSeqWordLoop wrd1 s fs1- else do done fs >>= yieldProceed jump- FL.Done b -> yieldProceed jump b- Skip s -> go SPEC idx wrd s fs- Stop -> skip $ SplitOnSuffixSeqWordDone idx fs wrd-- stepOuter gst (SplitOnSuffixSeqWordLoop wrd0 st0 fs0) =- go SPEC wrd0 st0 fs0-- where-- {-# INLINE go #-}- go !_ !wrd !st !fs = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- let jump c = SplitOnSuffixSeqWordInit c s- wrd1 = addToWord wrd x- old = (wordMask .&. wrd)- `shiftR` (elemBits * (patLen - 1))- r <-- if withSep- then fstep fs x- else fstep fs (toEnum $ fromIntegral old)- case r of- FL.Partial fs1 ->- if wrd1 .&. wordMask == wordPat- then done fs1 >>= yieldProceed jump- else go SPEC wrd1 s fs1- FL.Done b -> yieldProceed jump b- Skip s -> go SPEC wrd s fs- Stop ->- if wrd .&. wordMask == wordPat- then return Stop- else if withSep- then do- r <- done fs- skip $ SplitOnSuffixSeqYield r SplitOnSuffixSeqDone- else skip $ SplitOnSuffixSeqWordDone patLen fs wrd-- -------------------------------- -- General Pattern - Karp Rabin- --------------------------------- stepOuter gst (SplitOnSuffixSeqKRInit idx0 fs st0 rb rh0) = do- res <- step (adaptState gst) st0- case res of- Yield x s -> do- rh1 <- liftIO $ RB.unsafeInsert rb rh0 x- r <- if withSep then fstep fs x else return $ FL.Partial fs- case r of- FL.Partial fs1 ->- skip $ SplitOnSuffixSeqKRInit1 fs1 s rb rh1- FL.Done b -> do- let rst = RB.startOf rb- jump c = SplitOnSuffixSeqKRInit 0 c s rb rst- yieldProceed jump b- Skip s -> skip $ SplitOnSuffixSeqKRInit idx0 fs s rb rh0- Stop -> return Stop-- stepOuter gst (SplitOnSuffixSeqKRInit1 fs0 st0 rb rh0) = do- go SPEC 1 rh0 st0 fs0-- where-- go !_ !idx !rh st !fs = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- rh1 <- liftIO (RB.unsafeInsert rb rh x)- r <- if withSep then fstep fs x else return $ FL.Partial fs- case r of- FL.Partial fs1 ->- if idx /= maxIndex- then go SPEC (idx + 1) rh1 s fs1- else skip $- let fld = RB.unsafeFoldRing (RB.ringBound rb)- !ringHash = fld addCksum 0 rb- in if ringHash == patHash- then SplitOnSuffixSeqKRCheck fs1 s rb rh1- else SplitOnSuffixSeqKRLoop- fs1 s rb rh1 ringHash- FL.Done b -> do- let rst = RB.startOf rb- jump c = SplitOnSuffixSeqKRInit 0 c s rb rst- yieldProceed jump b- Skip s -> go SPEC idx rh s fs- 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 if withSep- then do- r <- done fs- skip $ SplitOnSuffixSeqYield r SplitOnSuffixSeqDone- else skip $ SplitOnSuffixSeqKRDone idx fs rb (RB.startOf rb)-- stepOuter gst (SplitOnSuffixSeqKRLoop fs0 st0 rb rh0 cksum0) =- go SPEC fs0 st0 rh0 cksum0-- where-- go !_ !fs !st !rh !cksum = do- res <- step (adaptState gst) st- case res of- Yield x s -> do- old <- liftIO $ peek rh- rh1 <- liftIO (RB.unsafeInsert rb rh x)- let cksum1 = deltaCksum cksum old x- r <- if withSep then fstep fs x else fstep fs old- case r of- FL.Partial fs1 ->- if cksum1 /= patHash- then go SPEC fs1 s rh1 cksum1- else skip $ SplitOnSuffixSeqKRCheck fs1 s rb rh1- FL.Done b -> do- let rst = RB.startOf rb- jump c = SplitOnSuffixSeqKRInit 0 c s rb rst- yieldProceed jump b- Skip s -> go SPEC fs s rh cksum- Stop ->- if RB.unsafeEqArray rb rh patArr- then return Stop- else if withSep- then do- r <- done fs- skip $ SplitOnSuffixSeqYield r SplitOnSuffixSeqDone- else skip $ SplitOnSuffixSeqKRDone patLen fs rb rh-- stepOuter _ (SplitOnSuffixSeqKRCheck fs st rb rh) = do- if RB.unsafeEqArray rb rh patArr- then do- r <- done fs- let rst = RB.startOf rb- jump c = SplitOnSuffixSeqKRInit 0 c st rb rst- yieldProceed jump r- else skip $ SplitOnSuffixSeqKRLoop fs st rb rh patHash-- stepOuter _ (SplitOnSuffixSeqKRDone 0 fs _ _) = do- r <- done fs- skip $ SplitOnSuffixSeqYield r SplitOnSuffixSeqDone- stepOuter _ (SplitOnSuffixSeqKRDone n fs rb rh) = do- old <- liftIO $ peek rh- let rh1 = RB.advance rb rh- r <- fstep fs old- case r of- FL.Partial fs1 -> skip $ SplitOnSuffixSeqKRDone (n - 1) fs1 rb rh1- FL.Done b -> do- let jump c = SplitOnSuffixSeqKRDone (n - 1) c rb rh1- yieldProceed jump b---- Implement this as a fold or a parser instead.--- This can be implemented easily using Rabin Karp--- | Split post any one of the given patterns.------ /Unimplemented/-{-# INLINE splitOnSuffixSeqAny #-}-splitOnSuffixSeqAny :: -- (Monad m, Unboxed a, Integral a) =>- [Array a] -> Fold m a b -> Stream m a -> Stream m b-splitOnSuffixSeqAny _subseq _f _m = undefined- -- D.fromStreamD $ D.splitPostAny f subseq (D.toStreamD m)---- | Split on a prefixed separator element, dropping the separator. The--- supplied 'Fold' is applied on the split segments.------ @--- > splitOnPrefix' p xs = Stream.toList $ Stream.splitOnPrefix p (Fold.toList) (Stream.fromList xs)--- > splitOnPrefix' (== '.') ".a.b"--- ["a","b"]--- @------ An empty stream results in an empty output stream:--- @--- > splitOnPrefix' (== '.') ""--- []--- @------ An empty segment consisting of only a prefix is folded to the default output--- of the fold:------ @--- > splitOnPrefix' (== '.') "."--- [""]------ > splitOnPrefix' (== '.') ".a.b."--- ["a","b",""]------ > splitOnPrefix' (== '.') ".a..b"--- ["a","","b"]------ @------ A prefix is optional at the beginning of the stream:------ @--- > splitOnPrefix' (== '.') "a"--- ["a"]------ > splitOnPrefix' (== '.') "a.b"--- ["a","b"]--- @------ 'splitOnPrefix' is an inverse of 'intercalatePrefix' with a single element:------ > Stream.intercalatePrefix (Stream.fromPure '.') Unfold.fromList . Stream.splitOnPrefix (== '.') Fold.toList === id------ Assuming the input stream does not contain the separator:------ > Stream.splitOnPrefix (== '.') Fold.toList . Stream.intercalatePrefix (Stream.fromPure '.') Unfold.fromList === id------ /Unimplemented/-{-# INLINE splitOnPrefix #-}-splitOnPrefix :: -- (IsStream t, MonadCatch m) =>- (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b-splitOnPrefix _predicate _f = undefined- -- parseMany (Parser.sliceBeginBy predicate f)---- 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]--- > [[],[]]---- This can be implemented easily using Rabin Karp--- | Split on any one of the given patterns.------ /Unimplemented/----{-# INLINE splitOnAny #-}-splitOnAny :: -- (Monad m, Unboxed a, Integral a) =>- [Array a] -> Fold m a b -> Stream m a -> Stream m b-splitOnAny _subseq _f _m =- undefined -- D.fromStreamD $ D.splitOnAny f subseq (D.toStreamD m)----------------------------------------------------------------------------------- Nested Container Transformation---------------------------------------------------------------------------------{-# ANN type SplitState Fuse #-}-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----------------------------------------------------------------------------------- Trimming----------------------------------------------------------------------------------- | Drop prefix from the input stream if present.------ Space: @O(1)@------ /Unimplemented/-{-# INLINE dropPrefix #-}-dropPrefix ::- -- (Monad m, Eq a) =>- Stream m a -> Stream m a -> Stream m a-dropPrefix = error "Not implemented yet!"---- | Drop all matching infix from the input stream if present. Infix stream--- may be consumed multiple times.------ Space: @O(n)@ where n is the length of the infix.------ /Unimplemented/-{-# INLINE dropInfix #-}-dropInfix ::- -- (Monad m, Eq a) =>- Stream m a -> Stream m a -> Stream m a-dropInfix = error "Not implemented yet!"---- | Drop suffix from the input stream if present. Suffix stream may be--- consumed multiple times.------ Space: @O(n)@ where n is the length of the suffix.------ /Unimplemented/-{-# INLINE dropSuffix #-}-dropSuffix ::- -- (Monad m, Eq a) =>- Stream m a -> Stream m a -> Stream m a-dropSuffix = error "Not implemented yet!"
− src/Streamly/Internal/Data/Stream/StreamD/Step.hs
@@ -1,39 +0,0 @@--- |--- Module : Streamly.Internal.Data.Stream.StreamD.Step--- Copyright : (c) 2018 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC--module Streamly.Internal.Data.Stream.StreamD.Step- (- -- * The stream type- Step (..)- )-where--import Fusion.Plugin.Types (Fuse(..))---- | 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--{--fromPure :: Monad m => a -> s -> m (Step s a)-fromPure a = return . Yield a--skip :: Monad m => s -> m (Step s a)-skip = return . Skip--stop :: Monad m => m (Step s a)-stop = return Stop--}
− src/Streamly/Internal/Data/Stream/StreamD/Top.hs
@@ -1,353 +0,0 @@-{-# LANGUAGE CPP #-}--- |--- Module : Streamly.Internal.Data.Stream.StreamD.Top--- Copyright : (c) 2020 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ Top level module that can depend on all other lower level Stream modules.--module Streamly.Internal.Data.Stream.StreamD.Top- (- -- * Transformation- -- ** Sampling- -- | Value agnostic filtering.- strideFromThen-- -- * Nesting- -- ** Set like operations- -- | These are not exactly set operations because streams are not- -- necessarily sets, they may have duplicated elements. These operations- -- are generic i.e. they work on streams of unconstrained types, therefore,- -- they have quadratic performance characterstics. For better performance- -- using Set structures see the Streamly.Internal.Data.Stream.Container- -- module.- , filterInStreamGenericBy- , deleteInStreamGenericBy- , unionWithStreamGenericBy-- -- ** Set like operations on sorted streams- , filterInStreamAscBy- , deleteInStreamAscBy- , unionWithStreamAscBy-- -- ** Join operations- , joinInnerGeneric-- -- * Joins on sorted stream- , joinInnerAscBy- , joinLeftAscBy- , joinOuterAscBy- )-where--#include "inline.hs"--import Control.Monad.IO.Class (MonadIO(..))-import Data.IORef (newIORef, readIORef, modifyIORef')-import Streamly.Internal.Data.Fold.Type (Fold)-import Streamly.Internal.Data.Stream.Common ()-import Streamly.Internal.Data.Stream.StreamD.Type (Stream, cross)--import qualified Data.List as List-import qualified Streamly.Internal.Data.Fold as Fold-import qualified Streamly.Internal.Data.Stream.StreamD.Type as Stream-import qualified Streamly.Internal.Data.Stream.StreamD.Nesting as Stream-import qualified Streamly.Internal.Data.Stream.StreamD.Transform as Stream--import Prelude hiding (filter, zipWith, concatMap, concat)--#include "DocTestDataStream.hs"----------------------------------------------------------------------------------- Sampling----------------------------------------------------------------------------------- XXX We can implement this using addition instead of "mod" to make it more--- efficient.---- | @strideFromthen offset stride@ takes the element at @offset@ index and--- then every element at strides of @stride@.------ >>> Stream.fold Fold.toList $ Stream.strideFromThen 2 3 $ Stream.enumerateFromTo 0 10--- [2,5,8]----{-# INLINE strideFromThen #-}-strideFromThen :: Monad m => Int -> Int -> Stream m a -> Stream m a-strideFromThen offset stride =- Stream.with Stream.indexed Stream.filter- (\(i, _) -> i >= offset && (i - offset) `mod` stride == 0)----------------------------------------------------------------------------------- SQL Joins------------------------------------------------------------------------------------- Some references:--- * https://en.wikipedia.org/wiki/Relational_algebra--- * https://en.wikipedia.org/wiki/Join_(SQL)---- TODO: OrdSet/IntSet/hashmap based versions of these. With Eq only--- constraint, the best would be to use an Array with linear search. If the--- second stream is sorted we can also use a binary search, using Ord--- constraint or an ordering function.------ For Storables we can cache the second stream into an unboxed array for--- possibly faster access/compact representation?------ If we do not want to keep the stream in memory but always read it from the--- source (disk/network) every time we iterate through it then we can do that--- too by reading the stream every time, the stream must have immutable state--- in that case and the user is responsible for the behavior if the stream--- source changes during iterations. We can also use an Unfold instead of--- stream. We probably need a way to distinguish streams that can be read--- mutliple times without any interference (e.g. unfolding a stream using an--- immutable handle would work i.e. using pread/pwrite instead of maintaining--- an offset in the handle).---- XXX We can do this concurrently.--- XXX If the second stream is sorted and passed as an Array we could use--- binary search if we have an Ord instance or Ordering returning function. The--- time complexity would then become (m x log n).---- | Like 'cross' but emits only those tuples where @a == b@ using the--- supplied equality predicate.------ Definition:------ >>> joinInnerGeneric eq s1 s2 = Stream.filter (\(a, b) -> a `eq` b) $ Stream.cross s1 s2------ You should almost always prefer @joinInnerOrd@ over 'joinInnerGeneric' if--- possible. @joinInnerOrd@ is an order of magnitude faster but may take more--- space for caching the second stream.------ See 'Streamly.Internal.Data.Unfold.joinInnerGeneric' for a much faster fused--- alternative.------ Time: O(m x n)------ /Pre-release/-{-# INLINE joinInnerGeneric #-}-joinInnerGeneric :: Monad m =>- (a -> b -> Bool) -> Stream m a -> Stream m b -> Stream m (a, b)-joinInnerGeneric eq s1 s2 = Stream.filter (\(a, b) -> a `eq` b) $ cross s1 s2-{--joinInnerGeneric eq s1 s2 = do- -- ConcatMap works faster than bind- Stream.concatMap (\a ->- Stream.concatMap (\b ->- if a `eq` b- then Stream.fromPure (a, b)- else Stream.nil- ) s2- ) s1--}---- | A more efficient 'joinInner' for sorted streams.------ Space: O(1)------ Time: O(m + n)------ /Unimplemented/-{-# INLINE joinInnerAscBy #-}-joinInnerAscBy ::- (a -> b -> Ordering) -> Stream m a -> Stream m b -> Stream m (a, b)-joinInnerAscBy = undefined---- | A more efficient 'joinLeft' for sorted streams.------ Space: O(1)------ Time: O(m + n)------ /Unimplemented/-{-# INLINE joinLeftAscBy #-}-joinLeftAscBy :: -- Monad m =>- (a -> b -> Ordering) -> Stream m a -> Stream m b -> Stream m (a, Maybe b)-joinLeftAscBy _eq _s1 _s2 = undefined---- | A more efficient 'joinOuter' for sorted streams.------ Space: O(1)------ Time: O(m + n)------ /Unimplemented/-{-# INLINE joinOuterAscBy #-}-joinOuterAscBy :: -- Monad m =>- (a -> b -> Ordering)- -> Stream m a- -> Stream m b- -> Stream m (Maybe a, Maybe b)-joinOuterAscBy _eq _s1 _s2 = undefined----------------------------------------------------------------------------------- Set operations (special joins)------------------------------------------------------------------------------------- TODO: OrdSet/IntSet/hashmap based versions of these. With Eq only constraint--- the best would be to use an Array with linear search. If the second stream--- is sorted we can also use a binary search, using Ord constraint.---- | Keep only those elements in the second stream that are present in the--- first stream too. The first stream is folded to a container using the--- supplied fold and then the elements in the container are looked up using the--- supplied lookup function.------ The first stream must be finite and must not block.-{-# INLINE filterStreamWith #-}-filterStreamWith :: Monad m =>- Fold m a (f a)- -> (a -> f a -> Bool)- -> Stream m a- -> Stream m a- -> Stream m a-filterStreamWith fld member s1 s2 =- Stream.concatEffect- $ do- xs <- Stream.fold fld s1- return $ Stream.filter (`member` xs) s2---- | 'filterInStreamGenericBy' retains only those elements in the second stream that--- are present in the first stream.------ >>> Stream.fold Fold.toList $ Stream.filterInStreamGenericBy (==) (Stream.fromList [1,2,2,4]) (Stream.fromList [2,1,1,3])--- [2,1,1]------ >>> Stream.fold Fold.toList $ Stream.filterInStreamGenericBy (==) (Stream.fromList [2,1,1,3]) (Stream.fromList [1,2,2,4])--- [1,2,2]------ Similar to the list intersectBy operation but with the stream argument order--- flipped.------ The first stream must be finite and must not block. Second stream is--- processed only after the first stream is fully realized.------ Space: O(n) where @n@ is the number of elements in the second stream.------ Time: O(m x n) where @m@ is the number of elements in the first stream and--- @n@ is the number of elements in the second stream.------ /Pre-release/-{-# INLINE filterInStreamGenericBy #-}-filterInStreamGenericBy :: Monad m =>- (a -> a -> Bool) -> Stream m a -> Stream m a -> Stream m a-filterInStreamGenericBy eq =- -- XXX Use an (unboxed) array instead.- filterStreamWith- (Fold.scanMaybe (Fold.uniqBy eq) Fold.toListRev)- (List.any . eq)---- | Like 'filterInStreamGenericBy' but assumes that the input streams are sorted in--- ascending order. To use it on streams sorted in descending order pass an--- inverted comparison function returning GT for less than and LT for greater--- than.------ Space: O(1)------ Time: O(m+n)------ /Pre-release/-{-# INLINE filterInStreamAscBy #-}-filterInStreamAscBy :: Monad m =>- (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a-filterInStreamAscBy eq s1 s2 = Stream.intersectBySorted eq s2 s1---- | Delete all elements of the first stream from the seconds stream. If an--- element occurs multiple times in the first stream as many occurrences of it--- are deleted from the second stream.------ >>> Stream.fold Fold.toList $ Stream.deleteInStreamGenericBy (==) (Stream.fromList [1,2,3]) (Stream.fromList [1,2,2])--- [2]------ The following laws hold:------ > deleteInStreamGenericBy (==) s1 (s1 `append` s2) === s2--- > deleteInStreamGenericBy (==) s1 (s1 `interleave` s2) === s2------ Same as the list 'Data.List.//' operation but with argument order flipped.------ The first stream must be finite and must not block. Second stream is--- processed only after the first stream is fully realized.------ Space: O(m) where @m@ is the number of elements in the first stream.------ Time: O(m x n) where @m@ is the number of elements in the first stream and--- @n@ is the number of elements in the second stream.------ /Pre-release/-{-# INLINE deleteInStreamGenericBy #-}-deleteInStreamGenericBy :: Monad m =>- (a -> a -> Bool) -> Stream m a -> Stream m a -> Stream m a-deleteInStreamGenericBy eq s1 s2 =- Stream.concatEffect- $ do- -- This may work well if s1 is small- -- If s1 is big we can go through s1, deleting elements from s2 and- -- not emitting an element if it was successfully deleted from s2.- -- we will need a deleteBy that can return whether the element was- -- deleted or not.- xs <- Stream.fold Fold.toList s2- let f = Fold.foldl' (flip (List.deleteBy eq)) xs- fmap Stream.fromList $ Stream.fold f s1---- | A more efficient 'deleteInStreamGenericBy' for streams sorted in ascending order.------ Space: O(1)------ /Unimplemented/-{-# INLINE deleteInStreamAscBy #-}-deleteInStreamAscBy :: -- (Monad m) =>- (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a-deleteInStreamAscBy _eq _s1 _s2 = undefined---- XXX Remove the MonadIO constraint. We can just cache one stream and then--- implement using differenceEqBy.---- | This essentially appends to the second stream all the occurrences of--- elements in the first stream that are not already present in the second--- stream.------ Equivalent to the following except that @s2@ is evaluated only once:------ >>> unionWithStreamGenericBy eq s1 s2 = s2 `Stream.append` (Stream.deleteInStreamGenericBy eq s2 s1)------ Example:------ >>> Stream.fold Fold.toList $ Stream.unionWithStreamGenericBy (==) (Stream.fromList [1,1,2,3]) (Stream.fromList [1,2,2,4])--- [1,2,2,4,3]------ Space: O(n)------ Time: O(m x n)------ /Pre-release/-{-# INLINE unionWithStreamGenericBy #-}-unionWithStreamGenericBy :: MonadIO m =>- (a -> a -> Bool) -> Stream m a -> Stream m a -> Stream m a-unionWithStreamGenericBy eq s1 s2 =- Stream.concatEffect- $ do- xs <- Stream.fold Fold.toList s1- -- XXX we can use postscanlMAfter' instead of IORef- ref <- liftIO $ newIORef $! List.nubBy eq xs- let f x = do- liftIO $ modifyIORef' ref (List.deleteBy eq x)- return x- s3 = Stream.concatEffect- $ do- xs1 <- liftIO $ readIORef ref- return $ Stream.fromList xs1- return $ Stream.mapM f s2 `Stream.append` s3---- | A more efficient 'unionWithStreamGenericBy' for sorted streams.------ Space: O(1)------ /Unimplemented/-{-# INLINE unionWithStreamAscBy #-}-unionWithStreamAscBy :: -- (Monad m) =>- (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a-unionWithStreamAscBy _eq _s1 _s2 = undefined
− src/Streamly/Internal/Data/Stream/StreamD/Transform.hs
@@ -1,1945 +0,0 @@-{-# LANGUAGE CPP #-}--- |--- Module : Streamly.Internal.Data.Stream.StreamD.Transform--- Copyright : (c) 2018 Composewell Technologies--- (c) Roman Leshchinskiy 2008-2010--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ "Streamly.Internal.Data.Pipe" might ultimately replace this module.---- A few functions in this module have been adapted from the vector package--- (c) Roman Leshchinskiy. See the notes in specific combinators.--module Streamly.Internal.Data.Stream.StreamD.Transform- (- -- * Piping- -- | Pass through a 'Pipe'.- transform-- -- * Mapping- -- | Stateless one-to-one maps.- , map- , mapM- , sequence-- -- * Mapping Effects- , tap- , tapOffsetEvery- , trace- , trace_-- -- * Folding- , foldrS- , foldlS-- -- * Scanning By 'Fold'- , postscan- , scan- , scanMany-- -- * Splitting- , splitOn-- -- * Scanning- -- | Left scans. Stateful, mostly one-to-one maps.- , scanlM'- , scanlMAfter'- , scanl'- , scanlM- , scanl- , scanl1M'- , scanl1'- , scanl1M- , scanl1-- , prescanl'- , prescanlM'-- , postscanl- , postscanlM- , postscanl'- , postscanlM'- , postscanlMAfter'-- , postscanlx'- , postscanlMx'- , scanlMx'- , scanlx'-- -- * Filtering- -- | Produce a subset of the stream.- , with- , scanMaybe- , filter- , filterM- , deleteBy- , uniqBy- , uniq- , prune- , repeated-- -- * Trimming- -- | Produce a subset of the stream trimmed at ends.- , take- , takeWhile- , takeWhileM- , takeWhileLast- , takeWhileAround- , drop- , dropWhile- , dropWhileM- , dropLast- , dropWhileLast- , dropWhileAround-- -- * Inserting Elements- -- | Produce a superset of the stream.- , insertBy- , intersperse- , intersperseM- , intersperseMWith- , intersperseMSuffix- , intersperseMSuffixWith-- -- * Inserting Side Effects- , intersperseM_- , intersperseMSuffix_- , intersperseMPrefix_-- , delay- , delayPre- , delayPost-- -- * Reordering- -- | Produce strictly the same set but reordered.- , reverse- , reverseUnbox- , reassembleBy-- -- * Position Indexing- , indexed- , indexedR-- -- * Time Indexing- , timestampWith- , timestamped- , timeIndexWith- , timeIndexed-- -- * Searching- , findIndices- , elemIndices- , slicesBy-- -- * Rolling map- -- | Map using the previous element.- , rollingMap- , rollingMapM- , rollingMap2-- -- * Maybe Streams- , mapMaybe- , mapMaybeM- , catMaybes-- -- * Either Streams- , catLefts- , catRights- , catEithers- )-where--#include "inline.hs"--import Control.Concurrent (threadDelay)-import Control.Monad (void)-import Control.Monad.IO.Class (MonadIO (liftIO))-import Data.Either (fromLeft, isLeft, isRight, fromRight)-import Data.Functor ((<&>))-import Data.Maybe (fromJust, isJust)-import Fusion.Plugin.Types (Fuse(..))--import Streamly.Internal.Data.Fold.Type (Fold(..))-import Streamly.Internal.Data.Pipe.Type (Pipe(..), PipeState(..))-import Streamly.Internal.Data.SVar.Type (adaptState)-import Streamly.Internal.Data.Time.Units (AbsTime, RelTime64)-import Streamly.Internal.Data.Unboxed (Unbox)-import Streamly.Internal.System.IO (defaultChunkSize)---- import qualified Data.List as List-import qualified Streamly.Internal.Data.Array.Type as A-import qualified Streamly.Internal.Data.Fold as FL-import qualified Streamly.Internal.Data.Pipe.Type as Pipe-import qualified Streamly.Internal.Data.Stream.StreamK.Type as K--import Prelude hiding- ( drop, dropWhile, filter, map, mapM, reverse- , scanl, scanl1, sequence, take, takeWhile, zipWith)--import Streamly.Internal.Data.Stream.StreamD.Generate- (absTimesWith, relTimesWith)-import Streamly.Internal.Data.Stream.StreamD.Type--#include "DocTestDataStream.hs"----------------------------------------------------------------------------------- Piping----------------------------------------------------------------------------------- | Use a 'Pipe' to transform a stream.------ /Pre-release/----{-# 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 Folds----------------------------------------------------------------------------------- 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----------------------------------------------------------------------------------- Transformation by Mapping----------------------------------------------------------------------------------- |--- >>> sequence = Stream.mapM id------ Replace the elements of a stream of monadic actions with the outputs of--- those actions.------ >>> s = Stream.fromList [putStr "a", putStr "b", putStrLn "c"]--- >>> Stream.fold Fold.drain $ Stream.sequence s--- abc----{-# 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----------------------------------------------------------------------------------- Mapping side effects---------------------------------------------------------------------------------data TapState fs st a- = TapInit | Tapping !fs st | TapDone st---- XXX Multiple yield points---- | 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 = Stream.enumerateFromTo 1 2--- >>> Stream.fold Fold.drain $ Stream.tap (Fold.drainMapM print) s--- 1--- 2------ Compare with 'trace'.----{-# 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' TapInit-- where-- step' _ TapInit = do- res <- initial- return- $ Skip- $ case res of- FL.Partial s -> Tapping s state- FL.Done _ -> TapDone state- step' gst (Tapping acc st) = do- r <- step gst st- case r of- Yield x s -> do- res <- fstep acc x- return- $ Yield x- $ case res of- FL.Partial fs -> Tapping fs s- FL.Done _ -> TapDone s- Skip s -> return $ Skip (Tapping acc s)- Stop -> do- void $ extract acc- return Stop- step' gst (TapDone st) = do- r <- step gst st- return- $ case r of- Yield x s -> Yield x (TapDone s)- Skip s -> Skip (TapDone s)- Stop -> Stop--data TapOffState fs s a- = TapOffInit- | TapOffTapping !fs s Int- | TapOffDone s---- XXX Multiple yield points-{-# 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' TapOffInit-- where-- {-# INLINE_LATE step' #-}- step' _ TapOffInit = do- res <- initial- return- $ Skip- $ case res of- FL.Partial s -> TapOffTapping s state (offset `mod` n)- FL.Done _ -> TapOffDone state- step' gst (TapOffTapping acc st count) = do- r <- step gst st- case r of- Yield x s -> do- next <-- if count <= 0- then do- res <- fstep acc x- return- $ case res of- FL.Partial sres ->- TapOffTapping sres s (n - 1)- FL.Done _ -> TapOffDone s- else return $ TapOffTapping acc s (count - 1)- return $ Yield x next- Skip s -> return $ Skip (TapOffTapping acc s count)- Stop -> do- void $ extract acc- return Stop- step' gst (TapOffDone st) = do- r <- step gst st- return- $ case r of- Yield x s -> Yield x (TapOffDone s)- Skip s -> Skip (TapOffDone s)- Stop -> Stop---- | Apply a monadic function to each element flowing through the stream and--- discard the results.------ >>> s = Stream.enumerateFromTo 1 2--- >>> Stream.fold Fold.drain $ Stream.trace print s--- 1--- 2------ Compare with 'tap'.----{-# INLINE trace #-}-trace :: Monad m => (a -> m b) -> Stream m a -> Stream m a-trace f = mapM (\x -> void (f x) >> return x)---- | Perform a side effect before yielding each element of the stream and--- discard the results.------ >>> s = Stream.enumerateFromTo 1 2--- >>> Stream.fold Fold.drain $ Stream.trace_ (print "got here") s--- "got here"--- "got here"------ Same as 'intersperseMPrefix_' but always serial.------ See also: 'trace'------ /Pre-release/-{-# INLINE trace_ #-}-trace_ :: Monad m => m b -> Stream m a -> Stream m a-trace_ eff = mapM (\x -> eff >> return x)----------------------------------------------------------------------------------- Scanning with a Fold---------------------------------------------------------------------------------data ScanState s f = ScanInit s | ScanDo s !f | ScanDone---- | Postscan a stream using the given monadic fold.------ The following example extracts the input stream up to a point where the--- running average of elements is no more than 10:------ >>> import Data.Maybe (fromJust)--- >>> let avg = Fold.teeWith (/) Fold.sum (fmap fromIntegral Fold.length)--- >>> s = Stream.enumerateFromTo 1.0 100.0--- >>> :{--- Stream.fold Fold.toList--- $ fmap (fromJust . fst)--- $ Stream.takeWhile (\(_,x) -> x <= 10)--- $ Stream.postscan (Fold.tee Fold.latest avg) s--- :}--- [1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0,17.0,18.0,19.0]----{-# INLINE_NORMAL postscan #-}-postscan :: Monad m => FL.Fold m a b -> Stream m a -> Stream m b-postscan (FL.Fold fstep initial extract) (Stream sstep state) =- Stream step (ScanInit state)-- where-- {-# INLINE_LATE step #-}- step _ (ScanInit st) = do- res <- initial- return- $ case res of- FL.Partial fs -> Skip $ ScanDo st fs- FL.Done b -> Yield b ScanDone- step gst (ScanDo st fs) = do- res <- sstep (adaptState gst) st- case res of- Yield x s -> do- r <- fstep fs x- case r of- FL.Partial fs1 -> do- !b <- extract fs1- return $ Yield b $ ScanDo s fs1- FL.Done b -> return $ Yield b ScanDone- Skip s -> return $ Skip $ ScanDo s fs- Stop -> return Stop- step _ ScanDone = return Stop--{-# INLINE scanWith #-}-scanWith :: Monad m- => Bool -> Fold m a b -> Stream m a -> Stream m b-scanWith restart (Fold fstep initial extract) (Stream sstep state) =- Stream step (ScanInit state)-- where-- {-# INLINE runStep #-}- runStep st action = do- res <- action- case res of- FL.Partial fs -> do- !b <- extract fs- return $ Yield b $ ScanDo st fs- FL.Done b ->- let next = if restart then ScanInit st else ScanDone- in return $ Yield b next-- {-# INLINE_LATE step #-}- step _ (ScanInit st) = runStep st initial- step gst (ScanDo st fs) = do- res <- sstep (adaptState gst) st- case res of- Yield x s -> runStep s (fstep fs x)- Skip s -> return $ Skip $ ScanDo s fs- Stop -> return Stop- step _ ScanDone = return Stop---- 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.---- | Strict left scan. Scan a stream using the given monadic fold.------ >>> s = Stream.fromList [1..10]--- >>> Stream.fold Fold.toList $ Stream.takeWhile (< 10) $ Stream.scan Fold.sum s--- [0,1,3,6]------ See also: 'usingStateT'------- EXPLANATION:--- >>> scanl' step z = Stream.scan (Fold.foldl' step z)------ Like 'map', 'scanl'' too is a one to one transformation,--- however it adds an extra element.------ >>> s = Stream.fromList [1,2,3,4]--- >>> Stream.fold Fold.toList $ scanl' (+) 0 s--- [0,1,3,6,10]------ >>> Stream.fold Fold.toList $ scanl' (flip (:)) [] s--- [[],[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'@:------ >>> foldl' step z = Stream.fold (Fold.foldl' step z)--- >>> foldl' (\(s, p) x -> (s + x, p * x)) (0,1) s--- (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:------ >>> :{--- foldl' (\(_, p) (s, x) -> (s, p * x)) (0,1)--- $ scanl' (\(s, _) x -> (s + x, x)) (0,1)--- $ Stream.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.----{-# INLINE_NORMAL scan #-}-scan :: Monad m- => FL.Fold m a b -> Stream m a -> Stream m b-scan = scanWith False---- | Like 'scan' but restarts scanning afresh when the scanning fold--- terminates.----{-# INLINE_NORMAL scanMany #-}-scanMany :: Monad m- => FL.Fold m a b -> Stream m a -> Stream m b-scanMany = scanWith True----------------------------------------------------------------------------------- Scanning - Prescans----------------------------------------------------------------------------------- Adapted from the vector package.------ 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 =- postscanlMx' (\b a -> return (fstep b a)) (return begin) (return . done)---- 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 =- scanlMx' (\b a -> return (fstep b a)) (return begin) (return . done)----------------------------------------------------------------------------------- postscans----------------------------------------------------------------------------------- Adapted from the vector package.-{-# INLINE_NORMAL postscanlM' #-}-postscanlM' :: Monad m => (b -> a -> m b) -> m b -> Stream m a -> Stream m b-postscanlM' fstep begin (Stream step state) =- Stream step' Nothing- where- {-# INLINE_LATE step' #-}- step' _ Nothing = do- !x <- begin- return $ Skip (Just (state, x))-- step' gst (Just (st, acc)) = do- r <- step (adaptState gst) st- case r of- Yield x s -> do- !y <- fstep acc x- return $ Yield y (Just (s, y))- Skip s -> return $ Skip (Just (s, acc))- Stop -> return Stop--{-# INLINE_NORMAL postscanl' #-}-postscanl' :: Monad m => (a -> b -> a) -> a -> Stream m b -> Stream m a-postscanl' f seed = postscanlM' (\a b -> return (f a b)) (return seed)--{-# ANN type PScanAfterState Fuse #-}-data PScanAfterState m st acc =- PScanAfterStep st (m acc)- | PScanAfterYield acc (PScanAfterState m st acc)- | PScanAfterStop---- We can possibly have the "done" function as a Maybe to provide an option to--- emit or not emit the accumulator when the stream stops.------ TBD: use a single Yield point----{-# INLINE_NORMAL postscanlMAfter' #-}-postscanlMAfter' :: Monad m- => (b -> a -> m b) -> m b -> (b -> m b) -> Stream m a -> Stream m b-postscanlMAfter' fstep initial done (Stream step1 state1) = do- Stream step (PScanAfterStep state1 initial)-- where-- {-# INLINE_LATE step #-}- step gst (PScanAfterStep st acc) = do- r <- step1 (adaptState gst) st- case r of- Yield x s -> do- !old <- acc- !y <- fstep old x- return (Skip $ PScanAfterYield y (PScanAfterStep s (return y)))- Skip s -> return $ Skip $ PScanAfterStep s acc- -- Strictness is important for fusion- Stop -> do- !v <- acc- !res <- done v- return (Skip $ PScanAfterYield res PScanAfterStop)- step _ (PScanAfterYield acc next) = return $ Yield acc next- step _ PScanAfterStop = return Stop--{-# INLINE_NORMAL postscanlM #-}-postscanlM :: Monad m => (b -> a -> m b) -> m b -> Stream m a -> Stream m b-postscanlM fstep begin (Stream step state) = Stream step' Nothing- where- {-# INLINE_LATE step' #-}- step' _ Nothing = do- r <- begin- return $ Skip (Just (state, r))-- step' gst (Just (st, acc)) = do- r <- step (adaptState gst) st- case r of- Yield x s -> do- y <- fstep acc x- return (Yield y (Just (s, y)))- Skip s -> return $ Skip (Just (s, acc))- Stop -> return Stop--{-# INLINE_NORMAL postscanl #-}-postscanl :: Monad m => (a -> b -> a) -> a -> Stream m b -> Stream m a-postscanl f seed = postscanlM (\a b -> return (f a b)) (return seed)---- | Like 'scanl'' but with a monadic step function and a monadic seed.----{-# INLINE_NORMAL scanlM' #-}-scanlM' :: Monad m => (b -> a -> m b) -> m b -> Stream m a -> Stream m b-scanlM' fstep begin (Stream step state) = Stream step' Nothing- where- {-# INLINE_LATE step' #-}- step' _ Nothing = do- !x <- begin- return $ Yield x (Just (state, x))- step' gst (Just (st, acc)) = do- r <- step (adaptState gst) st- case r of- Yield x s -> do- !y <- fstep acc x- return $ Yield y (Just (s, y))- Skip s -> return $ Skip (Just (s, acc))- Stop -> return Stop---- | @scanlMAfter' accumulate initial done stream@ is like 'scanlM'' except--- that it provides an additional @done@ function to be applied on the--- accumulator when the stream stops. The result of @done@ is also emitted in--- the stream.------ This function can be used to allocate a resource in the beginning of the--- scan and release it when the stream ends or to flush the internal state of--- the scan at the end.------ /Pre-release/----{-# INLINE scanlMAfter' #-}-scanlMAfter' :: Monad m- => (b -> a -> m b) -> m b -> (b -> m b) -> Stream m a -> Stream m b-scanlMAfter' fstep initial done s =- initial `consM` postscanlMAfter' fstep initial done s---- >>> scanl' f z xs = z `Stream.cons` postscanl' f z xs---- | Strict left scan. Like 'map', 'scanl'' too is a one to one transformation,--- however it adds an extra element.------ >>> Stream.toList $ Stream.scanl' (+) 0 $ Stream.fromList [1,2,3,4]--- [0,1,3,6,10]------ >>> Stream.toList $ Stream.scanl' (flip (:)) [] $ Stream.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'@:------ >>> Stream.fold (Fold.foldl' (\(s, p) x -> (s + x, p * x)) (0,1)) $ Stream.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:------ >>> :{--- Stream.fold (Fold.foldl' (\(_, p) (s, x) -> (s, p * x)) (0,1))--- $ Stream.scanl' (\(s, _) x -> (s + x, x)) (0,1)--- $ Stream.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.------ >>> scanl' step z = Stream.scan (Fold.foldl' step z)--- >>> scanl' f z xs = Stream.scanlM' (\a b -> return (f a b)) (return z) xs------ See also: 'usingStateT'----{-# INLINE scanl' #-}-scanl' :: Monad m => (b -> a -> b) -> b -> Stream m a -> Stream m b-scanl' f seed = scanlM' (\a b -> return (f a b)) (return seed)--{-# INLINE_NORMAL scanlM #-}-scanlM :: Monad m => (b -> a -> m b) -> m b -> Stream m a -> Stream m b-scanlM fstep begin (Stream step state) = Stream step' Nothing- where- {-# INLINE_LATE step' #-}- step' _ Nothing = do- x <- begin- return $ Yield x (Just (state, x))- step' gst (Just (st, acc)) = do- r <- step (adaptState gst) st- case r of- Yield x s -> do- y <- fstep acc x- return $ Yield y (Just (s, y))- Skip s -> return $ Skip (Just (s, acc))- Stop -> return Stop--{-# INLINE scanl #-}-scanl :: Monad m => (b -> a -> b) -> b -> Stream m a -> Stream m b-scanl f seed = scanlM (\a b -> return (f a b)) (return seed)---- Adapted from the vector package-{-# 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))---- Adapted from the vector package---- | Like 'scanl1'' but with a monadic step function.----{-# 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---- | 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.------ >>> Stream.toList $ Stream.scanl1' (+) $ Stream.fromList [1,2,3,4]--- [1,3,6,10]----{-# INLINE scanl1' #-}-scanl1' :: Monad m => (a -> a -> a) -> Stream m a -> Stream m a-scanl1' f = scanl1M' (\x y -> return (f x y))------------------------------------------------------------------------------------ Filtering------------------------------------------------------------------------------------ | Modify a @Stream m a -> Stream m a@ stream transformation that accepts a--- predicate @(a -> b)@ to accept @((s, a) -> b)@ instead, provided a--- transformation @Stream m a -> Stream m (s, a)@. Convenient to filter with--- index or time.------ >>> filterWithIndex = Stream.with Stream.indexed Stream.filter------ /Pre-release/-{-# INLINE with #-}-with :: Monad m =>- (Stream m a -> Stream m (s, a))- -> (((s, a) -> b) -> Stream m (s, a) -> Stream m (s, a))- -> (((s, a) -> b) -> Stream m a -> Stream m a)-with f comb g = fmap snd . comb g . f---- Adapted from the vector package---- | Same as 'filter' but with a monadic predicate.------ >>> f p x = p x >>= \r -> return $ if r then Just x else Nothing--- >>> filterM p = Stream.mapMaybeM (f p)----{-# 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---- | Include only those elements that pass a predicate.------ >>> filter p = Stream.filterM (return . p)--- >>> filter p = Stream.mapMaybe (\x -> if p x then Just x else Nothing)--- >>> filter p = Stream.scanMaybe (Fold.filtering p)----{-# INLINE filter #-}-filter :: Monad m => (a -> Bool) -> Stream m a -> Stream m a-filter f = filterM (return . f)--- filter p = scanMaybe (FL.filtering p)---- | Drop repeated elements that are adjacent to each other using the supplied--- comparison function.------ >>> uniq = Stream.uniqBy (==)------ To strip duplicate path separators:------ >>> input = Stream.fromList "//a//b"--- >>> f x y = x == '/' && y == '/'--- >>> Stream.fold Fold.toList $ Stream.uniqBy f input--- "/a/b"------ Space: @O(1)@------ /Pre-release/----{-# INLINE uniqBy #-}-uniqBy :: Monad m =>- (a -> a -> Bool) -> Stream m a -> Stream m a--- uniqBy eq = scanMaybe (FL.uniqBy eq)-uniqBy eq = catMaybes . rollingMap f-- where-- f pre curr =- case pre of- Nothing -> Just curr- Just x -> if x `eq` curr then Nothing else Just curr---- Adapted from the vector package---- | Drop repeated elements that are adjacent to each other.------ >>> uniq = Stream.uniqBy (==)----{-# INLINE_NORMAL uniq #-}-uniq :: (Eq a, Monad m) => Stream m a -> Stream m a--- uniq = scanMaybe FL.uniq-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---- | Deletes the first occurrence of the element in the stream that satisfies--- the given equality predicate.------ >>> input = Stream.fromList [1,3,3,5]--- >>> Stream.fold Fold.toList $ Stream.deleteBy (==) 3 input--- [1,3,5]----{-# INLINE_NORMAL deleteBy #-}-deleteBy :: Monad m => (a -> a -> Bool) -> a -> Stream m a -> Stream m a--- deleteBy cmp x = scanMaybe (FL.deleteBy cmp x)-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---- | Strip all leading and trailing occurrences of an element passing a--- predicate and make all other consecutive occurrences uniq.------ >> prune p = Stream.dropWhileAround p $ Stream.uniqBy (x y -> p x && p y)------ @--- > Stream.prune isSpace (Stream.fromList " hello world! ")--- "hello world!"------ @------ Space: @O(1)@------ /Unimplemented/-{-# INLINE prune #-}-prune ::- -- (Monad m, Eq a) =>- (a -> Bool) -> Stream m a -> Stream m a-prune = error "Not implemented yet!"---- Possible implementation:--- @repeated =--- Stream.catMaybes . Stream.parseMany (Parser.groupBy (==) Fold.repeated)@------ 'Fold.repeated' should return 'Just' when repeated, and 'Nothing' for a--- single element.---- | Emit only repeated elements, once.------ /Unimplemented/-repeated :: -- (Monad m, Eq a) =>- Stream m a -> Stream m a-repeated = undefined----------------------------------------------------------------------------------- Trimming----------------------------------------------------------------------------------- | Take all consecutive elements at the end of the stream for which the--- predicate is true.------ O(n) space, where n is the number elements taken.------ /Unimplemented/-{-# INLINE takeWhileLast #-}-takeWhileLast :: -- Monad m =>- (a -> Bool) -> Stream m a -> Stream m a-takeWhileLast = undefined -- fromStreamD $ D.takeWhileLast n $ toStreamD m---- | Like 'takeWhile' and 'takeWhileLast' combined.------ O(n) space, where n is the number elements taken from the end.------ /Unimplemented/-{-# INLINE takeWhileAround #-}-takeWhileAround :: -- Monad m =>- (a -> Bool) -> Stream m a -> Stream m a-takeWhileAround = undefined -- fromStreamD $ D.takeWhileAround n $ toStreamD m---- Adapted from the vector package---- | Discard first 'n' elements from the stream and take the rest.----{-# 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---- Adapted from the vector package-data DropWhileState s a- = DropWhileDrop s- | DropWhileYield a s- | DropWhileNext s---- | Same as 'dropWhile' but with a monadic predicate.----{-# INLINE_NORMAL dropWhileM #-}-dropWhileM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a--- dropWhileM p = scanMaybe (FL.droppingWhileM p)-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)---- | Drop elements in the stream as long as the predicate succeeds and then--- take the rest of the stream.----{-# INLINE dropWhile #-}-dropWhile :: Monad m => (a -> Bool) -> Stream m a -> Stream m a--- dropWhile p = scanMaybe (FL.droppingWhile p)-dropWhile f = dropWhileM (return . f)---- | Drop @n@ elements at the end of the stream.------ O(n) space, where n is the number elements dropped.------ /Unimplemented/-{-# INLINE dropLast #-}-dropLast :: -- Monad m =>- Int -> Stream m a -> Stream m a-dropLast = undefined -- fromStreamD $ D.dropLast n $ toStreamD m---- | Drop all consecutive elements at the end of the stream for which the--- predicate is true.------ O(n) space, where n is the number elements dropped.------ /Unimplemented/-{-# INLINE dropWhileLast #-}-dropWhileLast :: -- Monad m =>- (a -> Bool) -> Stream m a -> Stream m a-dropWhileLast = undefined -- fromStreamD $ D.dropWhileLast n $ toStreamD m---- | Like 'dropWhile' and 'dropWhileLast' combined.------ O(n) space, where n is the number elements dropped from the end.------ /Unimplemented/-{-# INLINE dropWhileAround #-}-dropWhileAround :: -- Monad m =>- (a -> Bool) -> Stream m a -> Stream m a-dropWhileAround = undefined -- fromStreamD $ D.dropWhileAround n $ toStreamD m----------------------------------------------------------------------------------- Inserting Elements----------------------------------------------------------------------------------- | @insertBy cmp elem stream@ inserts @elem@ before the first element in--- @stream@ that is less than @elem@ when compared using @cmp@.------ >>> insertBy cmp x = Stream.mergeBy cmp (Stream.fromPure x)------ >>> input = Stream.fromList [1,3,5]--- >>> Stream.fold Fold.toList $ Stream.insertBy compare 2 input--- [1,2,3,5]----{-# 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)--data LoopState x s = FirstYield s- | InterspersingYield s- | YieldAndCarry x s---- intersperseM = intersperseMWith 1---- | Insert an effect and its output before consuming an element of a stream--- except the first one.------ >>> input = Stream.fromList "hello"--- >>> Stream.fold Fold.toList $ Stream.trace putChar $ Stream.intersperseM (putChar '.' >> return ',') input--- h.,e.,l.,l.,o"h,e,l,l,o"------ Be careful about the order of effects. In the above example we used trace--- after the intersperse, if we use it before the intersperse the output would--- be he.l.l.o."h,e,l,l,o".------ >>> Stream.fold Fold.toList $ Stream.intersperseM (putChar '.' >> return ',') $ Stream.trace putChar input--- he.l.l.o."h,e,l,l,o"----{-# 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)---- | Insert a pure value between successive elements of a stream.------ >>> input = Stream.fromList "hello"--- >>> Stream.fold Fold.toList $ Stream.intersperse ',' input--- "h,e,l,l,o"----{-# INLINE intersperse #-}-intersperse :: Monad m => a -> Stream m a -> Stream m a-intersperse a = intersperseM (return a)---- | Insert a side effect before consuming an element of a stream except the--- first one.------ >>> input = Stream.fromList "hello"--- >>> Stream.fold Fold.drain $ Stream.trace putChar $ Stream.intersperseM_ (putChar '.') input--- h.e.l.l.o------ /Pre-release/-{-# INLINE_NORMAL intersperseM_ #-}-intersperseM_ :: Monad m => m b -> Stream m a -> Stream m a-intersperseM_ m (Stream step1 state1) = Stream step (Left (pure (), state1))- where- {-# INLINE_LATE step #-}- step gst (Left (eff, st)) = do- r <- step1 gst st- case r of- Yield x s -> eff >> return (Yield x (Right s))- Skip s -> return $ Skip (Left (eff, s))- Stop -> return Stop-- step _ (Right st) = return $ Skip $ Left (void m, st)---- | Intersperse a monadic action into the input stream after every @n@--- elements.------ >> input = Stream.fromList "hello"--- >> Stream.fold Fold.toList $ Stream.intersperseMWith 2 (return ',') input--- "he,ll,o"------ /Unimplemented/-{-# INLINE intersperseMWith #-}-intersperseMWith :: -- Monad m =>- Int -> m a -> Stream m a -> Stream m a-intersperseMWith _n _f _xs = undefined--data SuffixState s a- = SuffixElem s- | SuffixSuffix s- | SuffixYield a (SuffixState s a)---- | Insert an effect and its output after consuming an element of a stream.------ >>> input = Stream.fromList "hello"--- >>> Stream.fold Fold.toList $ Stream.trace putChar $ Stream.intersperseMSuffix (putChar '.' >> return ',') input--- h.,e.,l.,l.,o.,"h,e,l,l,o,"------ /Pre-release/-{-# INLINE_NORMAL intersperseMSuffix #-}-intersperseMSuffix :: forall m a. Monad m => m a -> Stream m a -> Stream m a-intersperseMSuffix 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---- | Insert a side effect after consuming an element of a stream.------ >>> input = Stream.fromList "hello"--- >>> Stream.fold Fold.toList $ Stream.intersperseMSuffix_ (threadDelay 1000000) input--- "hello"------ /Pre-release/----{-# INLINE_NORMAL intersperseMSuffix_ #-}-intersperseMSuffix_ :: Monad m => m b -> Stream m a -> Stream m a-intersperseMSuffix_ m (Stream step1 state1) = Stream step (Left state1)- where- {-# INLINE_LATE step #-}- step gst (Left st) = do- r <- step1 gst st- case r of- Yield x s -> return $ Yield x (Right s)- Skip s -> return $ Skip $ Left s- Stop -> return Stop-- step _ (Right st) = m >> return (Skip (Left st))--data SuffixSpanState s a- = SuffixSpanElem s Int- | SuffixSpanSuffix s- | SuffixSpanYield a (SuffixSpanState s a)- | SuffixSpanLast- | SuffixSpanStop---- | Like 'intersperseMSuffix' but intersperses an effectful action into the--- input stream after every @n@ elements and after the last element.------ >>> input = Stream.fromList "hello"--- >>> Stream.fold Fold.toList $ Stream.intersperseMSuffixWith 2 (return ',') input--- "he,ll,o,"------ /Pre-release/----{-# INLINE_NORMAL intersperseMSuffixWith #-}-intersperseMSuffixWith :: forall m a. Monad m- => Int -> m a -> Stream m a -> Stream m a-intersperseMSuffixWith 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---- | Insert a side effect before consuming an element of a stream.------ Definition:------ >>> intersperseMPrefix_ m = Stream.mapM (\x -> void m >> return x)------ >>> input = Stream.fromList "hello"--- >>> Stream.fold Fold.toList $ Stream.trace putChar $ Stream.intersperseMPrefix_ (putChar '.' >> return ',') input--- .h.e.l.l.o"hello"------ Same as 'trace_'.------ /Pre-release/----{-# INLINE intersperseMPrefix_ #-}-intersperseMPrefix_ :: Monad m => m b -> Stream m a -> Stream m a-intersperseMPrefix_ m = mapM (\x -> void m >> return x)----------------------------------------------------------------------------------- Inserting Time----------------------------------------------------------------------------------- XXX This should be in Prelude, should we export this as a helper function?---- | Block the current thread for specified number of seconds.-{-# INLINE sleep #-}-sleep :: MonadIO m => Double -> m ()-sleep n = liftIO $ threadDelay $ round $ n * 1000000---- | Introduce a delay of specified seconds between elements of the stream.------ Definition:------ >>> sleep n = liftIO $ threadDelay $ round $ n * 1000000--- >>> delay = Stream.intersperseM_ . sleep------ Example:------ >>> input = Stream.enumerateFromTo 1 3--- >>> Stream.fold (Fold.drainMapM print) $ Stream.delay 1 input--- 1--- 2--- 3----{-# INLINE delay #-}-delay :: MonadIO m => Double -> Stream m a -> Stream m a-delay = intersperseM_ . sleep---- | Introduce a delay of specified seconds after consuming an element of a--- stream.------ Definition:------ >>> sleep n = liftIO $ threadDelay $ round $ n * 1000000--- >>> delayPost = Stream.intersperseMSuffix_ . sleep------ Example:------ >>> input = Stream.enumerateFromTo 1 3--- >>> Stream.fold (Fold.drainMapM print) $ Stream.delayPost 1 input--- 1--- 2--- 3------ /Pre-release/----{-# INLINE delayPost #-}-delayPost :: MonadIO m => Double -> Stream m a -> Stream m a-delayPost n = intersperseMSuffix_ $ liftIO $ threadDelay $ round $ n * 1000000---- | Introduce a delay of specified seconds before consuming an element of a--- stream.------ Definition:------ >>> sleep n = liftIO $ threadDelay $ round $ n * 1000000--- >>> delayPre = Stream.intersperseMPrefix_. sleep------ Example:------ >>> input = Stream.enumerateFromTo 1 3--- >>> Stream.fold (Fold.drainMapM print) $ Stream.delayPre 1 input--- 1--- 2--- 3------ /Pre-release/----{-# INLINE delayPre #-}-delayPre :: MonadIO m => Double -> Stream m a -> Stream m a-delayPre = intersperseMPrefix_. sleep----------------------------------------------------------------------------------- Reordering----------------------------------------------------------------------------------- | Returns the elements of the stream in reverse order. The stream must be--- finite. Note that this necessarily buffers the entire stream in memory.------ Definition:------ >>> reverse m = Stream.concatEffect $ Stream.fold Fold.toListRev m >>= return . Stream.fromList----{-# INLINE_NORMAL reverse #-}-reverse :: Monad m => Stream m a -> Stream m a-reverse m = concatEffect $ fold FL.toListRev m <&> fromList-{--reverse m = Stream step Nothing- where- {-# INLINE_LATE step #-}- step _ Nothing = do- xs <- foldl' (flip (:)) [] m- return $ Skip (Just xs)- step _ (Just (x:xs)) = return $ Yield x (Just xs)- step _ (Just []) = return Stop--}---- | Like 'reverse' but several times faster, requires an 'Unbox' instance.------ /O(n) space/------ /Pre-release/-{-# INLINE reverseUnbox #-}-reverseUnbox :: (MonadIO m, Unbox a) => Stream m a -> Stream m a-reverseUnbox =- A.flattenArraysRev -- unfoldMany A.readRev- . fromStreamK- . K.reverse- . toStreamK- . A.chunksOf defaultChunkSize---- | 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.------ /Unimplemented/----{-# INLINE reassembleBy #-}-reassembleBy- :: -- Monad m =>- Fold m a b- -> (a -> a -> Int)- -> Stream m a- -> Stream m b-reassembleBy = undefined----------------------------------------------------------------------------------- Position Indexing----------------------------------------------------------------------------------- Adapted from the vector package---- |--- >>> f = Fold.foldl' (\(i, _) x -> (i + 1, x)) (-1,undefined)--- >>> indexed = Stream.postscan f--- >>> indexed = Stream.zipWith (,) (Stream.enumerateFrom 0)--- >>> indexedR n = fmap (\(i, a) -> (n - i, a)) . indexed------ Pair each element in a stream with its index, starting from index 0.------ >>> Stream.fold Fold.toList $ Stream.indexed $ Stream.fromList "hello"--- [(0,'h'),(1,'e'),(2,'l'),(3,'l'),(4,'o')]----{-# INLINE_NORMAL indexed #-}-indexed :: Monad m => Stream m a -> Stream m (Int, a)--- indexed = scanMaybe FL.indexing-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---- Adapted from the vector package---- |--- >>> f n = Fold.foldl' (\(i, _) x -> (i - 1, x)) (n + 1,undefined)--- >>> indexedR n = Stream.postscan (f n)------ >>> s n = Stream.enumerateFromThen n (n - 1)--- >>> indexedR n = Stream.zipWith (,) (s n)------ Pair each element in a stream with its index, starting from the--- given index @n@ and counting down.------ >>> Stream.fold Fold.toList $ Stream.indexedR 10 $ Stream.fromList "hello"--- [(10,'h'),(9,'e'),(8,'l'),(7,'l'),(6,'o')]----{-# INLINE_NORMAL indexedR #-}-indexedR :: Monad m => Int -> Stream m a -> Stream m (Int, a)--- indexedR n = scanMaybe (FL.indexingRev n)-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------------------------------------------------------------------------------------ Time Indexing------------------------------------------------------------------------------------ Note: The timestamp stream must be the second stream in the zip so that the--- timestamp is generated after generating the stream element and not before.--- If we do not do that then the following example will generate the same--- timestamp for first two elements:------ Stream.fold Fold.toList $ Stream.timestamped $ Stream.delay $ Stream.enumerateFromTo 1 3---- | Pair each element in a stream with an absolute timestamp, using a clock of--- specified granularity. The timestamp is generated just before the element--- is consumed.------ >>> Stream.fold Fold.toList $ Stream.timestampWith 0.01 $ Stream.delay 1 $ Stream.enumerateFromTo 1 3--- [(AbsTime (TimeSpec {sec = ..., nsec = ...}),1),(AbsTime (TimeSpec {sec = ..., nsec = ...}),2),(AbsTime (TimeSpec {sec = ..., nsec = ...}),3)]------ /Pre-release/----{-# INLINE timestampWith #-}-timestampWith :: (MonadIO m)- => Double -> Stream m a -> Stream m (AbsTime, a)-timestampWith g stream = zipWith (flip (,)) stream (absTimesWith g)---- TBD: check performance vs a custom implementation without using zipWith.------ /Pre-release/----{-# INLINE timestamped #-}-timestamped :: (MonadIO m)- => Stream m a -> Stream m (AbsTime, a)-timestamped = timestampWith 0.01---- | Pair each element in a stream with relative times starting from 0, using a--- clock with the specified granularity. The time is measured just before the--- element is consumed.------ >>> Stream.fold Fold.toList $ Stream.timeIndexWith 0.01 $ Stream.delay 1 $ Stream.enumerateFromTo 1 3--- [(RelTime64 (NanoSecond64 ...),1),(RelTime64 (NanoSecond64 ...),2),(RelTime64 (NanoSecond64 ...),3)]------ /Pre-release/----{-# INLINE timeIndexWith #-}-timeIndexWith :: (MonadIO m)- => Double -> Stream m a -> Stream m (RelTime64, a)-timeIndexWith g stream = zipWith (flip (,)) stream (relTimesWith g)---- | Pair each element in a stream with relative times starting from 0, using a--- 10 ms granularity clock. The time is measured just before the element is--- consumed.------ >>> Stream.fold Fold.toList $ Stream.timeIndexed $ Stream.delay 1 $ Stream.enumerateFromTo 1 3--- [(RelTime64 (NanoSecond64 ...),1),(RelTime64 (NanoSecond64 ...),2),(RelTime64 (NanoSecond64 ...),3)]------ /Pre-release/----{-# INLINE timeIndexed #-}-timeIndexed :: (MonadIO m)- => Stream m a -> Stream m (RelTime64, a)-timeIndexed = timeIndexWith 0.01----------------------------------------------------------------------------------- Searching----------------------------------------------------------------------------------- | Find all the indices where the element in the stream satisfies the given--- predicate.------ >>> findIndices p = Stream.scanMaybe (Fold.findIndices 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---- | Find all the indices where the value of the element in the stream is equal--- to the given value.------ >>> elemIndices a = Stream.findIndices (== a)----{-# INLINE elemIndices #-}-elemIndices :: (Monad m, Eq a) => a -> Stream m a -> Stream m Int-elemIndices a = findIndices (== a)--{-# INLINE_NORMAL slicesBy #-}-slicesBy :: Monad m => (a -> Bool) -> Stream m a -> Stream m (Int, Int)-slicesBy p (Stream step1 state1) = Stream step (Just (state1, 0, 0))-- where-- {-# INLINE_LATE step #-}- step gst (Just (st, i, len)) = i `seq` len `seq` do- r <- step1 (adaptState gst) st- return- $ case r of- Yield x s ->- if p x- then Yield (i, len + 1) (Just (s, i + len + 1, 0))- else Skip (Just (s, i, len + 1))- Skip s -> Skip (Just (s, i, len))- Stop -> if len == 0 then Stop else Yield (i, len) Nothing- step _ Nothing = return Stop----------------------------------------------------------------------------------- Rolling map---------------------------------------------------------------------------------data RollingMapState s a = RollingMapGo s a---- | Like 'rollingMap' but with an effectful map function.------ /Pre-release/----{-# INLINE rollingMapM #-}-rollingMapM :: Monad m => (Maybe a -> a -> m b) -> Stream m a -> Stream m b--- rollingMapM f = scanMaybe (FL.slide2 $ Window.rollingMapM f)-rollingMapM f (Stream step1 state1) = Stream step (RollingMapGo state1 Nothing)-- where-- step gst (RollingMapGo s1 curr) = do- r <- step1 (adaptState gst) s1- case r of- Yield x s -> do- !res <- f curr x- return $ Yield res $ RollingMapGo s (Just x)- Skip s -> return $ Skip $ RollingMapGo s curr- Stop -> return Stop---- rollingMap is a special case of an incremental sliding fold. It can be--- written as:------ > fld f = slidingWindow 1 (Fold.foldl' (\_ (x,y) -> f y x)--- > rollingMap f = Stream.postscan (fld f) undefined---- | Apply a function on every two successive elements of a stream. The first--- argument of the map function is the previous element and the second argument--- is the current element. When the current element is the first element, the--- previous element is 'Nothing'.------ /Pre-release/----{-# INLINE rollingMap #-}-rollingMap :: Monad m => (Maybe a -> a -> b) -> Stream m a -> Stream m b--- rollingMap f = scanMaybe (FL.slide2 $ Window.rollingMap f)-rollingMap f = rollingMapM (\x y -> return $ f x y)---- | Like 'rollingMap' but requires at least two elements in the stream,--- returns an empty stream otherwise.------ This is the stream equivalent of the list idiom @zipWith f xs (tail xs)@.------ /Pre-release/----{-# INLINE rollingMap2 #-}-rollingMap2 :: Monad m => (a -> a -> b) -> Stream m a -> Stream m b-rollingMap2 f = catMaybes . rollingMap g-- where-- g Nothing _ = Nothing- g (Just x) y = Just (f x y)----------------------------------------------------------------------------------- Maybe Streams----------------------------------------------------------------------------------- XXX Will this always fuse properly?---- | 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 = Stream.catMaybes . fmap f----{-# INLINE_NORMAL mapMaybe #-}-mapMaybe :: Monad m => (a -> Maybe b) -> Stream m a -> Stream m b-mapMaybe f = fmap fromJust . filter isJust . map f---- | Like 'mapMaybe' but maps a monadic function.------ Equivalent to:------ >>> mapMaybeM f = Stream.catMaybes . Stream.mapM f------ >>> mapM f = Stream.mapMaybeM (\x -> Just <$> f x)----{-# INLINE_NORMAL mapMaybeM #-}-mapMaybeM :: Monad m => (a -> m (Maybe b)) -> Stream m a -> Stream m b-mapMaybeM f = fmap fromJust . filter isJust . mapM f---- | In a stream of 'Maybe's, discard 'Nothing's and unwrap 'Just's.------ >>> catMaybes = Stream.mapMaybe id--- >>> catMaybes = fmap fromJust . Stream.filter isJust------ /Pre-release/----{-# INLINE catMaybes #-}-catMaybes :: Monad m => Stream m (Maybe a) -> Stream m a--- catMaybes = fmap fromJust . filter isJust-catMaybes (Stream step state) = Stream step1 state-- where-- {-# INLINE_LATE step1 #-}- step1 gst st = do- r <- step (adaptState gst) st- case r of- Yield x s -> do- return- $ case x of- Just a -> Yield a s- Nothing -> Skip s- Skip s -> return $ Skip s- Stop -> return Stop---- | Use a filtering fold on a stream.------ >>> scanMaybe f = Stream.catMaybes . Stream.postscan f----{-# INLINE scanMaybe #-}-scanMaybe :: Monad m => Fold m a (Maybe b) -> Stream m a -> Stream m b-scanMaybe f = catMaybes . postscan f----------------------------------------------------------------------------------- Either streams----------------------------------------------------------------------------------- | Discard 'Right's and unwrap 'Left's in an 'Either' stream.------ >>> catLefts = fmap (fromLeft undefined) . Stream.filter isLeft------ /Pre-release/----{-# INLINE catLefts #-}-catLefts :: Monad m => Stream m (Either a b) -> Stream m a-catLefts = fmap (fromLeft undefined) . filter isLeft---- | Discard 'Left's and unwrap 'Right's in an 'Either' stream.------ >>> catRights = fmap (fromRight undefined) . Stream.filter isRight------ /Pre-release/----{-# INLINE catRights #-}-catRights :: Monad m => Stream m (Either a b) -> Stream m b-catRights = fmap (fromRight undefined) . filter isRight---- | Remove the either wrapper and flatten both lefts and as well as rights in--- the output stream.------ >>> catEithers = fmap (either id id)------ /Pre-release/----{-# INLINE catEithers #-}-catEithers :: Monad m => Stream m (Either a a) -> Stream m a-catEithers = fmap (either id id)----------------------------------------------------------------------------------- Splitting----------------------------------------------------------------------------------- | Split on an infixed separator element, dropping the separator. The--- supplied 'Fold' is applied on the split segments. Splits the stream on--- separator elements determined by the supplied predicate, separator is--- considered as infixed between two segments:------ >>> splitOn' p xs = Stream.fold Fold.toList $ Stream.splitOn p Fold.toList (Stream.fromList xs)--- >>> splitOn' (== '.') "a.b"--- ["a","b"]------ An empty stream is folded to the default value of the fold:------ >>> splitOn' (== '.') ""--- [""]------ If one or both sides of the separator are missing then the empty segment on--- that side is folded to the default output of the fold:------ >>> splitOn' (== '.') "."--- ["",""]------ >>> splitOn' (== '.') ".a"--- ["","a"]------ >>> splitOn' (== '.') "a."--- ["a",""]------ >>> splitOn' (== '.') "a..b"--- ["a","","b"]------ splitOn is an inverse of intercalating single element:------ > Stream.intercalate (Stream.fromPure '.') Unfold.fromList . Stream.splitOn (== '.') Fold.toList === id------ Assuming the input stream does not contain the separator:------ > Stream.splitOn (== '.') Fold.toList . Stream.intercalate (Stream.fromPure '.') Unfold.fromList === id----{-# INLINE splitOn #-}-splitOn :: Monad m => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b-splitOn predicate f =- -- We can express the infix splitting in terms of optional suffix split- -- fold. After applying a suffix split fold repeatedly if the last segment- -- ends with a suffix then we need to return the default output of the fold- -- after that to make it an infix split.- --- -- Alternately, we can also express it using an optional prefix split fold.- -- If the first segment starts with a prefix then we need to emit the- -- default output of the fold before that to make it an infix split, and- -- then apply prefix split fold repeatedly.- --- -- Since a suffix split fold can be easily expressed using a- -- non-backtracking fold, we use that.- foldManyPost (FL.takeEndBy_ predicate f)
− src/Streamly/Internal/Data/Stream/StreamD/Transformer.hs
@@ -1,182 +0,0 @@-{-# LANGUAGE CPP #-}--- |--- Module : Streamly.Internal.Data.Stream.StreamD.Transformer--- Copyright : (c) 2018 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ Transform the underlying monad of a stream using a monad transfomer.--module Streamly.Internal.Data.Stream.StreamD.Transformer- (- foldlT- , foldrT-- -- * Transform Inner Monad- , liftInner- , runReaderT- , usingReaderT- , evalStateT- , runStateT- , usingStateT- )-where--#include "inline.hs"--import Control.Monad.Trans.Class (MonadTrans(lift))-import Control.Monad.Trans.Reader (ReaderT)-import Control.Monad.Trans.State.Strict (StateT)-import GHC.Types (SPEC(..))-import Streamly.Internal.Data.SVar.Type (defState, adaptState)--import qualified Control.Monad.Trans.Reader as Reader-import qualified Control.Monad.Trans.State.Strict as State--import Streamly.Internal.Data.Stream.StreamD.Type--#include "DocTestDataStream.hs"---- | Lazy left fold to a transformer monad.----{-# 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---- | 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 = Stream.foldrT------ >>> step f x xs = lift $ f x (runIdentityT xs)--- >>> foldrM f z s = runIdentityT $ Stream.foldrT (step f) (lift z) s------ 'foldrT' can be used to translate streamly streams to other transformer--- monads e.g. to a different streaming type.------ /Pre-release/-{-# INLINE_NORMAL foldrT #-}-foldrT :: (Monad m, Monad (t m), MonadTrans t)- => (a -> t m b -> t m b) -> t m b -> Stream m a -> t m b-foldrT f final (Stream step state) = go SPEC state- where- {-# INLINE_LATE go #-}- go !_ st = do- r <- lift $ step defState st- case r of- Yield x s -> f x (go SPEC s)- Skip s -> go SPEC s- Stop -> final------------------------------------------------------------------------------------ Transform Inner Monad------------------------------------------------------------------------------------ | Lift the inner monad @m@ of @Stream m a@ to @t m@ where @t@ is a monad--- transformer.----{-# 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----------------------------------------------------------------------------------- Sharing read only state in a stream----------------------------------------------------------------------------------- | Evaluate the inner monad of a stream as 'ReaderT'.----{-# INLINE_NORMAL runReaderT #-}-runReaderT :: Monad m => m s -> Stream (ReaderT s m) a -> Stream m a-runReaderT env (Stream step state) = Stream step' (state, env)- where- {-# INLINE_LATE step' #-}- step' gst (st, action) = do- sv <- action- r <- Reader.runReaderT (step (adaptState gst) st) sv- return $ case r of- Yield x s -> Yield x (s, return sv)- Skip s -> Skip (s, return sv)- Stop -> Stop---- | Run a stream transformation using a given environment.----{-# INLINE usingReaderT #-}-usingReaderT- :: Monad m- => m r- -> (Stream (ReaderT r m) a -> Stream (ReaderT r m) a)- -> Stream m a- -> Stream m a-usingReaderT r f xs = runReaderT r $ f $ liftInner xs----------------------------------------------------------------------------------- Sharing read write state in a stream----------------------------------------------------------------------------------- | Evaluate the inner monad of a stream as 'StateT'.------ >>> evalStateT s = fmap snd . Stream.runStateT s----{-# INLINE_NORMAL evalStateT #-}-evalStateT :: Monad m => m s -> Stream (StateT s m) a -> Stream m a-evalStateT initial (Stream step state) = Stream step' (state, initial)- where- {-# INLINE_LATE step' #-}- step' gst (st, action) = do- sv <- action- (r, !sv') <- State.runStateT (step (adaptState gst) st) sv- return $ case r of- Yield x s -> Yield x (s, return sv')- Skip s -> Skip (s, return sv')- Stop -> Stop---- | Evaluate the inner monad of a stream as 'StateT' and emit the resulting--- state and value pair after each step.----{-# INLINE_NORMAL runStateT #-}-runStateT :: Monad m => m s -> Stream (StateT s m) a -> Stream m (s, a)-runStateT initial (Stream step state) = Stream step' (state, initial)- where- {-# INLINE_LATE step' #-}- step' gst (st, action) = do- sv <- action- (r, !sv') <- State.runStateT (step (adaptState gst) st) sv- return $ case r of- Yield x s -> Yield (sv', x) (s, return sv')- Skip s -> Skip (s, return sv')- Stop -> Stop---- | Run a stateful (StateT) stream transformation using a given state.------ >>> usingStateT s f = Stream.evalStateT s . f . Stream.liftInner------ See also: 'scan'----{-# INLINE usingStateT #-}-usingStateT- :: Monad m- => m s- -> (Stream (StateT s m) a -> Stream (StateT s m) a)- -> Stream m a- -> Stream m a-usingStateT s f = evalStateT s . f . liftInner
− src/Streamly/Internal/Data/Stream/StreamD/Type.hs
@@ -1,2074 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE UndecidableInstances #-}---- |--- Module : Streamly.Internal.Data.Stream.StreamD.Type--- Copyright : (c) 2018 Composewell Technologies--- (c) Roman Leshchinskiy 2008-2010--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC---- The stream type is inspired by the vector package. A few functions in this--- module have been originally adapted from the vector package (c) Roman--- Leshchinskiy. See the notes in specific functions.--module Streamly.Internal.Data.Stream.StreamD.Type- (- -- * The stream type- Step (..)- -- XXX UnStream is exported to avoid a performance issue in some- -- combinators if we use the pattern synonym "Stream".- , Stream (Stream, UnStream)-- -- * CrossStream type wrapper- , CrossStream- , unCross- , mkCross-- -- * Conversion to StreamK- , fromStreamK- , toStreamK-- -- * From Unfold- , unfold-- -- * Construction- -- ** Primitives- , nilM- , consM-- -- ** From Values- , fromPure- , fromEffect-- -- ** From Containers- , Streamly.Internal.Data.Stream.StreamD.Type.fromList-- -- * Elimination- -- ** Primitives- , uncons-- -- ** Strict Left Folds- , Streamly.Internal.Data.Stream.StreamD.Type.fold- , foldBreak- , foldAddLazy- , foldAdd- , foldEither-- , Streamly.Internal.Data.Stream.StreamD.Type.foldl'- , foldlM'- , foldlx'- , foldlMx'-- -- ** Lazy Right Folds- , foldrM- , foldrMx- , Streamly.Internal.Data.Stream.StreamD.Type.foldr- , foldrS-- -- ** Specific Folds- , drain- , Streamly.Internal.Data.Stream.StreamD.Type.toList-- -- * Mapping- , map- , mapM-- -- * Stateful Filters- , take- , takeWhile- , takeWhileM- , takeEndBy- , takeEndByM-- -- * Combining Two Streams- -- ** Zipping- , zipWithM- , zipWith-- -- ** Cross Product- , crossApply- , crossApplyFst- , crossApplySnd- , crossWith- , cross-- -- * Unfold Many- , ConcatMapUState (..)- , unfoldMany-- -- * Concat- , concatEffect- , concatMap- , concatMapM- , concat-- -- * Unfold Iterate- , unfoldIterateDfs- , unfoldIterateBfs- , unfoldIterateBfsRev-- -- * Concat Iterate- , concatIterateScan- , concatIterateDfs- , concatIterateBfs- , concatIterateBfsRev-- -- * Fold Many- , FoldMany (..) -- for inspection testing- , FoldManyPost (..)- , foldMany- , foldManyPost- , groupsOf- , refoldMany-- -- * Fold Iterate- , reduceIterateBfs- , foldIterateBfs-- -- * Multi-stream folds- , eqBy- , cmpBy- )-where--#include "inline.hs"--import Control.Applicative (liftA2)-import Control.Monad.Catch (MonadThrow, throwM)-import Control.Monad.Trans.Class (MonadTrans(lift))-import Control.Monad.IO.Class (MonadIO(..))-import Data.Foldable (Foldable(foldl'), fold, foldr)-import Data.Functor (($>))-import Data.Functor.Identity (Identity(..))-import Data.Maybe (fromMaybe)-import Data.Semigroup (Endo(..))-import Fusion.Plugin.Types (Fuse(..))-import GHC.Base (build)-import GHC.Exts (IsList(..), IsString(..), oneShot)-import GHC.Types (SPEC(..))-import Prelude hiding (map, mapM, take, concatMap, takeWhile, zipWith, concat)-import Text.Read- ( Lexeme(Ident), lexP, parens, prec, readPrec, readListPrec- , readListPrecDefault)--import Streamly.Internal.BaseCompat ((#.))-import Streamly.Internal.Data.Fold.Type (Fold(..))-import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)-import Streamly.Internal.Data.Refold.Type (Refold(..))-import Streamly.Internal.Data.Stream.StreamD.Step (Step (..))-import Streamly.Internal.Data.SVar.Type (State, adaptState, defState)-import Streamly.Internal.Data.Unfold.Type (Unfold(..))--import qualified Streamly.Internal.Data.Fold.Type as FL hiding (foldr)-import qualified Streamly.Internal.Data.Stream.StreamK.Type as K-#ifdef USE_UNFOLDS_EVERYWHERE-import qualified Streamly.Internal.Data.Unfold.Type as Unfold-#endif--#include "DocTestDataStream.hs"----------------------------------------------------------------------------------- The direct style stream type----------------------------------------------------------------------------------- gst = global state---- | A stream consists of a step function that generates the next step given a--- current state, and the current state.-data Stream m a =- forall s. UnStream (State K.StreamK m a -> s -> m (Step s a)) s---- XXX This causes perf trouble when pattern matching with "Stream" in a--- recursive way, e.g. in uncons, foldBreak, concatMap. We need to get rid of--- this.-unShare :: Stream m a -> Stream m a-unShare (UnStream step state) = UnStream step' state- where step' gst = step (adaptState gst)--pattern Stream :: (State K.StreamK m a -> s -> m (Step s a)) -> s -> Stream m a-pattern Stream step state <- (unShare -> UnStream step state)- where Stream = UnStream--{-# COMPLETE Stream #-}----------------------------------------------------------------------------------- Primitives----------------------------------------------------------------------------------- | A stream that terminates without producing any output, but produces a side--- effect.------ >>> Stream.fold Fold.toList (Stream.nilM (print "nil"))--- "nil"--- []------ /Pre-release/-{-# INLINE_NORMAL nilM #-}-nilM :: Applicative m => m b -> Stream m a-nilM m = Stream (\_ _ -> m $> Stop) ()---- | Like 'cons' but fuses an effect instead of a pure value.-{-# INLINE_NORMAL consM #-}-consM :: Applicative m => m a -> Stream m a -> Stream m a-consM m (Stream step state) = Stream step1 Nothing-- where-- {-# INLINE_LATE step1 #-}- step1 _ Nothing = (`Yield` Just state) <$> m- step1 gst (Just st) = do- (\case- Yield a s -> Yield a (Just s)- Skip s -> Skip (Just s)- Stop -> Stop) <$> step gst st---- | 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.------ Properties:------ >>> Nothing <- Stream.uncons Stream.nil--- >>> Just ("a", t) <- Stream.uncons (Stream.cons "a" Stream.nil)------ This can be used to consume the stream in an imperative manner one element--- at a time, 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.------ All the folds in this module can be expressed in terms of 'uncons', however,--- this is generally less efficient than specific folds because it takes apart--- the stream one element at a time, therefore, does not take adavantage of--- stream fusion.------ 'foldBreak' is a more general way of consuming a stream piecemeal.------ >>> :{--- uncons xs = do--- r <- Stream.foldBreak Fold.one xs--- return $ case r of--- (Nothing, _) -> Nothing--- (Just h, t) -> Just (h, t)--- :}----{-# INLINE_NORMAL uncons #-}-uncons :: Monad m => Stream m a -> m (Maybe (a, Stream m a))-uncons (UnStream step state) = go SPEC state- where- go !_ st = do- r <- step defState st- case r of- Yield x s -> return $ Just (x, Stream step s)- Skip s -> go SPEC s- Stop -> return Nothing----------------------------------------------------------------------------------- From 'Unfold'---------------------------------------------------------------------------------data UnfoldState s = UnfoldNothing | UnfoldJust s---- | Convert an 'Unfold' into a stream by supplying it an input seed.------ >>> s = Stream.unfold Unfold.replicateM (3, putStrLn "hello")--- >>> Stream.fold Fold.drain s--- hello--- hello--- hello----{-# INLINE_NORMAL unfold #-}-unfold :: Applicative m => Unfold m a b -> a -> Stream m b-unfold (Unfold ustep inject) seed = Stream step UnfoldNothing-- where-- {-# INLINE_LATE step #-}- step _ UnfoldNothing = Skip . UnfoldJust <$> inject seed- step _ (UnfoldJust st) = do- (\case- Yield x s -> Yield x (UnfoldJust s)- Skip s -> Skip (UnfoldJust s)- Stop -> Stop) <$> ustep st----------------------------------------------------------------------------------- From Values----------------------------------------------------------------------------------- | Create a singleton stream from a pure value.------ >>> fromPure a = a `Stream.cons` Stream.nil--- >>> fromPure = pure--- >>> fromPure = Stream.fromEffect . pure----{-# INLINE_NORMAL fromPure #-}-fromPure :: Applicative m => a -> Stream m a-fromPure x = Stream (\_ s -> pure $ step undefined s) True- where- {-# INLINE_LATE step #-}- step _ True = Yield x False- step _ False = Stop---- | Create a singleton stream from a monadic action.------ >>> fromEffect m = m `Stream.consM` Stream.nil--- >>> fromEffect = Stream.sequence . Stream.fromPure------ >>> Stream.fold Fold.drain $ Stream.fromEffect (putStrLn "hello")--- hello----{-# INLINE_NORMAL fromEffect #-}-fromEffect :: Applicative m => m a -> Stream m a-fromEffect m = Stream step True-- where-- {-# INLINE_LATE step #-}- step _ True = (`Yield` False) <$> m- step _ False = pure Stop----------------------------------------------------------------------------------- From Containers----------------------------------------------------------------------------------- Adapted from the vector package.---- | Construct a stream from a list of pure values.-{-# INLINE_LATE fromList #-}-fromList :: Applicative m => [a] -> Stream m a-#ifdef USE_UNFOLDS_EVERYWHERE-fromList = unfold Unfold.fromList-#else-fromList = Stream step- where- {-# INLINE_LATE step #-}- step _ (x:xs) = pure $ Yield x xs- step _ [] = pure Stop-#endif----------------------------------------------------------------------------------- Conversions From/To----------------------------------------------------------------------------------- | Convert a CPS encoded StreamK to direct style step encoded StreamD-{-# INLINE_LATE fromStreamK #-}-fromStreamK :: Applicative m => K.StreamK m a -> Stream m a-fromStreamK = Stream step- where- step gst m1 =- let stop = pure Stop- single a = pure $ Yield a K.nil- yieldk a r = pure $ Yield a r- in K.foldStreamShared gst yieldk single stop m1---- | Convert a direct style step encoded StreamD to a CPS encoded StreamK-{-# INLINE_LATE toStreamK #-}-toStreamK :: Monad m => Stream m a -> K.StreamK m a-toStreamK (Stream step state) = go state- where- 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"- forall s. toStreamK (fromStreamK s) = s #-}-{-# RULES "toStreamK/fromStreamK fusion"- forall s. fromStreamK (toStreamK s) = s #-}-#endif----------------------------------------------------------------------------------- Running a 'Fold'----------------------------------------------------------------------------------- >>> fold f = Fold.extractM . Stream.foldAddLazy f--- >>> fold f = Stream.fold Fold.one . Stream.foldManyPost f--- >>> fold f = Fold.extractM <=< Stream.foldAdd f---- | Fold a stream using the supplied left 'Fold' and reducing the resulting--- expression strictly at each step. The behavior is similar to 'foldl''. A--- 'Fold' can terminate early without consuming the full stream. See the--- documentation of individual 'Fold's for termination behavior.------ Definitions:------ >>> fold f = fmap fst . Stream.foldBreak f--- >>> fold f = Stream.parse (Parser.fromFold f)------ Example:------ >>> Stream.fold Fold.sum (Stream.enumerateFromTo 1 100)--- 5050----{-# INLINE_NORMAL fold #-}-fold :: Monad m => Fold m a b -> Stream m a -> m b-fold fld strm = do- (b, _) <- foldBreak fld strm- return b---- | Fold resulting in either breaking the stream or continuation of the fold.--- Instead of supplying the input stream in one go we can run the fold multiple--- times, each time supplying the next segment of the input stream. If the fold--- has not yet finished it returns a fold that can be run again otherwise it--- returns the fold result and the residual stream.------ /Internal/-{-# INLINE_NORMAL foldEither #-}-foldEither :: Monad m =>- Fold m a b -> Stream m a -> m (Either (Fold m a b) (b, Stream m a))-foldEither (Fold fstep begin done) (UnStream step state) = do- res <- begin- case res of- FL.Partial fs -> go SPEC fs state- FL.Done fb -> return $! Right (fb, Stream step state)-- where-- {-# INLINE go #-}- go !_ !fs st = do- r <- step defState st- case r of- Yield x s -> do- res <- fstep fs x- case res of- FL.Done b -> return $! Right (b, Stream step s)- FL.Partial fs1 -> go SPEC fs1 s- Skip s -> go SPEC fs s- Stop -> return $! Left (Fold fstep (return $ FL.Partial fs) done)---- | Like 'fold' but also returns the remaining stream. The resulting stream--- would be 'Stream.nil' if the stream finished before the fold.----{-# INLINE_NORMAL foldBreak #-}-foldBreak :: Monad m => Fold m a b -> Stream m a -> m (b, Stream m a)-foldBreak fld strm = do- r <- foldEither fld strm- case r of- Right res -> return res- Left (Fold _ initial extract) -> do- res <- initial- case res of- FL.Done _ -> error "foldBreak: unreachable state"- FL.Partial s -> do- b <- extract s- return (b, nil)-- where-- nil = Stream (\_ _ -> return Stop) ()---- | Append a stream to a fold lazily to build an accumulator incrementally.------ Example, to continue folding a list of streams on the same sum fold:------ >>> streams = [Stream.fromList [1..5], Stream.fromList [6..10]]--- >>> f = Prelude.foldl Stream.foldAddLazy Fold.sum streams--- >>> Stream.fold f Stream.nil--- 55----{-# INLINE_NORMAL foldAddLazy #-}-foldAddLazy :: Monad m => Fold m a b -> Stream m a -> Fold m a b-foldAddLazy (Fold fstep finitial fextract) (Stream sstep state) =- Fold fstep initial fextract-- where-- initial = do- res <- finitial- case res of- FL.Partial fs -> go SPEC fs state- FL.Done fb -> return $ FL.Done fb-- {-# INLINE go #-}- go !_ !fs st = do- r <- sstep defState st- case r of- Yield x s -> do- res <- fstep fs x- case res of- FL.Done b -> return $ FL.Done b- FL.Partial fs1 -> go SPEC fs1 s- Skip s -> go SPEC fs s- Stop -> return $ FL.Partial fs---- >>> foldAdd f = Stream.foldAddLazy f >=> Fold.reduce---- |--- >>> foldAdd = flip Fold.addStream----foldAdd :: Monad m => Fold m a b -> Stream m a -> m (Fold m a b)-foldAdd f =- Streamly.Internal.Data.Stream.StreamD.Type.fold (FL.duplicate f)----------------------------------------------------------------------------------- Right Folds----------------------------------------------------------------------------------- Adapted from the vector package.------ XXX Use of SPEC constructor in folds causes 2x performance degradation in--- one shot operations, but helps immensely in operations composed of multiple--- combinators or the same combinator many times. There seems to be an--- opportunity to optimize here, can we get both, better perf for single ops--- as well as composed ops? Without SPEC, all single operation benchmarks--- become 2x faster.---- The way we want a left fold to be strict, dually we want the right fold to--- be lazy. The correct signature of the fold function to keep it lazy must be--- (a -> m b -> m b) instead of (a -> b -> m b). We were using the latter--- earlier, which is incorrect. In the latter signature we have to feed the--- value to the fold function after evaluating the monadic action, depending on--- the bind behavior of the monad, the action may get evaluated immediately--- introducing unnecessary strictness to the fold. If the implementation is--- lazy the following example, must work:------ S.foldrM (\x t -> if x then return t else return False) (return True)--- (S.fromList [False,undefined] :: Stream IO Bool)---- | 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 = Stream.fromList (2:4:5:undefined)--- >>> step x xs = if odd x then return True else xs--- >>> Stream.foldrM step (return False) s--- True----{-# INLINE_NORMAL foldrM #-}-foldrM :: Monad m => (a -> m b -> m b) -> m b -> Stream m a -> m b-foldrM f z (Stream step state) = go SPEC state- where- {-# INLINE_LATE go #-}- go !_ st = do- r <- step defState st- case r of- Yield x s -> f x (go SPEC s)- Skip s -> go SPEC s- Stop -> z--{-# INLINE_NORMAL foldrMx #-}-foldrMx :: Monad m- => (a -> m x -> m x) -> m x -> (m x -> m b) -> Stream m a -> m b-foldrMx fstep final convert (Stream step state) = convert $ go SPEC state- where- {-# INLINE_LATE go #-}- go !_ st = do- r <- step defState st- case r of- Yield x s -> fstep x (go SPEC s)- Skip s -> go SPEC s- Stop -> final---- XXX Should we make all argument strict wherever we use SPEC?---- Note that foldr works on pure values, therefore it becomes necessarily--- strict when the monad m is strict. In that case it cannot terminate early,--- it would evaluate all of its input. Though, this should work fine with lazy--- monads. For example, if "any" is implemented using "foldr" instead of--- "foldrM" it performs the same with Identity monad but performs 1000x slower--- with IO monad.---- | 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.------ >>> foldr f z = Stream.foldrM (\a b -> f a <$> b) (return z)------ Note: This is similar to Fold.foldr' (the right fold via left fold), but--- could be more efficient.----{-# INLINE_NORMAL foldr #-}-foldr :: Monad m => (a -> b -> b) -> b -> Stream m a -> m b-foldr f z = foldrM (liftA2 f . return) (return z)---- this performs horribly, should not be used-{-# INLINE_NORMAL foldrS #-}-foldrS- :: Monad m- => (a -> Stream m b -> Stream m b)- -> Stream m b- -> Stream m a- -> Stream m b-foldrS f final (Stream step state) = go SPEC state- where- {-# INLINE_LATE go #-}- go !_ st = concatEffect $ fmap g $ step defState st-- g r =- case r of- Yield x s -> f x (go SPEC s)- Skip s -> go SPEC s- Stop -> final----------------------------------------------------------------------------------- Left Folds----------------------------------------------------------------------------------- XXX run begin action only if the stream is not empty.-{-# INLINE_NORMAL foldlMx' #-}-foldlMx' :: Monad m => (x -> a -> m x) -> m x -> (x -> m b) -> Stream m a -> m b-foldlMx' fstep begin done (Stream step state) =- begin >>= \x -> go SPEC x state- where- -- XXX !acc?- {-# INLINE_LATE go #-}- go !_ acc st = acc `seq` do- r <- step defState st- case r of- Yield x s -> do- acc' <- fstep acc x- go SPEC acc' s- Skip s -> go SPEC acc s- Stop -> done acc--{-# INLINE foldlx' #-}-foldlx' :: Monad m => (x -> a -> x) -> x -> (x -> b) -> Stream m a -> m b-foldlx' fstep begin done =- foldlMx' (\b a -> return (fstep b a)) (return begin) (return . done)---- Adapted from the vector package.--- XXX implement in terms of foldlMx'?-{-# INLINE_NORMAL foldlM' #-}-foldlM' :: Monad m => (b -> a -> m b) -> m b -> Stream m a -> m b-foldlM' fstep mbegin (Stream step state) = do- begin <- mbegin- go SPEC begin state- where- {-# INLINE_LATE go #-}- go !_ acc st = acc `seq` do- r <- step defState st- case r of- Yield x s -> do- acc' <- fstep acc x- go SPEC acc' s- Skip s -> go SPEC acc s- Stop -> return acc--{-# INLINE foldl' #-}-foldl' :: Monad m => (b -> a -> b) -> b -> Stream m a -> m b-foldl' fstep begin = foldlM' (\b a -> return (fstep b a)) (return begin)----------------------------------------------------------------------------------- Special folds----------------------------------------------------------------------------------- >>> drain = mapM_ (\_ -> return ())---- |--- Definitions:------ >>> drain = Stream.fold Fold.drain--- >>> drain = Stream.foldrM (\_ xs -> xs) (return ())------ Run a stream, discarding 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 ()----------------------------------------------------------------------------------- To Containers----------------------------------------------------------------------------------- This toList impl is faster (30% on streaming-benchmarks) than the--- corresponding left fold. The left fold retains an additional argument in the--- recursive loop.------ Core for the right fold loop:------ main_$s$wgo1--- = \ sc_s3e6 sc1_s3e5 ->--- case ># sc1_s3e5 100000# of {--- __DEFAULT ->--- case main_$s$wgo1 sc_s3e6 (+# sc1_s3e5 1#) of------ Core for the left fold loop:------ main_$s$wgo1--- = \ sc_s3oT sc1_s3oS sc2_s3oR ->--- case sc2_s3oR of fs2_a2lw { __DEFAULT ->--- case ># sc1_s3oS 100000# of {--- __DEFAULT ->--- let { wild_a2og = I# sc1_s3oS } in--- main_$s$wgo1--- sc_s3oT (+# sc1_s3oS 1#) (\ x_X9 -> fs2_a2lw (: wild_a2og x_X9));---- |--- Definitions:------ >>> toList = Stream.foldr (:) []--- >>> toList = Stream.fold Fold.toList------ 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.Data.Array" instead.------ Note that this could a bit more efficient compared to @Stream.fold--- Fold.toList@, and it can fuse with pure list consumers.----{-# INLINE_NORMAL toList #-}-toList :: Monad m => Stream m a -> m [a]-toList = Streamly.Internal.Data.Stream.StreamD.Type.foldr (:) []---- Use foldr/build fusion to fuse with list consumers--- This can be useful when using the IsList instance-{-# INLINE_LATE toListFB #-}-toListFB :: (a -> b -> b) -> b -> Stream Identity a -> b-toListFB c n (Stream step state) = go state- where- go st = case runIdentity (step defState st) of- Yield x s -> x `c` go s- Skip s -> go s- Stop -> n--{-# RULES "toList Identity" Streamly.Internal.Data.Stream.StreamD.Type.toList = toListId #-}-{-# INLINE_EARLY toListId #-}-toListId :: Stream Identity a -> Identity [a]-toListId s = Identity $ build (\c n -> toListFB c n s)----------------------------------------------------------------------------------- Multi-stream folds----------------------------------------------------------------------------------- Adapted from the vector package.---- | Compare two streams for equality-{-# INLINE_NORMAL eqBy #-}-eqBy :: Monad m => (a -> b -> Bool) -> Stream m a -> Stream m b -> m Bool-eqBy eq (Stream step1 t1) (Stream step2 t2) = eq_loop0 SPEC t1 t2- where- eq_loop0 !_ s1 s2 = do- r <- step1 defState s1- case r of- Yield x s1' -> eq_loop1 SPEC x s1' s2- Skip s1' -> eq_loop0 SPEC s1' s2- Stop -> eq_null s2-- eq_loop1 !_ x s1 s2 = do- r <- step2 defState s2- case r of- Yield y s2'- | eq x y -> eq_loop0 SPEC s1 s2'- | otherwise -> return False- Skip s2' -> eq_loop1 SPEC x s1 s2'- Stop -> return False-- eq_null s2 = do- r <- step2 defState s2- case r of- Yield _ _ -> return False- Skip s2' -> eq_null s2'- Stop -> return True---- Adapted from the vector package.---- | Compare two streams lexicographically.-{-# INLINE_NORMAL cmpBy #-}-cmpBy- :: Monad m- => (a -> b -> Ordering) -> Stream m a -> Stream m b -> m Ordering-cmpBy cmp (Stream step1 t1) (Stream step2 t2) = cmp_loop0 SPEC t1 t2- where- cmp_loop0 !_ s1 s2 = do- r <- step1 defState s1- case r of- Yield x s1' -> cmp_loop1 SPEC x s1' s2- Skip s1' -> cmp_loop0 SPEC s1' s2- Stop -> cmp_null s2-- cmp_loop1 !_ x s1 s2 = do- r <- step2 defState s2- case r of- Yield y s2' -> case x `cmp` y of- EQ -> cmp_loop0 SPEC s1 s2'- c -> return c- Skip s2' -> cmp_loop1 SPEC x s1 s2'- Stop -> return GT-- cmp_null s2 = do- r <- step2 defState s2- case r of- Yield _ _ -> return LT- Skip s2' -> cmp_null s2'- Stop -> return EQ----------------------------------------------------------------------------------- Transformations----------------------------------------------------------------------------------- Adapted from the vector package.---- |--- >>> mapM f = Stream.sequence . fmap f------ Apply a monadic function to each element of the stream and replace it with--- the output of the resulting action.------ >>> s = Stream.fromList ["a", "b", "c"]--- >>> Stream.fold Fold.drain $ Stream.mapM putStr s--- abc----{-# INLINE_NORMAL mapM #-}-mapM :: Monad m => (a -> m b) -> Stream m a -> Stream m b-mapM f (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 -> f x >>= \a -> return $ Yield a s- Skip s -> return $ Skip s- Stop -> return Stop--{-# INLINE map #-}-map :: Monad m => (a -> b) -> Stream m a -> Stream m b-map f = mapM (return . f)---- (Functor m) based implementation of fmap does not fuse well in--- streaming-benchmarks. XXX need to investigate why.-instance Monad m => Functor (Stream m) where- {-# INLINE fmap #-}- fmap = map-- {-# INLINE (<$) #-}- (<$) = fmap . const----------------------------------------------------------------------------------- Lists----------------------------------------------------------------------------------- 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?--instance IsList (Stream Identity a) where- type (Item (Stream Identity a)) = a-- {-# INLINE fromList #-}- fromList = Streamly.Internal.Data.Stream.StreamD.Type.fromList-- {-# INLINE toList #-}- toList = runIdentity . Streamly.Internal.Data.Stream.StreamD.Type.toList--instance Eq a => Eq (Stream Identity a) where- {-# INLINE (==) #-}- (==) xs ys = runIdentity $ eqBy (==) xs ys--instance Ord a => Ord (Stream Identity a) where- {-# INLINE compare #-}- compare xs ys = runIdentity $ 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 (GHC.Exts.toList dl)--instance Read a => Read (Stream Identity a) where- readPrec = parens $ prec 10 $ do- Ident "fromList" <- lexP- Streamly.Internal.Data.Stream.StreamD.Type.fromList <$> readPrec-- readListPrec = readListPrecDefault--instance (a ~ Char) => IsString (Stream Identity a) where- {-# INLINE fromString #-}- fromString = Streamly.Internal.Data.Stream.StreamD.Type.fromList------------------------------------------------------------------------------------ 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.---- There is no Traversable instance because, there is no scalable cons for--- StreamD, use toList and fromList instead.--instance (Foldable m, Monad m) => Foldable (Stream m) where-- {-# INLINE foldMap #-}- foldMap f =- Data.Foldable.fold- . Streamly.Internal.Data.Stream.StreamD.Type.foldr (mappend . f) mempty-- {-# INLINE foldr #-}- foldr f z t = appEndo (foldMap (Endo #. f) t) z-- {-# INLINE foldl' #-}- foldl' f z0 xs = Data.Foldable.foldr f' id xs z0- where f' x k = oneShot $ \z -> k $! f z x-- {-# INLINE length #-}- length = Data.Foldable.foldl' (\n _ -> n + 1) 0-- {-# INLINE elem #-}- elem = any . (==)-- {-# INLINE maximum #-}- maximum =- fromMaybe (errorWithoutStackTrace "maximum: empty stream")- . toMaybe- . Data.Foldable.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- . Data.Foldable.foldl' getMin Nothing'-- where-- getMin Nothing' x = Just' x- getMin (Just' mn) x = Just' $! min mn x-- {-# INLINE sum #-}- sum = Data.Foldable.foldl' (+) 0-- {-# INLINE product #-}- product = Data.Foldable.foldl' (*) 1------------------------------------------------------------------------------------ Filtering------------------------------------------------------------------------------------ Adapted from the vector package.---- | Take first 'n' elements from the stream and discard the rest.----{-# INLINE_NORMAL take #-}-take :: Applicative m => Int -> Stream m a -> Stream m a-take n (Stream step state) = n `seq` Stream step' (state, 0)-- where-- {-# INLINE_LATE step' #-}- step' gst (st, i) | i < n = do- (\case- Yield x s -> Yield x (s, i + 1)- Skip s -> Skip (s, i)- Stop -> Stop) <$> step gst st- step' _ (_, _) = pure Stop---- Adapted from the vector package.---- | Same as 'takeWhile' but with a monadic predicate.----{-# INLINE_NORMAL takeWhileM #-}-takeWhileM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a--- takeWhileM p = scanMaybe (FL.takingEndByM_ (\x -> not <$> p x))-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---- | End the stream as soon as the predicate fails on an element.----{-# INLINE takeWhile #-}-takeWhile :: Monad m => (a -> Bool) -> Stream m a -> Stream m a-takeWhile f = takeWhileM (return . f)---- Like takeWhile but with an inverted condition and also taking--- the matching element.--{-# INLINE_NORMAL takeEndByM #-}-takeEndByM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a-takeEndByM f (Stream step state) = Stream step' (Just state)- where- {-# INLINE_LATE step' #-}- step' gst (Just st) = do- r <- step gst st- case r of- Yield x s -> do- b <- f x- return $- if not b- then Yield x (Just s)- else Yield x Nothing- Skip s -> return $ Skip (Just s)- Stop -> return Stop-- step' _ Nothing = return Stop--{-# INLINE takeEndBy #-}-takeEndBy :: Monad m => (a -> Bool) -> Stream m a -> Stream m a-takeEndBy f = takeEndByM (return . f)----------------------------------------------------------------------------------- Zipping----------------------------------------------------------------------------------- | Like 'zipWith' but using a monadic zipping function.----{-# 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--{-# RULES "zipWithM xs xs"- forall f xs. zipWithM @Identity f xs xs = mapM (\x -> f x x) xs #-}---- | Stream @a@ is evaluated first, followed by stream @b@, the resulting--- elements @a@ and @b@ are then zipped using the supplied zip function and the--- result @c@ is yielded to the consumer.------ If stream @a@ or stream @b@ ends, the zipped stream ends. If stream @b@ ends--- first, the element @a@ from previous evaluation of stream @a@ is discarded.------ >>> s1 = Stream.fromList [1,2,3]--- >>> s2 = Stream.fromList [4,5,6]--- >>> Stream.fold Fold.toList $ Stream.zipWith (+) s1 s2--- [5,7,9]----{-# 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))----------------------------------------------------------------------------------- Combine N Streams - concatAp----------------------------------------------------------------------------------- | Apply a stream of functions to a stream of values and flatten the results.------ Note that the second stream is evaluated multiple times.------ >>> crossApply = Stream.crossWith id----{-# INLINE_NORMAL crossApply #-}-crossApply :: Functor f => Stream f (a -> b) -> Stream f a -> Stream f b-crossApply (Stream stepa statea) (Stream stepb stateb) =- Stream step' (Left statea)-- where-- {-# INLINE_LATE step' #-}- step' gst (Left st) = fmap- (\case- 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- (\case- 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 crossApplySnd #-}-crossApplySnd :: Functor f => Stream f a -> Stream f b -> Stream f b-crossApplySnd (Stream stepa statea) (Stream stepb stateb) =- Stream step (Left statea)-- where-- {-# INLINE_LATE step #-}- step gst (Left st) =- fmap- (\case- Yield _ s -> Skip (Right (s, stateb))- Skip s -> Skip (Left s)- Stop -> Stop)- (stepa (adaptState gst) st)- step gst (Right (ostate, st)) =- fmap- (\case- Yield b s -> Yield b (Right (ostate, s))- Skip s -> Skip (Right (ostate, s))- Stop -> Skip (Left ostate))- (stepb gst st)--{-# INLINE_NORMAL crossApplyFst #-}-crossApplyFst :: Functor f => Stream f a -> Stream f b -> Stream f a-crossApplyFst (Stream stepa statea) (Stream stepb stateb) =- Stream step (Left statea)-- where-- {-# INLINE_LATE step #-}- step gst (Left st) =- fmap- (\case- Yield b s -> Skip (Right (s, stateb, b))- Skip s -> Skip (Left s)- Stop -> Stop)- (stepa gst st)- step gst (Right (ostate, st, b)) =- fmap- (\case- Yield _ s -> Yield b (Right (ostate, s, b))- Skip s -> Skip (Right (ostate, s, b))- Stop -> Skip (Left ostate))- (stepb (adaptState gst) st)--{--instance Applicative f => Applicative (Stream f) where- {-# INLINE pure #-}- pure = fromPure-- {-# INLINE (<*>) #-}- (<*>) = crossApply-- {-# INLINE liftA2 #-}- liftA2 f x = (<*>) (fmap f x)-- {-# INLINE (*>) #-}- (*>) = crossApplySnd-- {-# INLINE (<*) #-}- (<*) = crossApplyFst--}---- |--- Definition:------ >>> crossWith f m1 m2 = fmap f m1 `Stream.crossApply` m2------ Note that the second stream is evaluated multiple times.----{-# INLINE crossWith #-}-crossWith :: Monad m => (a -> b -> c) -> Stream m a -> Stream m b -> Stream m c-crossWith f m1 m2 = fmap f m1 `crossApply` m2---- | Given a @Stream m a@ and @Stream m b@ generate a stream with all possible--- combinations of the tuple @(a, b)@.------ Definition:------ >>> cross = Stream.crossWith (,)------ The second stream is evaluated multiple times. If that is not desired it can--- be cached in an 'Data.Array.Array' and then generated from the array before--- calling this function. Caching may also improve performance if the stream is--- expensive to evaluate.------ See 'Streamly.Internal.Data.Unfold.cross' for a much faster fused--- alternative.------ Time: O(m x n)------ /Pre-release/-{-# INLINE cross #-}-cross :: Monad m => Stream m a -> Stream m b -> Stream m (a, b)-cross = crossWith (,)----------------------------------------------------------------------------------- Combine N Streams - unfoldMany---------------------------------------------------------------------------------{-# ANN type ConcatMapUState Fuse #-}-data ConcatMapUState o i =- ConcatMapUOuter o- | ConcatMapUInner o i---- | @unfoldMany 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'.---- | 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.----{-# INLINE_NORMAL unfoldMany #-}-unfoldMany :: Monad m => Unfold m a b -> Stream m a -> Stream m b-unfoldMany (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)----------------------------------------------------------------------------------- Combine N Streams - concatMap----------------------------------------------------------------------------------- Adapted from the vector package.---- | 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.------ See 'unfoldMany' for a fusible alternative.----{-# INLINE_NORMAL concatMapM #-}-concatMapM :: Monad m => (a -> m (Stream m b)) -> Stream m a -> Stream m b-concatMapM f (Stream step state) = Stream step' (Left state)- where- {-# INLINE_LATE step' #-}- step' gst (Left st) = do- r <- step (adaptState gst) st- case r of- Yield a s -> do- b_stream <- f a- return $ Skip (Right (b_stream, s))- Skip s -> return $ Skip (Left s)- Stop -> return Stop-- -- XXX flattenArrays is 5x faster than "concatMap fromArray". if somehow we- -- can get inner_step to inline and fuse here we can perhaps get the same- -- performance using "concatMap fromArray".- --- -- XXX using the pattern synonym "Stream" causes a major performance issue- -- here even if the synonym does not include an adaptState call. Need to- -- find out why. Is that something to be fixed in GHC?- step' gst (Right (UnStream inner_step inner_st, st)) = do- r <- inner_step (adaptState gst) inner_st- case r of- Yield b inner_s ->- return $ Yield b (Right (Stream inner_step inner_s, st))- Skip inner_s ->- return $ Skip (Right (Stream inner_step inner_s, st))- Stop -> return $ Skip (Left st)---- | Map a stream producing function on each element of the stream and then--- flatten the results into a single stream.------ >>> concatMap f = Stream.concatMapM (return . f)--- >>> concatMap f = Stream.concat . fmap f--- >>> concatMap f = Stream.unfoldMany (Unfold.lmap f Unfold.fromStream)------ See 'unfoldMany' for a fusible alternative.----{-# INLINE concatMap #-}-concatMap :: Monad m => (a -> Stream m b) -> Stream m a -> Stream m b-concatMap f = concatMapM (return . f)---- | Flatten a stream of streams to a single stream.------ >>> concat = Stream.concatMap id------ /Pre-release/-{-# INLINE concat #-}-concat :: Monad m => Stream m (Stream m a) -> Stream m a-concat = concatMap id---- XXX The idea behind this rule is to rewrite any calls to "concatMap--- fromArray" automatically to flattenArrays which is much faster. However, we--- need an INLINE_EARLY on concatMap for this rule to fire. But if we use--- INLINE_EARLY on concatMap or fromArray then direct uses of--- "concatMap fromArray" (without the RULE) become much slower, this means--- "concatMap f" in general would become slower. Need to find a solution to--- this.------ {-# RULES "concatMap Array.toStreamD"--- concatMap Array.toStreamD = Array.flattenArray #-}---- >>> concatEffect = Stream.concat . lift -- requires (MonadTrans t)--- >>> concatEffect = join . lift -- requires (MonadTrans t, Monad (Stream m))---- | Given a stream value in the underlying monad, lift and join the underlying--- monad with the stream monad.------ >>> concatEffect = Stream.concat . Stream.fromEffect--- >>> concatEffect eff = Stream.concatMapM (\() -> eff) (Stream.fromPure ())------ See also: 'concat', 'sequence'----{-# INLINE concatEffect #-}-concatEffect :: Monad m => m (Stream m a) -> Stream m a-concatEffect generator = concatMapM (\() -> generator) (fromPure ())--{---- 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- {-# INLINE return #-}- return = pure-- {-# INLINE (>>=) #-}- (>>=) = flip concatMap-- {-# INLINE (>>) #-}- (>>) = (*>)--}----------------------------------------------------------------------------------- Traversing a tree top down----------------------------------------------------------------------------------- Next stream is to be generated by the return value of the previous stream. A--- general intuitive way of doing that could be to use an appending monad--- instance for streams where the result of the previous stream is used to--- generate the next one. In the first pass we can just emit the values in the--- stream and keep building a buffered list/stream, once done we can then--- process the buffered stream.---- | Generate a stream from an initial state, scan and concat the stream,--- generate a stream again from the final state of the previous scan and repeat--- the process.-{-# INLINE_NORMAL concatIterateScan #-}-concatIterateScan :: Monad m =>- (b -> a -> m b)- -> (b -> m (Maybe (b, Stream m a)))- -> b- -> Stream m a-concatIterateScan scanner generate initial = Stream step (Left initial)-- where-- {-# INLINE_LATE step #-}- step _ (Left acc) = do- r <- generate acc- case r of- Nothing -> return Stop- Just v -> return $ Skip (Right v)-- step gst (Right (st, UnStream inner_step inner_st)) = do- r <- inner_step (adaptState gst) inner_st- case r of- Yield b inner_s -> do- acc <- scanner st b- return $ Yield b (Right (acc, Stream inner_step inner_s))- Skip inner_s ->- return $ Skip (Right (st, Stream inner_step inner_s))- Stop -> return $ Skip (Left st)---- Note: The iterate function returns a Maybe Stream instead of returning a nil--- stream for indicating a leaf node. This is to optimize so that we do not--- have to store any state. This makes the stored state proportional to the--- number of non-leaf nodes rather than total number of nodes.---- | Same as 'concatIterateBfs' except that the traversal of the last--- element on a level is emitted first and then going backwards up to the first--- element (reversed ordering). This may be slightly faster than--- 'concatIterateBfs'.----{-# INLINE_NORMAL concatIterateBfsRev #-}-concatIterateBfsRev :: Monad m =>- (a -> Maybe (Stream m a))- -> Stream m a- -> Stream m a-concatIterateBfsRev f stream = Stream step (stream, [])-- where-- {-# INLINE_LATE step #-}- step gst (UnStream step1 st, xs) = do- r <- step1 (adaptState gst) st- case r of- Yield a s -> do- let xs1 =- case f a of- Nothing -> xs- Just x -> x:xs- return $ Yield a (Stream step1 s, xs1)- Skip s -> return $ Skip (Stream step1 s, xs)- Stop ->- case xs of- (y:ys) -> return $ Skip (y, ys)- [] -> return Stop---- | Similar to 'concatIterateDfs' except that it traverses the stream in--- breadth first style (BFS). First, all the elements in the input stream are--- emitted, and then their traversals are emitted.------ Example, list a directory tree using BFS:------ >>> f = either (Just . Dir.readEitherPaths) (const Nothing)--- >>> input = Stream.fromPure (Left ".")--- >>> ls = Stream.concatIterateBfs f input------ /Pre-release/-{-# INLINE_NORMAL concatIterateBfs #-}-concatIterateBfs :: Monad m =>- (a -> Maybe (Stream m a))- -> Stream m a- -> Stream m a-concatIterateBfs f stream = Stream step (stream, [], [])-- where-- {-# INLINE_LATE step #-}- step gst (UnStream step1 st, xs, ys) = do- r <- step1 (adaptState gst) st- case r of- Yield a s -> do- let ys1 =- case f a of- Nothing -> ys- Just y -> y:ys- return $ Yield a (Stream step1 s, xs, ys1)- Skip s -> return $ Skip (Stream step1 s, xs, ys)- Stop ->- case xs of- (x:xs1) -> return $ Skip (x, xs1, ys)- [] ->- case reverse ys of- (x:xs1) -> return $ Skip (x, xs1, [])- [] -> return Stop---- | Traverse the stream in depth first style (DFS). Map each element in the--- input stream to a stream and flatten, recursively map the resulting elements--- as well to a stream and flatten until no more streams are generated.------ Example, list a directory tree using DFS:------ >>> f = either (Just . Dir.readEitherPaths) (const Nothing)--- >>> input = Stream.fromPure (Left ".")--- >>> ls = Stream.concatIterateDfs f input------ This is equivalent to using @concatIterateWith StreamK.append@.------ /Pre-release/-{-# INLINE_NORMAL concatIterateDfs #-}-concatIterateDfs :: Monad m =>- (a -> Maybe (Stream m a))- -> Stream m a- -> Stream m a-concatIterateDfs f stream = Stream step (stream, [])-- where-- {-# INLINE_LATE step #-}- step gst (UnStream step1 st, xs) = do- r <- step1 (adaptState gst) st- case r of- Yield a s -> do- let st1 =- case f a of- Nothing -> (Stream step1 s, xs)- Just x -> (x, Stream step1 s:xs)- return $ Yield a st1- Skip s -> return $ Skip (Stream step1 s, xs)- Stop ->- case xs of- (y:ys) -> return $ Skip (y, ys)- [] -> return Stop--{-# ANN type IterateUnfoldState Fuse #-}-data IterateUnfoldState o i =- IterateUnfoldOuter o- | IterateUnfoldInner o i [i]---- | Same as @concatIterateDfs@ but more efficient due to stream fusion.------ Example, list a directory tree using DFS:------ >>> f = Unfold.either Dir.eitherReaderPaths Unfold.nil--- >>> input = Stream.fromPure (Left ".")--- >>> ls = Stream.unfoldIterateDfs f input------ /Pre-release/-{-# INLINE_NORMAL unfoldIterateDfs #-}-unfoldIterateDfs :: Monad m =>- Unfold m a a- -> Stream m a- -> Stream m a-unfoldIterateDfs (Unfold istep inject) (Stream ostep ost) =- Stream step (IterateUnfoldOuter ost)-- where-- {-# INLINE_LATE step #-}- step gst (IterateUnfoldOuter o) = do- r <- ostep (adaptState gst) o- case r of- Yield a s -> do- i <- inject a- i `seq` return (Yield a (IterateUnfoldInner s i []))- Skip s -> return $ Skip (IterateUnfoldOuter s)- Stop -> return Stop-- step _ (IterateUnfoldInner o i ii) = do- r <- istep i- case r of- Yield x s -> do- i1 <- inject x- i1 `seq` return $ Yield x (IterateUnfoldInner o i1 (s:ii))- Skip s -> return $ Skip (IterateUnfoldInner o s ii)- Stop ->- case ii of- (y:ys) -> return $ Skip (IterateUnfoldInner o y ys)- [] -> return $ Skip (IterateUnfoldOuter o)--{-# ANN type IterateUnfoldBFSRevState Fuse #-}-data IterateUnfoldBFSRevState o i =- IterateUnfoldBFSRevOuter o [i]- | IterateUnfoldBFSRevInner i [i]---- | Like 'unfoldIterateBfs' but processes the children in reverse order,--- therefore, may be slightly faster.------ /Pre-release/-{-# INLINE_NORMAL unfoldIterateBfsRev #-}-unfoldIterateBfsRev :: Monad m =>- Unfold m a a- -> Stream m a- -> Stream m a-unfoldIterateBfsRev (Unfold istep inject) (Stream ostep ost) =- Stream step (IterateUnfoldBFSRevOuter ost [])-- where-- {-# INLINE_LATE step #-}- step gst (IterateUnfoldBFSRevOuter o ii) = do- r <- ostep (adaptState gst) o- case r of- Yield a s -> do- i <- inject a- i `seq` return (Yield a (IterateUnfoldBFSRevOuter s (i:ii)))- Skip s -> return $ Skip (IterateUnfoldBFSRevOuter s ii)- Stop ->- case ii of- (y:ys) -> return $ Skip (IterateUnfoldBFSRevInner y ys)- [] -> return Stop-- step _ (IterateUnfoldBFSRevInner i ii) = do- r <- istep i- case r of- Yield x s -> do- i1 <- inject x- i1 `seq` return $ Yield x (IterateUnfoldBFSRevInner s (i1:ii))- Skip s -> return $ Skip (IterateUnfoldBFSRevInner s ii)- Stop ->- case ii of- (y:ys) -> return $ Skip (IterateUnfoldBFSRevInner y ys)- [] -> return Stop--{-# ANN type IterateUnfoldBFSState Fuse #-}-data IterateUnfoldBFSState o i =- IterateUnfoldBFSOuter o [i]- | IterateUnfoldBFSInner i [i] [i]---- | Like 'unfoldIterateDfs' but uses breadth first style traversal.------ /Pre-release/-{-# INLINE_NORMAL unfoldIterateBfs #-}-unfoldIterateBfs :: Monad m =>- Unfold m a a- -> Stream m a- -> Stream m a-unfoldIterateBfs (Unfold istep inject) (Stream ostep ost) =- Stream step (IterateUnfoldBFSOuter ost [])-- where-- {-# INLINE_LATE step #-}- step gst (IterateUnfoldBFSOuter o rii) = do- r <- ostep (adaptState gst) o- case r of- Yield a s -> do- i <- inject a- i `seq` return (Yield a (IterateUnfoldBFSOuter s (i:rii)))- Skip s -> return $ Skip (IterateUnfoldBFSOuter s rii)- Stop ->- case reverse rii of- (y:ys) -> return $ Skip (IterateUnfoldBFSInner y ys [])- [] -> return Stop-- step _ (IterateUnfoldBFSInner i ii rii) = do- r <- istep i- case r of- Yield x s -> do- i1 <- inject x- i1 `seq` return $ Yield x (IterateUnfoldBFSInner s ii (i1:rii))- Skip s -> return $ Skip (IterateUnfoldBFSInner s ii rii)- Stop ->- case ii of- (y:ys) -> return $ Skip (IterateUnfoldBFSInner y ys rii)- [] ->- case reverse rii of- (y:ys) -> return $ Skip (IterateUnfoldBFSInner y ys [])- [] -> return Stop----------------------------------------------------------------------------------- Folding a tree bottom up----------------------------------------------------------------------------------- | Binary BFS style reduce, folds a level entirely using the supplied fold--- function, collecting the outputs as next level of the tree, then repeats the--- same process on the next level. The last elements of a previously folded--- level are folded first.-{-# INLINE_NORMAL reduceIterateBfs #-}-reduceIterateBfs :: Monad m =>- (a -> a -> m a) -> Stream m a -> m (Maybe a)-reduceIterateBfs f (Stream step state) = go SPEC state [] Nothing-- where-- go _ st xs Nothing = do- r <- step defState st- case r of- Yield x1 s -> go SPEC s xs (Just x1)- Skip s -> go SPEC s xs Nothing- Stop ->- case xs of- [] -> return Nothing- _ -> goBuf SPEC xs []- go _ st xs (Just x1) = do- r2 <- step defState st- case r2 of- Yield x2 s -> do- x <- f x1 x2- go SPEC s (x:xs) Nothing- Skip s -> go SPEC s xs (Just x1)- Stop ->- case xs of- [] -> return (Just x1)- _ -> goBuf SPEC (x1:xs) []-- goBuf _ [] ys = goBuf SPEC ys []- goBuf _ [x1] ys = do- case ys of- [] -> return (Just x1)- (x2:xs) -> do- y <- f x1 x2- goBuf SPEC xs [y]- goBuf _ (x1:x2:xs) ys = do- y <- f x1 x2- goBuf SPEC xs (y:ys)---- | N-Ary BFS style iterative fold, if the input stream finished before the--- fold then it returns Left otherwise Right. If the fold returns Left we--- terminate.------ /Unimplemented/-foldIterateBfs ::- Fold m a (Either a a) -> Stream m a -> m (Maybe a)-foldIterateBfs = undefined----------------------------------------------------------------------------------- Grouping/Splitting----------------------------------------------------------------------------------- s = stream state, fs = fold state-{-# ANN type FoldManyPost Fuse #-}-data FoldManyPost s fs b a- = FoldManyPostStart s- | FoldManyPostLoop s fs- | FoldManyPostYield b (FoldManyPost s fs b a)- | FoldManyPostDone---- XXX Need a more intuitive name, and need to reconcile the names--- foldMany/fold/parse/parseMany/parseManyPost etc.---- XXX foldManyPost keeps the last fold always partial. if the last fold is--- complete then another fold is applied on empty input. This is used for--- applying folds like takeEndBy such that the last element is not the--- separator (infix style). But that looks like a hack. We should remove this--- and use a custom combinator for infix parsing.---- | Like 'foldMany' but evaluates the fold even if the fold did not receive--- any input, therefore, always results in a non-empty output even on an empty--- stream (default result of the fold).------ Example, empty stream:------ >>> f = Fold.take 2 Fold.sum--- >>> fmany = Stream.fold Fold.toList . Stream.foldManyPost f--- >>> fmany $ Stream.fromList []--- [0]------ Example, last fold empty:------ >>> fmany $ Stream.fromList [1..4]--- [3,7,0]------ Example, last fold non-empty:------ >>> fmany $ Stream.fromList [1..5]--- [3,7,5]------ Note that using a closed fold e.g. @Fold.take 0@, would result in an--- infinite stream without consuming the input.------ /Pre-release/----{-# INLINE_NORMAL foldManyPost #-}-foldManyPost :: Monad m => Fold m a b -> Stream m a -> Stream m b-foldManyPost (Fold fstep initial extract) (Stream step state) =- Stream step' (FoldManyPostStart state)-- where-- {-# INLINE consume #-}- consume x s fs = do- res <- fstep fs x- return- $ Skip- $ case res of- FL.Done b -> FoldManyPostYield b (FoldManyPostStart s)- FL.Partial ps -> FoldManyPostLoop s ps-- {-# INLINE_LATE step' #-}- step' _ (FoldManyPostStart st) = do- r <- initial- return- $ Skip- $ case r of- FL.Done b -> FoldManyPostYield b (FoldManyPostStart st)- FL.Partial fs -> FoldManyPostLoop st fs- step' gst (FoldManyPostLoop st fs) = do- r <- step (adaptState gst) st- case r of- Yield x s -> consume x s fs- Skip s -> return $ Skip (FoldManyPostLoop s fs)- Stop -> do- b <- extract fs- return $ Skip (FoldManyPostYield b FoldManyPostDone)- step' _ (FoldManyPostYield b next) = return $ Yield b next- step' _ FoldManyPostDone = return Stop--{-# ANN type FoldMany Fuse #-}-data FoldMany s fs b a- = FoldManyStart s- | FoldManyFirst fs s- | FoldManyLoop s fs- | FoldManyYield b (FoldMany s fs b a)- | FoldManyDone---- XXX Nested foldMany does not fuse.---- | Apply a 'Fold' repeatedly on a stream and emit the results in the output--- stream.------ Definition:------ >>> foldMany f = Stream.parseMany (Parser.fromFold f)------ Example, empty stream:------ >>> f = Fold.take 2 Fold.sum--- >>> fmany = Stream.fold Fold.toList . Stream.foldMany f--- >>> fmany $ Stream.fromList []--- []------ Example, last fold empty:------ >>> fmany $ Stream.fromList [1..4]--- [3,7]------ Example, last fold non-empty:------ >>> fmany $ Stream.fromList [1..5]--- [3,7,5]------ Note that using a closed fold e.g. @Fold.take 0@, would result in an--- infinite stream on a non-empty input stream.----{-# INLINE_NORMAL foldMany #-}-foldMany :: Monad m => Fold m a b -> Stream m a -> Stream m b-foldMany (Fold fstep initial extract) (Stream step state) =- Stream step' (FoldManyStart state)-- where-- {-# INLINE consume #-}- consume x s fs = do- res <- fstep fs x- return- $ Skip- $ case res of- FL.Done b -> FoldManyYield b (FoldManyStart s)- FL.Partial ps -> FoldManyLoop s ps-- {-# INLINE_LATE step' #-}- step' _ (FoldManyStart st) = do- r <- initial- return- $ Skip- $ case r of- FL.Done b -> FoldManyYield b (FoldManyStart st)- FL.Partial fs -> FoldManyFirst fs st- step' gst (FoldManyFirst fs st) = do- r <- step (adaptState gst) st- case r of- Yield x s -> consume x s fs- Skip s -> return $ Skip (FoldManyFirst fs s)- Stop -> return Stop- step' gst (FoldManyLoop st fs) = do- r <- step (adaptState gst) st- case r of- Yield x s -> consume x s fs- Skip s -> return $ Skip (FoldManyLoop s fs)- Stop -> do- b <- extract fs- return $ Skip (FoldManyYield b FoldManyDone)- step' _ (FoldManyYield b next) = return $ Yield b next- step' _ FoldManyDone = return Stop--{-# INLINE groupsOf #-}-groupsOf :: Monad m => Int -> Fold m a b -> Stream m a -> Stream m b-groupsOf n f = foldMany (FL.take n f)---- Keep the argument order consistent with refoldIterateM.---- | Like 'foldMany' but for the 'Refold' type. The supplied action is used as--- the initial value for each refold.------ /Internal/-{-# INLINE_NORMAL refoldMany #-}-refoldMany :: Monad m => Refold m x a b -> m x -> Stream m a -> Stream m b-refoldMany (Refold fstep inject extract) action (Stream step state) =- Stream step' (FoldManyStart state)-- where-- {-# INLINE consume #-}- consume x s fs = do- res <- fstep fs x- return- $ Skip- $ case res of- FL.Done b -> FoldManyYield b (FoldManyStart s)- FL.Partial ps -> FoldManyLoop s ps-- {-# INLINE_LATE step' #-}- step' _ (FoldManyStart st) = do- r <- action >>= inject- return- $ Skip- $ case r of- FL.Done b -> FoldManyYield b (FoldManyStart st)- FL.Partial fs -> FoldManyFirst fs st- step' gst (FoldManyFirst fs st) = do- r <- step (adaptState gst) st- case r of- Yield x s -> consume x s fs- Skip s -> return $ Skip (FoldManyFirst fs s)- Stop -> return Stop- step' gst (FoldManyLoop st fs) = do- r <- step (adaptState gst) st- case r of- Yield x s -> consume x s fs- Skip s -> return $ Skip (FoldManyLoop s fs)- Stop -> do- b <- extract fs- return $ Skip (FoldManyYield b FoldManyDone)- step' _ (FoldManyYield b next) = return $ Yield b next- step' _ FoldManyDone = return Stop----------------------------------------------------------------------------------- Stream with a cross product style monad instance----------------------------------------------------------------------------------- XXX CrossStream performs better than the CrossStreamK when nesting two--- loops, however, CrossStreamK seems to be better for more than two nestings,--- need to do more perf investigation.---- | A newtype wrapper for the 'Stream' type with a cross product style monad--- instance.------ A 'Monad' bind behaves like a @for@ loop:------ >>> :{--- Stream.fold Fold.toList $ Stream.unCross $ do--- x <- Stream.mkCross $ Stream.fromList [1,2]--- -- Perform the following actions for each x in the stream--- return x--- :}--- [1,2]------ Nested monad binds behave like nested @for@ loops:------ >>> :{--- Stream.fold Fold.toList $ Stream.unCross $ do--- x <- Stream.mkCross $ Stream.fromList [1,2]--- y <- Stream.mkCross $ Stream.fromList [3,4]--- -- Perform the following actions for each x, for each y--- return (x, y)--- :}--- [(1,3),(1,4),(2,3),(2,4)]----newtype CrossStream m a = CrossStream {unCrossStream :: Stream m a}- deriving (Functor, Foldable)--{-# INLINE mkCross #-}-mkCross :: Stream m a -> CrossStream m a-mkCross = CrossStream--{-# INLINE unCross #-}-unCross :: CrossStream m a -> Stream m a-unCross = unCrossStream---- Pure (Identity monad) stream instances-deriving instance IsList (CrossStream Identity a)-deriving instance (a ~ Char) => IsString (CrossStream Identity a)-deriving instance Eq a => Eq (CrossStream Identity a)-deriving instance Ord a => Ord (CrossStream Identity a)---- Do not use automatic derivation for this to show as "fromList" rather than--- "fromList Identity".-instance Show a => Show (CrossStream Identity a) where- {-# INLINE show #-}- show (CrossStream xs) = show xs--instance Read a => Read (CrossStream Identity a) where- {-# INLINE readPrec #-}- readPrec = fmap CrossStream readPrec----------------------------------------------------------------------------------- Applicative----------------------------------------------------------------------------------- Note: we need to define all the typeclass operations because we want to--- INLINE them.-instance Monad m => Applicative (CrossStream m) where- {-# INLINE pure #-}- pure x = CrossStream (fromPure x)-- {-# INLINE (<*>) #-}- (CrossStream s1) <*> (CrossStream s2) =- CrossStream (crossApply s1 s2)-- {-# INLINE liftA2 #-}- liftA2 f x = (<*>) (fmap f x)-- {-# INLINE (*>) #-}- (CrossStream s1) *> (CrossStream s2) =- CrossStream (crossApplySnd s1 s2)-- {-# INLINE (<*) #-}- (CrossStream s1) <* (CrossStream s2) =- CrossStream (crossApplyFst s1 s2)----------------------------------------------------------------------------------- Monad---------------------------------------------------------------------------------instance Monad m => Monad (CrossStream m) where- return = pure-- -- Benchmarks better with StreamD bind and pure:- -- toList, filterAllout, *>, *<, >> (~2x)- ---- -- Benchmarks better with CPS bind and pure:- -- Prime sieve (25x)- -- n binds, breakAfterSome, filterAllIn, state transformer (~2x)- --- {-# INLINE (>>=) #-}- (>>=) (CrossStream m) f = CrossStream (concatMap (unCrossStream . f) m)-- {-# INLINE (>>) #-}- (>>) = (*>)----------------------------------------------------------------------------------- Transformers---------------------------------------------------------------------------------instance (MonadIO m) => MonadIO (CrossStream m) where- liftIO x = CrossStream (fromEffect $ liftIO x)--instance MonadTrans CrossStream where- {-# INLINE lift #-}- lift x = CrossStream (fromEffect x)--instance (MonadThrow m) => MonadThrow (CrossStream m) where- throwM = lift . throwM
− src/Streamly/Internal/Data/Stream/StreamDK.hs
@@ -1,52 +0,0 @@--- |--- Module : Streamly.Internal.Data.Stream.StreamDK--- Copyright : (c) 2019 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC--------- This module has the following problems due to rewrite rules:------ * Rewrite rules lead to optimization problems, blocking fusion in some--- cases, specifically when combining multiple operations e.g. (filter . drop).--- * Rewrite rules lead to problems when calling a function recursively. For--- example, the StreamD version of foldBreak cannot be used recursively when--- wrapped in rewrite rules because each recursive call adds a roundtrip--- conversion from D to K and back to D. We can use the StreamK versions of--- these though because the rewrite rule gets eliminated in that case.--- * If we have a unified module, we need two different versions of several--- operations e.g. appendK and appendD, both are useful in different cases.----module Streamly.Internal.Data.Stream.StreamDK- ( module Streamly.Internal.Data.Stream.Type- , module Streamly.Internal.Data.Stream.Bottom- , module Streamly.Internal.Data.Stream.Eliminate- , module Streamly.Internal.Data.Stream.Exception- , module Streamly.Internal.Data.Stream.Expand- , module Streamly.Internal.Data.Stream.Generate- , module Streamly.Internal.Data.Stream.Lift- , module Streamly.Internal.Data.Stream.Reduce- , module Streamly.Internal.Data.Stream.Transform- , module Streamly.Internal.Data.Stream.Cross- , module Streamly.Internal.Data.Stream.Zip-- -- modules having dependencies on libraries other than base- , module Streamly.Internal.Data.Stream.Transformer- )-where--import Streamly.Internal.Data.Stream.Bottom-import Streamly.Internal.Data.Stream.Cross-import Streamly.Internal.Data.Stream.Eliminate-import Streamly.Internal.Data.Stream.Exception-import Streamly.Internal.Data.Stream.Expand-import Streamly.Internal.Data.Stream.Generate-import Streamly.Internal.Data.Stream.Lift-import Streamly.Internal.Data.Stream.Reduce-import Streamly.Internal.Data.Stream.Transform-import Streamly.Internal.Data.Stream.Type-import Streamly.Internal.Data.Stream.Zip--import Streamly.Internal.Data.Stream.Transformer
− src/Streamly/Internal/Data/Stream/StreamK.hs
@@ -1,1372 +0,0 @@-{-# LANGUAGE CPP #-}--- |--- Module : Streamly.Internal.Data.Stream.StreamK--- Copyright : (c) 2017 Composewell Technologies------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC----module Streamly.Internal.Data.Stream.StreamK- (- -- * Setup- -- | To execute the code examples provided in this module in ghci, please- -- run the following commands first.- --- -- $setup-- -- * The stream type- Stream- , StreamK(..)- , fromStream- , toStream-- , CrossStreamK- , unCross- , mkCross-- -- * 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- , fromPure- , fromEffect- , fromFoldable- , fromList-- -- * foldr/build- , foldrS- , foldrSM- , buildS- , augmentS-- -- * Elimination- -- ** General Folds- , foldr- , foldr1- , foldrM-- , foldl'- , foldlM'- , foldlS- , foldlx'- , foldlMx'- , fold- , foldBreak- , foldEither- , foldConcat- , parseDBreak- , parseD- , parseBreakChunks- , parseChunks-- -- ** 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- , hoist-- -- * Transformation- -- ** By folding (scans)- , scanl'- , scanlx'-- -- ** Filtering- , filter- , take- , takeWhile- , drop- , dropWhile-- -- ** Mapping- , map- , mapM- , sequence-- -- ** Inserting- , intersperseM- , intersperse- , insertBy-- -- ** Deleting- , deleteBy-- -- ** Reordering- , reverse- , sortBy-- -- ** Map and Filter- , mapMaybe-- -- ** Zipping- , zipWith- , zipWithM-- -- ** Merging- , mergeBy- , mergeByM-- -- ** Nesting- , crossApplyWith- , crossApply- , crossApplySnd- , crossApplyFst- , crossWith-- , concatMapWith- , concatMap- , concatEffect- , bindWith- , concatIterateWith- , concatIterateLeftsWith- , concatIterateScanWith-- , mergeMapWith- , mergeIterateWith-- -- ** Transformation comprehensions- , the-- -- * Semigroup Style Composition- , append- , interleave-- -- * Utilities- , consM- , mfix- )-where--#include "ArrayMacros.h"-#include "inline.hs"-#include "assert.hs"--import Control.Monad (void, join)-import Data.Proxy (Proxy(..))-import GHC.Types (SPEC(..))-import Streamly.Internal.Data.Array.Type (Array(..))-import Streamly.Internal.Data.Fold.Type (Fold(..))-import Streamly.Internal.Data.Producer.Type (Producer(..))-import Streamly.Internal.Data.SVar.Type (adaptState, defState)-import Streamly.Internal.Data.Unboxed (sizeOf, Unbox)-import Streamly.Internal.Data.Parser.ParserK.Type (ParserK)--import qualified Streamly.Internal.Data.Array.Type as Array-import qualified Streamly.Internal.Data.Fold.Type as FL-import qualified Streamly.Internal.Data.Parser as Parser-import qualified Streamly.Internal.Data.Parser.ParserD.Type as PR-import qualified Streamly.Internal.Data.Parser.ParserK.Type as ParserK-import qualified Streamly.Internal.Data.Stream.StreamD as Stream-import qualified Prelude--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, splitAt)--import Streamly.Internal.Data.Stream.StreamK.Type-import Streamly.Internal.Data.Parser.ParserD (ParseError(..))--#include "DocTestDataStreamK.hs"--{-# INLINE fromStream #-}-fromStream :: Monad m => Stream.Stream m a -> StreamK m a-fromStream = Stream.toStreamK--{-# INLINE toStream #-}-toStream :: Applicative m => StreamK m a -> Stream.Stream m a-toStream = Stream.fromStreamK------------------------------------------------------------------------------------ Generation----------------------------------------------------------------------------------{---- 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 :: (b -> Maybe (m a, b)) -> b -> StreamK m a-unfoldrA = undefined--}------------------------------------------------------------------------------------ Special generation----------------------------------------------------------------------------------repeatM :: Monad m => m a -> StreamK m a-repeatM = repeatMWith consM--{-# INLINE replicateM #-}-replicateM :: Monad m => Int -> m a -> StreamK m a-replicateM = replicateMWith consM-{-# INLINE replicate #-}-replicate :: Int -> a -> StreamK m a-replicate n a = go n- where- go cnt = if cnt <= 0 then nil else a `cons` go (cnt - 1)--{-# INLINE fromIndicesM #-}-fromIndicesM :: Monad m => (Int -> m a) -> StreamK m a-fromIndicesM = fromIndicesMWith consM-{-# INLINE fromIndices #-}-fromIndices :: (Int -> a) -> StreamK m a-fromIndices gen = go 0- where- go n = gen n `cons` go (n + 1)--{-# INLINE iterate #-}-iterate :: (a -> a) -> a -> StreamK m a-iterate step = go- where- go !s = cons s (go (step s))--{-# INLINE iterateM #-}-iterateM :: Monad m => (a -> m a) -> m a -> StreamK m a-iterateM = iterateMWith consM------------------------------------------------------------------------------------ Conversions----------------------------------------------------------------------------------{-# INLINE fromList #-}-fromList :: [a] -> StreamK m a-fromList = fromFoldable------------------------------------------------------------------------------------ Elimination by Folding----------------------------------------------------------------------------------{-# INLINE foldr1 #-}-foldr1 :: Monad m => (a -> a -> a) -> StreamK 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---- XXX replace the recursive "go" with explicit continuations.--- | Like 'foldx', but with a monadic step function.-{-# INLINABLE foldlMx' #-}-foldlMx' :: Monad m- => (x -> a -> m x) -> m x -> (x -> m b) -> StreamK m a -> m b-foldlMx' step begin done = go begin- 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---- | Fold a stream using the supplied left 'Fold' and reducing the resulting--- expression strictly at each step. The behavior is similar to 'foldl''. A--- 'Fold' can terminate early without consuming the full stream. See the--- documentation of individual 'Fold's for termination behavior.------ Definitions:------ >>> fold f = fmap fst . StreamK.foldBreak f--- >>> fold f = StreamK.parseD (Parser.fromFold f)------ Example:------ >>> StreamK.fold Fold.sum $ StreamK.fromStream $ Stream.enumerateFromTo 1 100--- 5050----{-# INLINABLE fold #-}-fold :: Monad m => FL.Fold m a b -> StreamK m a -> m b-fold (FL.Fold step begin done) m = do- res <- begin- case res of- FL.Partial fs -> go fs m- FL.Done fb -> return fb-- where- go !acc m1 =- let stop = done acc- single a = step acc a- >>= \case- FL.Partial s -> done s- FL.Done b1 -> return b1- yieldk a r = step acc a- >>= \case- FL.Partial s -> go s r- FL.Done b1 -> return b1- in foldStream defState yieldk single stop m1---- | Fold resulting in either breaking the stream or continuation of the fold.--- Instead of supplying the input stream in one go we can run the fold multiple--- times, each time supplying the next segment of the input stream. If the fold--- has not yet finished it returns a fold that can be run again otherwise it--- returns the fold result and the residual stream.------ /Internal/-{-# INLINE foldEither #-}-foldEither :: Monad m =>- Fold m a b -> StreamK m a -> m (Either (Fold m a b) (b, StreamK m a))-foldEither (FL.Fold step begin done) m = do- res <- begin- case res of- FL.Partial fs -> go fs m- FL.Done fb -> return $ Right (fb, m)-- where-- go !acc m1 =- let stop = return $ Left (Fold step (return $ FL.Partial acc) done)- single a =- step acc a- >>= \case- FL.Partial s ->- return $ Left (Fold step (return $ FL.Partial s) done)- FL.Done b1 -> return $ Right (b1, nil)- yieldk a r =- step acc a- >>= \case- FL.Partial s -> go s r- FL.Done b1 -> return $ Right (b1, r)- in foldStream defState yieldk single stop m1---- | Like 'fold' but also returns the remaining stream. The resulting stream--- would be 'StreamK.nil' if the stream finished before the fold.----{-# INLINE foldBreak #-}-foldBreak :: Monad m => Fold m a b -> StreamK m a -> m (b, StreamK m a)-foldBreak fld strm = do- r <- foldEither fld strm- case r of- Right res -> return res- Left (Fold _ initial extract) -> do- res <- initial- case res of- FL.Done _ -> error "foldBreak: unreachable state"- FL.Partial s -> do- b <- extract s- return (b, nil)---- XXX Array folds can be implemented using this.--- foldContainers? Specialized to foldArrays.---- | Generate streams from individual elements of a stream and fold the--- concatenation of those streams using the supplied fold. Return the result of--- the fold and residual stream.------ For example, this can be used to efficiently fold an Array Word8 stream--- using Word8 folds.------ /Internal/-{-# INLINE foldConcat #-}-foldConcat :: Monad m =>- Producer m a b -> Fold m b c -> StreamK m a -> m (c, StreamK m a)-foldConcat- (Producer pstep pinject pextract)- (Fold fstep begin done)- stream = do-- res <- begin- case res of- FL.Partial fs -> go fs stream- FL.Done fb -> return (fb, stream)-- where-- go !acc m1 = do- let stop = do- r <- done acc- return (r, nil)- single a = do- st <- pinject a- res <- go1 SPEC acc st- case res of- Left fs -> do- r <- done fs- return (r, nil)- Right (b, s) -> do- x <- pextract s- return (b, fromPure x)- yieldk a r = do- st <- pinject a- res <- go1 SPEC acc st- case res of- Left fs -> go fs r- Right (b, s) -> do- x <- pextract s- return (b, x `cons` r)- in foldStream defState yieldk single stop m1-- {-# INLINE go1 #-}- go1 !_ !fs st = do- r <- pstep st- case r of- Stream.Yield x s -> do- res <- fstep fs x- case res of- FL.Done b -> return $ Right (b, s)- FL.Partial fs1 -> go1 SPEC fs1 s- Stream.Skip s -> go1 SPEC fs s- Stream.Stop -> return $ Left fs---- | Like 'foldl'' but with a monadic step function.-{-# INLINE foldlM' #-}-foldlM' :: Monad m => (b -> a -> m b) -> m b -> StreamK m a -> m b-foldlM' step begin = foldlMx' step begin return----------------------------------------------------------------------------------- Specialized folds---------------------------------------------------------------------------------{-# INLINE head #-}-head :: Monad m => StreamK 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 elem #-}-elem :: (Monad m, Eq a) => a -> StreamK m a -> m Bool-elem e = go- 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 :: (Monad m, Eq a) => a -> StreamK m a -> m Bool-notElem e = go- 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 :: Monad m => (a -> Bool) -> StreamK m a -> m Bool-all p = go- 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 :: Monad m => (a -> Bool) -> StreamK m a -> m Bool-any p = go- 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 :: Monad m => StreamK m a -> m (Maybe a)-last = foldlx' (\_ y -> Just y) Nothing id--{-# INLINE minimum #-}-minimum :: (Monad m, Ord a) => StreamK m a -> m (Maybe a)-minimum = go Nothing- 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- :: (Monad m)- => (a -> a -> Ordering) -> StreamK m a -> m (Maybe a)-minimumBy cmp = go Nothing- 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 :: (Monad m, Ord a) => StreamK m a -> m (Maybe a)-maximum = go Nothing- 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 :: Monad m => (a -> a -> Ordering) -> StreamK m a -> m (Maybe a)-maximumBy cmp = go Nothing- 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 (!!) #-}-(!!) :: Monad m => StreamK 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 :: (Monad m, Eq a) => a -> StreamK m (a, b) -> m (Maybe b)-lookup e = go- 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 :: Monad m => (a -> m Bool) -> StreamK m a -> m (Maybe a)-findM p = go- 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 :: Monad m => (a -> Bool) -> StreamK m a -> m (Maybe a)-find p = findM (return . p)--{-# INLINE findIndices #-}-findIndices :: (a -> Bool) -> StreamK m a -> StreamK 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_ :: Monad m => (a -> m b) -> StreamK m a -> m ()-mapM_ f = go- 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--{-# INLINE mapM #-}-mapM :: Monad m => (a -> m b) -> StreamK m a -> StreamK m b-mapM = mapMWith consM----------------------------------------------------------------------------------- Converting folds---------------------------------------------------------------------------------{-# INLINABLE toList #-}-toList :: Monad m => StreamK m a -> m [a]-toList = foldr (:) []---- Based on suggestions by David Feuer and Pranay Sashank-{-# INLINE hoist #-}-hoist :: (Monad m, Monad n)- => (forall x. m x -> n x) -> StreamK m a -> StreamK 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' :: (x -> a -> x) -> x -> (x -> b) -> StreamK m a -> StreamK 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' :: (b -> a -> b) -> b -> StreamK m a -> StreamK m b-scanl' step begin = scanlx' step begin id------------------------------------------------------------------------------------ Filtering----------------------------------------------------------------------------------{-# INLINE filter #-}-filter :: (a -> Bool) -> StreamK m a -> StreamK m a-filter p = go- 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 :: Int -> StreamK m a -> StreamK m a-take = go- 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 :: (a -> Bool) -> StreamK m a -> StreamK m a-takeWhile p = go- 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 :: Int -> StreamK m a -> StreamK m a-drop n m = unShare (go n 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 :: (a -> Bool) -> StreamK m a -> StreamK m a-dropWhile p = go- 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 :: Monad m => StreamK m (m a) -> StreamK m a-sequence = go- where- go m1 = mkStream $ \st yld sng stp ->- let single ma = ma >>= sng- yieldk ma r = foldStreamShared st yld sng stp $ ma `consM` go r- in foldStream (adaptState st) yieldk single stp m1------------------------------------------------------------------------------------ Inserting----------------------------------------------------------------------------------{-# INLINE intersperseM #-}-intersperseM :: Monad m => m a -> StreamK m a -> StreamK m a-intersperseM a = prependingStart- where- prependingStart m1 = mkStream $ \st yld sng stp ->- let yieldk i x =- foldStreamShared st yld sng stp $ return i `consM` 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 `consM` fromPure i- yieldk i x =- foldStreamShared- st yld sng stp $ a `consM` return i `consM` go x- in foldStream st yieldk single stp m2--{-# INLINE intersperse #-}-intersperse :: Monad m => a -> StreamK m a -> StreamK m a-intersperse a = intersperseM (return a)--{-# INLINE insertBy #-}-insertBy :: (a -> a -> Ordering) -> a -> StreamK m a -> StreamK m a-insertBy cmp x = go- where- go m1 = mkStream $ \st yld _ _ ->- let single a = case cmp x a of- GT -> yld a (fromPure x)- _ -> yld x (fromPure 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 :: (a -> a -> Bool) -> a -> StreamK m a -> StreamK m a-deleteBy eq x = go- 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------------------------------------------------------------------------------------ Map and Filter----------------------------------------------------------------------------------{-# INLINE mapMaybe #-}-mapMaybe :: (a -> Maybe b) -> StreamK m a -> StreamK m b-mapMaybe f = go- where- go m1 = mkStream $ \st yld sng stp ->- let single a = maybe stp sng (f a)- 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.----{-# INLINE zipWith #-}-zipWith :: Monad m => (a -> b -> c) -> StreamK m a -> StreamK m b -> StreamK m c-zipWith f = zipWithM (\a b -> return (f a b))---- | Zip two streams serially using a monadic zipping function.----{-# INLINE zipWithM #-}-zipWithM :: Monad m =>- (a -> b -> m c) -> StreamK m a -> StreamK m b -> StreamK m c-zipWithM f = go-- where-- go mx my = mkStream $ \st yld sng stp -> do- let merge a ra =- let single2 b = f a b >>= sng- yield2 b rb = f a b >>= \x -> yld x (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 :: Monad m =>- (a -> a -> m Ordering) -> StreamK m a -> StreamK m a -> StreamK m a-mergeByM cmp = go-- where-- go mx my = mkStream $ \st yld sng stp -> do- let stop = foldStream st yld sng stp my- single x = foldStream st yld sng stp (goX0 x my)- yield x rx = foldStream st yld sng stp (goX x rx my)- foldStream st yield single stop mx-- goX0 x my = mkStream $ \st yld sng _ -> do- let stop = sng x- single y = do- r <- cmp x y- case r of- GT -> yld y (fromPure x)- _ -> yld x (fromPure y)- yield y ry = do- r <- cmp x y- case r of- GT -> yld y (goX0 x ry)- _ -> yld x (y `cons` ry)- in foldStream st yield single stop my-- goX x mx my = mkStream $ \st yld _ _ -> do- let stop = yld x mx- single y = do- r <- cmp x y- case r of- GT -> yld y (x `cons` mx)- _ -> yld x (goY0 mx y)- yield y ry = do- r <- cmp x y- case r of- GT -> yld y (goX x mx ry)- _ -> yld x (goY mx y ry)- in foldStream st yield single stop my-- goY0 mx y = mkStream $ \st yld sng _ -> do- let stop = sng y- single x = do- r <- cmp x y- case r of- GT -> yld y (fromPure x)- _ -> yld x (fromPure y)- yield x rx = do- r <- cmp x y- case r of- GT -> yld y (x `cons` rx)- _ -> yld x (goY0 rx y)- in foldStream st yield single stop mx-- goY mx y my = mkStream $ \st yld _ _ -> do- let stop = yld y my- single x = do- r <- cmp x y- case r of- GT -> yld y (goX0 x my)- _ -> yld x (y `cons` my)- yield x rx = do- r <- cmp x y- case r of- GT -> yld y (goX x rx my)- _ -> yld x (goY rx y my)- in foldStream st yield single stop mx--{-# INLINE mergeBy #-}-mergeBy :: (a -> a -> Ordering) -> StreamK m a -> StreamK m a -> StreamK m a--- XXX GHC: This has slightly worse performance than replacing "r <- cmp x y"--- with "let r = cmp x y" in the monadic version. The definition below is--- exactly the same as mergeByM except this change.--- mergeBy cmp = mergeByM (\a b -> return $ cmp a b)-mergeBy cmp = go-- where-- go mx my = mkStream $ \st yld sng stp -> do- let stop = foldStream st yld sng stp my- single x = foldStream st yld sng stp (goX0 x my)- yield x rx = foldStream st yld sng stp (goX x rx my)- foldStream st yield single stop mx-- goX0 x my = mkStream $ \st yld sng _ -> do- let stop = sng x- single y = do- case cmp x y of- GT -> yld y (fromPure x)- _ -> yld x (fromPure y)- yield y ry = do- case cmp x y of- GT -> yld y (goX0 x ry)- _ -> yld x (y `cons` ry)- in foldStream st yield single stop my-- goX x mx my = mkStream $ \st yld _ _ -> do- let stop = yld x mx- single y = do- case cmp x y of- GT -> yld y (x `cons` mx)- _ -> yld x (goY0 mx y)- yield y ry = do- case cmp x y of- GT -> yld y (goX x mx ry)- _ -> yld x (goY mx y ry)- in foldStream st yield single stop my-- goY0 mx y = mkStream $ \st yld sng _ -> do- let stop = sng y- single x = do- case cmp x y of- GT -> yld y (fromPure x)- _ -> yld x (fromPure y)- yield x rx = do- case cmp x y of- GT -> yld y (x `cons` rx)- _ -> yld x (goY0 rx y)- in foldStream st yield single stop mx-- goY mx y my = mkStream $ \st yld _ _ -> do- let stop = yld y my- single x = do- case cmp x y of- GT -> yld y (goX0 x my)- _ -> yld x (y `cons` my)- yield x rx = do- case cmp x y of- GT -> yld y (goX x rx my)- _ -> yld x (goY rx y my)- in foldStream st yield single stop mx----------------------------------------------------------------------------------- Transformation comprehensions---------------------------------------------------------------------------------{-# INLINE the #-}-the :: (Eq a, Monad m) => StreamK 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 :: StreamK m a -> StreamK m a -> StreamK 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----------------------------------------------------------------------------------- MonadError---------------------------------------------------------------------------------{---- XXX handle and test cross thread state transfer-withCatchError- :: MonadError e m- => StreamK m a -> (e -> StreamK m a) -> StreamK 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--}------------------------------------------------------------------------------------ Parsing------------------------------------------------------------------------------------ Inlined definition.-{-# INLINE splitAt #-}-splitAt :: Int -> [a] -> ([a],[a])-splitAt n ls- | n <= 0 = ([], ls)- | otherwise = splitAt' n ls- where- splitAt' :: Int -> [a] -> ([a], [a])- splitAt' _ [] = ([], [])- splitAt' 1 (x:xs) = ([x], xs)- splitAt' m (x:xs) = (x:xs', xs'')- where- (xs', xs'') = splitAt' (m - 1) xs---- | Run a 'Parser' over a stream and return rest of the Stream.-{-# INLINE_NORMAL parseDBreak #-}-parseDBreak- :: Monad m- => PR.Parser a m b- -> StreamK m a- -> m (Either ParseError b, StreamK m a)-parseDBreak (PR.Parser pstep initial extract) stream = do- res <- initial- case res of- PR.IPartial s -> goStream stream [] s- PR.IDone b -> return (Right b, stream)- PR.IError err -> return (Left (ParseError err), stream)-- where-- -- "buf" contains last few items in the stream that we may have to- -- backtrack to.- --- -- XXX currently we are using a dumb list based approach for backtracking- -- buffer. This can be replaced by a sliding/ring buffer using Data.Array.- -- That will allow us more efficient random back and forth movement.- goStream st buf !pst =- let stop = do- r <- extract pst- case r of- PR.Error err -> return (Left (ParseError err), nil)- PR.Done n b -> do- assertM(n <= length buf)- let src0 = Prelude.take n buf- src = Prelude.reverse src0- return (Right b, fromList src)- PR.Partial _ _ -> error "Bug: parseBreak: Partial in extract"- PR.Continue 0 s -> goStream nil buf s- PR.Continue n s -> do- assertM(n <= length buf)- let (src0, buf1) = splitAt n buf- src = Prelude.reverse src0- goBuf nil buf1 src s- single x = yieldk x nil- yieldk x r = do- res <- pstep pst x- case res of- PR.Partial 0 s -> goStream r [] s- PR.Partial n s -> do- assertM(n <= length (x:buf))- let src0 = Prelude.take n (x:buf)- src = Prelude.reverse src0- goBuf r [] src s- PR.Continue 0 s -> goStream r (x:buf) s- PR.Continue n s -> do- assertM(n <= length (x:buf))- let (src0, buf1) = splitAt n (x:buf)- src = Prelude.reverse src0- goBuf r buf1 src s- PR.Done 0 b -> return (Right b, r)- PR.Done n b -> do- assertM(n <= length (x:buf))- let src0 = Prelude.take n (x:buf)- src = Prelude.reverse src0- return (Right b, append (fromList src) r)- PR.Error err -> return (Left (ParseError err), r)- in foldStream defState yieldk single stop st-- goBuf st buf [] !pst = goStream st buf pst- goBuf st buf (x:xs) !pst = do- pRes <- pstep pst x- case pRes of- PR.Partial 0 s -> goBuf st [] xs s- PR.Partial n s -> do- assert (n <= length (x:buf)) (return ())- let src0 = Prelude.take n (x:buf)- src = Prelude.reverse src0 ++ xs- goBuf st [] src s- PR.Continue 0 s -> goBuf st (x:buf) xs s- PR.Continue n s -> do- assert (n <= length (x:buf)) (return ())- let (src0, buf1) = splitAt n (x:buf)- src = Prelude.reverse src0 ++ xs- goBuf st buf1 src s- PR.Done n b -> do- assert (n <= length (x:buf)) (return ())- let src0 = Prelude.take n (x:buf)- src = Prelude.reverse src0- return (Right b, append (fromList src) st)- PR.Error err -> return (Left (ParseError err), nil)---- Using ParserD or ParserK on StreamK may not make much difference. We should--- perhaps use only chunked parsing on StreamK. We can always convert a stream--- to chunks before parsing. Or just have a ParserK element parser for StreamK--- and convert ParserD to ParserK for element parsing using StreamK.-{-# INLINE parseD #-}-parseD :: Monad m =>- Parser.Parser a m b -> StreamK m a -> m (Either ParseError b)-parseD f = fmap fst . parseDBreak f------------------------------------------------------------------------------------ Chunked parsing using ParserK------------------------------------------------------------------------------------ The backracking buffer consists of arrays in the most-recent-first order. We--- want to take a total of n array elements from this buffer. Note: when we--- have to take an array partially, we must take the last part of the array.-{-# INLINE backTrack #-}-backTrack :: forall m a. Unbox a =>- Int- -> [Array a]- -> StreamK m (Array a)- -> (StreamK m (Array a), [Array a])-backTrack = go-- where-- go _ [] stream = (stream, [])- go n xs stream | n <= 0 = (stream, xs)- go n (x:xs) stream =- let len = Array.length x- in if n > len- then go (n - len) xs (cons x stream)- else if n == len- then (cons x stream, xs)- else let !(Array contents start end) = x- !start1 = end - (n * SIZE_OF(a))- arr1 = Array contents start1 end- arr2 = Array contents start start1- in (cons arr1 stream, arr2:xs)---- | A continuation to extract the result when a CPS parser is done.-{-# INLINE parserDone #-}-parserDone :: Applicative m =>- ParserK.ParseResult b -> Int -> ParserK.Input a -> m (ParserK.Step a m b)-parserDone (ParserK.Success n b) _ _ = pure $ ParserK.Done n b-parserDone (ParserK.Failure n e) _ _ = pure $ ParserK.Error n e---- XXX parseDBreakChunks may be faster than converting parserD to parserK and--- using parseBreakChunks. We can also use parseBreak as an alternative to the--- monad instance of ParserD.---- | Run a 'ParserK' over a chunked 'StreamK' and return the rest of the Stream.-{-# INLINE_NORMAL parseBreakChunks #-}-parseBreakChunks- :: (Monad m, Unbox a)- => ParserK a m b- -> StreamK m (Array a)- -> m (Either ParseError b, StreamK m (Array a))-parseBreakChunks parser input = do- let parserk = ParserK.runParser parser parserDone 0 0- in go [] parserk input-- where-- {-# INLINE goStop #-}- goStop backBuf parserk = do- pRes <- parserk ParserK.None- case pRes of- -- If we stop in an alternative, it will try calling the next- -- parser, the next parser may call initial returning Partial and- -- then immediately we have to call extract on it.- ParserK.Partial 0 cont1 ->- go [] cont1 nil- ParserK.Partial n cont1 -> do- let n1 = negate n- assertM(n1 >= 0 && n1 <= sum (Prelude.map Array.length backBuf))- let (s1, backBuf1) = backTrack n1 backBuf nil- in go backBuf1 cont1 s1- ParserK.Continue 0 cont1 ->- go backBuf cont1 nil- ParserK.Continue n cont1 -> do- let n1 = negate n- assertM(n1 >= 0 && n1 <= sum (Prelude.map Array.length backBuf))- let (s1, backBuf1) = backTrack n1 backBuf nil- in go backBuf1 cont1 s1- ParserK.Done 0 b ->- return (Right b, nil)- ParserK.Done n b -> do- let n1 = negate n- assertM(n1 >= 0 && n1 <= sum (Prelude.map Array.length backBuf))- let (s1, _) = backTrack n1 backBuf nil- in return (Right b, s1)- ParserK.Error _ err -> return (Left (ParseError err), nil)-- seekErr n len =- error $ "parseBreak: Partial: forward seek not implemented n = "- ++ show n ++ " len = " ++ show len-- yieldk backBuf parserk arr stream = do- pRes <- parserk (ParserK.Chunk arr)- let len = Array.length arr- case pRes of- ParserK.Partial n cont1 ->- case compare n len of- EQ -> go [] cont1 stream- LT -> do- if n >= 0- then yieldk [] cont1 arr stream- else do- let n1 = negate n- bufLen = sum (Prelude.map Array.length backBuf)- s = cons arr stream- assertM(n1 >= 0 && n1 <= bufLen)- let (s1, _) = backTrack n1 backBuf s- go [] cont1 s1- GT -> seekErr n len- ParserK.Continue n cont1 ->- case compare n len of- EQ -> go (arr:backBuf) cont1 stream- LT -> do- if n >= 0- then yieldk backBuf cont1 arr stream- else do- let n1 = negate n- bufLen = sum (Prelude.map Array.length backBuf)- s = cons arr stream- assertM(n1 >= 0 && n1 <= bufLen)- let (s1, backBuf1) = backTrack n1 backBuf s- go backBuf1 cont1 s1- GT -> seekErr n len- ParserK.Done n b -> do- let n1 = len - n- assertM(n1 <= sum (Prelude.map Array.length (arr:backBuf)))- let (s1, _) = backTrack n1 (arr:backBuf) stream- in return (Right b, s1)- ParserK.Error _ err -> return (Left (ParseError err), nil)-- go backBuf parserk stream = do- let stop = goStop backBuf parserk- single a = yieldk backBuf parserk a nil- in foldStream- defState (yieldk backBuf parserk) single stop stream--{-# INLINE parseChunks #-}-parseChunks :: (Monad m, Unbox a) =>- ParserK a m b -> StreamK m (Array a) -> m (Either ParseError b)-parseChunks f = fmap fst . parseBreakChunks f------------------------------------------------------------------------------------ Sorting------------------------------------------------------------------------------------ | Sort the input stream using a supplied comparison function.------ Sorting can be achieved by simply:------ >>> sortBy cmp = StreamK.mergeMapWith (StreamK.mergeBy cmp) StreamK.fromPure------ However, this combinator uses a parser to first split the input stream into--- down and up sorted segments and then merges them to optimize sorting when--- pre-sorted sequences exist in the input stream.------ /O(n) space/----{-# INLINE sortBy #-}-sortBy :: Monad m => (a -> a -> Ordering) -> StreamK m a -> StreamK m a--- sortBy f = Stream.concatPairsWith (Stream.mergeBy f) Stream.fromPure-sortBy cmp =- let p =- Parser.groupByRollingEither- (\x -> (< GT) . cmp x)- FL.toStreamKRev- FL.toStreamK- in mergeMapWith (mergeBy cmp) id- . Stream.toStreamK- . Stream.catRights -- its a non-failing backtracking parser- . Stream.parseMany (fmap (either id id) p)- . Stream.fromStreamK
− src/Streamly/Internal/Data/Stream/StreamK/Alt.hs
@@ -1,244 +0,0 @@--- |--- 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.StreamK.Alt- (- -- * Stream Type-- Stream- , Step (..)-- -- * Construction- , nil- , cons- , consM- , unfoldr- , unfoldrM- , replicateM-- -- * Folding- , uncons- , foldrS-- -- * Specific Folds- , drain- )-where--#include "inline.hs"---- XXX Use Cons and Nil instead of Yield and Stop?-data Step m a = Yield a (Stream m a) | Stop--newtype Stream m a = Stream (m (Step m a))------------------------------------------------------------------------------------ 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 = go- 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/StreamK/Transformer.hs
@@ -1,79 +0,0 @@--- |--- Module : Streamly.Internal.Data.Stream.StreamK.Transformer--- Copyright : (c) 2017 Composewell Technologies--- License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC----module Streamly.Internal.Data.Stream.StreamK.Transformer- (- foldlT- , foldrT-- , liftInner- , evalStateT- )-where--import Control.Monad.Trans.Class (MonadTrans(lift))-import Control.Monad.Trans.State.Strict (StateT)-import Streamly.Internal.Data.Stream.StreamK- (StreamK, nil, cons, uncons, concatEffect)--import qualified Control.Monad.Trans.State.Strict as State---- | Lazy left fold to an arbitrary transformer monad.-{-# INLINE foldlT #-}-foldlT :: (Monad m, Monad (s m), MonadTrans s)- => (s m b -> a -> s m b) -> s m b -> StreamK m a -> s m b-foldlT step = go- where- go acc m1 = do- res <- lift $ uncons m1- case res of- Just (h, t) -> go (step acc h) t- Nothing -> acc---- | Right associative fold to an arbitrary transformer monad.-{-# INLINE foldrT #-}-foldrT :: (Monad m, Monad (s m), MonadTrans s)- => (a -> s m b -> s m b) -> s m b -> StreamK m a -> s m b-foldrT step final = go- where- go m1 = do- res <- lift $ uncons m1- case res of- Just (h, t) -> step h (go t)- Nothing -> final----------------------------------------------------------------------------------- Lifting inner monad---------------------------------------------------------------------------------{-# INLINE evalStateT #-}-evalStateT :: Monad m => m s -> StreamK (StateT s m) a -> StreamK m a-evalStateT = go-- where-- go st m1 = concatEffect $ fmap f (st >>= State.runStateT (uncons m1))-- f (res, s1) =- case res of- Just (h, t) -> cons h (go (return s1) t)- Nothing -> nil--{-# INLINE liftInner #-}-liftInner :: (Monad m, MonadTrans t, Monad (t m)) =>- StreamK m a -> StreamK (t m) a-liftInner = go-- where-- go m1 = concatEffect $ fmap f $ lift $ uncons m1-- f res =- case res of- Just (h, t) -> cons h (go t)- Nothing -> nil
− src/Streamly/Internal/Data/Stream/StreamK/Type.hs
@@ -1,2063 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE UndecidableInstances #-}--- |--- Module : Streamly.Internal.Data.Stream.StreamK.Type--- Copyright : (c) 2017 Composewell Technologies------ 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- (- -- * StreamK type- Stream- , StreamK (..)-- -- * CrossStreamK type wrapper- , CrossStreamK- , unCross- , mkCross-- -- * foldr/build Fusion- , mkStream- , foldStream- , foldStreamShared- , foldrM- , foldrS- , foldrSShared- , foldrSM- , build- , buildS- , buildM- , buildSM- , augmentS- , augmentSM- , unShare-- -- * Construction- -- ** Primitives- , fromStopK- , fromYieldK- , consK- , cons- , (.:)- , consM- , consMBy- , nil- , nilM-- -- ** Unfolding- , unfoldr- , unfoldrMWith- , unfoldrM-- -- ** From Values- , fromEffect- , fromPure- , repeat- , repeatMWith- , replicateMWith-- -- ** From Indices- , fromIndicesMWith-- -- ** Iteration- , iterateMWith-- -- ** From Containers- , fromFoldable- , fromFoldableM-- -- ** Cyclic- , mfix-- -- * Elimination- -- ** Primitives- , uncons-- -- ** Strict Left Folds- , Streamly.Internal.Data.Stream.StreamK.Type.foldl'- , foldlx'-- -- ** Lazy Right Folds- , Streamly.Internal.Data.Stream.StreamK.Type.foldr-- -- ** Specific Folds- , drain- , null- , tail- , init-- -- * Mapping- , map- , mapMWith- , mapMSerial-- -- * Combining Two Streams- -- ** Appending- , conjoin- , append-- -- ** Interleave- , interleave- , interleaveFst- , interleaveMin-- -- ** Cross Product- , crossApplyWith- , crossApply- , crossApplySnd- , crossApplyFst- , crossWith- , cross-- -- * Concat- , before- , concatEffect- , concatMapEffect- , concatMapWith- , concatMap- , bindWith- , concatIterateWith- , concatIterateLeftsWith- , concatIterateScanWith-- -- * Merge- , mergeMapWith- , mergeIterateWith-- -- * Buffered Operations- , foldlS- , reverse- )-where--#include "inline.hs"---- import Control.Applicative (liftA2)-import Control.Monad ((>=>))-import Control.Monad.Catch (MonadThrow, throwM)-import Control.Monad.Trans.Class (MonadTrans(lift))-import Control.Applicative (liftA2)-import Control.Monad.IO.Class (MonadIO(..))-import Data.Foldable (Foldable(foldl'), fold, foldr)-import Data.Function (fix)-import Data.Functor.Identity (Identity(..))-import Data.Maybe (fromMaybe)-import Data.Semigroup (Endo(..))-import GHC.Exts (IsList(..), IsString(..), oneShot)-import Streamly.Internal.BaseCompat ((#.))-import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)-import Streamly.Internal.Data.SVar.Type (State, adaptState, defState)-import Text.Read- ( Lexeme(Ident), lexP, parens, prec, readPrec, readListPrec- , readListPrecDefault)--import qualified Prelude--import Prelude hiding- (map, mapM, concatMap, foldr, repeat, null, reverse, tail, init)--#include "DocTestDataStreamK.hs"----------------------------------------------------------------------------------- Basic stream type----------------------------------------------------------------------------------- It uses stop, singleton and yield continuations equivalent to the following--- direct style type:------ @--- data StreamK m a = Stop | Singleton a | Yield a (StreamK 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 State param.---- | Continuation Passing Style (CPS) version of "Streamly.Data.Stream.Stream".--- Unlike "Streamly.Data.Stream.Stream", 'StreamK' can be composed recursively--- without affecting performance.------ Semigroup instance appends two streams:------ >>> (<>) = Stream.append----{-# DEPRECATED Stream "Please use StreamK instead." #-}-type Stream = StreamK--newtype StreamK m a =- MkStream (forall r.- State StreamK m a -- state- -> (a -> StreamK m a -> m r) -- yield- -> (a -> m r) -- singleton- -> m r -- stop- -> m r- )--mkStream- :: (forall r. State StreamK m a- -> (a -> StreamK m a -> m r)- -> (a -> m r)- -> m r- -> m r)- -> StreamK m a-mkStream = 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 = (m >>=)---- | Make an empty stream from a stop function.-fromStopK :: StopK m -> StreamK m a-fromStopK k = mkStream $ \_ _ _ stp -> k stp---- | Make a singleton stream from a callback function. The callback function--- calls the one-shot yield continuation to yield an element.-fromYieldK :: YieldK m a -> StreamK m a-fromYieldK k = mkStream $ \_ _ sng _ -> k sng---- | Add a yield function at the head of the stream.-consK :: YieldK m a -> StreamK m a -> StreamK m a-consK k r = mkStream $ \_ yld _ _ -> k (`yld` r)---- XXX Build a stream from a repeating callback function.----------------------------------------------------------------------------------- Construction---------------------------------------------------------------------------------infixr 5 `cons`---- faster than consM because there is no bind.---- | A right associative prepend operation to add a pure value at the head of--- an existing stream::------ >>> s = 1 `StreamK.cons` 2 `StreamK.cons` 3 `StreamK.cons` StreamK.nil--- >>> Stream.fold Fold.toList (StreamK.toStream s)--- [1,2,3]------ It can be used efficiently with 'Prelude.foldr':------ >>> fromFoldable = Prelude.foldr StreamK.cons StreamK.nil------ Same as the following but more efficient:------ >>> cons x xs = return x `StreamK.consM` xs----{-# INLINE_NORMAL cons #-}-cons :: a -> StreamK m a -> StreamK m a-cons a r = mkStream $ \_ yield _ _ -> yield a r--infixr 5 .:---- | Operator equivalent of 'cons'.------ @--- > toList $ 1 .: 2 .: 3 .: nil--- [1,2,3]--- @----{-# INLINE (.:) #-}-(.:) :: a -> StreamK m a -> StreamK m a-(.:) = cons---- | A stream that terminates without producing any output or side effect.------ >>> Stream.fold Fold.toList (StreamK.toStream StreamK.nil)--- []----{-# INLINE_NORMAL nil #-}-nil :: StreamK m a-nil = mkStream $ \_ _ _ stp -> stp---- | A stream that terminates without producing any output, but produces a side--- effect.------ >>> Stream.fold Fold.toList (StreamK.toStream (StreamK.nilM (print "nil")))--- "nil"--- []------ /Pre-release/-{-# INLINE_NORMAL nilM #-}-nilM :: Applicative m => m b -> StreamK m a-nilM m = mkStream $ \_ _ _ stp -> m *> stp---- | Create a singleton stream from a pure value.------ >>> fromPure a = a `StreamK.cons` StreamK.nil--- >>> fromPure = pure--- >>> fromPure = StreamK.fromEffect . pure----{-# INLINE_NORMAL fromPure #-}-fromPure :: a -> StreamK m a-fromPure a = mkStream $ \_ _ single _ -> single a---- | Create a singleton stream from a monadic action.------ >>> fromEffect m = m `StreamK.consM` StreamK.nil------ >>> Stream.fold Fold.drain $ StreamK.toStream $ StreamK.fromEffect (putStrLn "hello")--- hello----{-# INLINE_NORMAL fromEffect #-}-fromEffect :: Monad m => m a -> StreamK m a-fromEffect m = mkStream $ \_ _ single _ -> m >>= single--infixr 5 `consM`---- 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.---- | A right associative prepend operation to add an effectful value at the--- head of an existing stream::------ >>> s = putStrLn "hello" `StreamK.consM` putStrLn "world" `StreamK.consM` StreamK.nil--- >>> Stream.fold Fold.drain (StreamK.toStream s)--- hello--- world------ It can be used efficiently with 'Prelude.foldr':------ >>> fromFoldableM = Prelude.foldr StreamK.consM StreamK.nil------ Same as the following but more efficient:------ >>> consM x xs = StreamK.fromEffect x `StreamK.append` xs----{-# INLINE consM #-}-{-# SPECIALIZE consM :: IO a -> StreamK IO a -> StreamK IO a #-}-consM :: Monad m => m a -> StreamK m a -> StreamK m a-consM m r = MkStream $ \_ yld _ _ -> m >>= (`yld` r)---- XXX specialize to IO?-{-# INLINE consMBy #-}-consMBy :: Monad m =>- (StreamK m a -> StreamK m a -> StreamK m a) -> m a -> StreamK m a -> StreamK m a-consMBy f m r = fromEffect 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- :: State StreamK m a- -> (a -> StreamK m a -> m r)- -> (a -> m r)- -> m r- -> StreamK m a- -> m r-foldStreamShared s yield single stop (MkStream k) = k s yield single stop---- | 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- :: State StreamK m a- -> (a -> StreamK m a -> m r)- -> (a -> m r)- -> m r- -> StreamK m a- -> m r-foldStream s yield single stop (MkStream k) =- k (adaptState s) yield single stop------------------------------------------------------------------------------------ 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 ::- (forall r. State StreamK m b- -> (b -> StreamK m b -> m r)- -> (b -> m r)- -> m r- -> StreamK m b- -> m r)- -> (a -> StreamK m b -> StreamK m b)- -> StreamK m b- -> StreamK m a- -> StreamK 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 ::- (a -> StreamK m b -> StreamK m b)- -> StreamK m b- -> StreamK m a- -> StreamK 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 (fromPure x) = k x z #-}--- {-# RULES "foldrSShared/app" [1]--- forall ys. foldrSShared consM ys = \xs -> xs `conjoin` ys #-}---- | Right fold to a streaming monad.------ > foldrS StreamK.cons StreamK.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.------ >>> input = StreamK.fromStream $ Stream.fromList [1..5]--- >>> Stream.fold Fold.toList $ StreamK.toStream $ StreamK.foldrS StreamK.cons StreamK.nil input--- [1,2,3,4,5]------ Find if any element in the stream is 'True':------ >>> step x xs = if odd x then StreamK.fromPure True else xs--- >>> input = StreamK.fromStream (Stream.fromList (2:4:5:undefined)) :: StreamK IO Int--- >>> Stream.fold Fold.toList $ StreamK.toStream $ StreamK.foldrS step (StreamK.fromPure False) input--- [True]------ Map (+2) on odd elements and filter out the even elements:------ >>> step x xs = if odd x then (x + 2) `StreamK.cons` xs else xs--- >>> input = StreamK.fromStream (Stream.fromList [1..5]) :: StreamK IO Int--- >>> Stream.fold Fold.toList $ StreamK.toStream $ StreamK.foldrS step StreamK.nil input--- [3,5,7]------ /Pre-release/-{-# INLINE_NORMAL foldrS #-}-foldrS ::- (a -> StreamK m b -> StreamK m b)- -> StreamK m b- -> StreamK m a- -> StreamK 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 (fromPure 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 :: Monad m- => (forall r. State StreamK m b- -> (b -> StreamK m b -> m r)- -> (b -> m r)- -> m r- -> StreamK m b- -> m r)- -> (m a -> StreamK m b -> StreamK m b)- -> StreamK m b- -> StreamK m a- -> StreamK 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 :: Monad m- => (m a -> StreamK m b -> StreamK m b)- -> StreamK m b- -> StreamK m a- -> StreamK 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 (fromEffect 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 :: Monad m- => (m a -> StreamK m b -> StreamK m b)- -> StreamK m b- -> StreamK m a- -> StreamK 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 (fromEffect x) = k x z #-}--- {-# RULES "foldrSM/app" [1]--- forall ys. foldrSM consM ys = \xs -> xs `conjoin` ys #-}------------------------------------------------------------------------------------ build----------------------------------------------------------------------------------{-# INLINE_NORMAL build #-}-build :: forall m a. (forall b. (a -> b -> b) -> b -> b) -> StreamK 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 ::- ((a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a)- -> StreamK m a-buildS g = g cons nil--{-# RULES "foldrS/buildS"- forall k z- (g :: (a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a).- foldrS k z (buildS g) = g k z #-}--{-# RULES "foldrS/cons/buildS"- forall k z x- (g :: (a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a).- foldrS k z (x `cons` buildS g) = k x (g k z) #-}--{-# RULES "foldrSShared/buildS"- forall k z- (g :: (a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a).- foldrSShared k z (buildS g) = g k z #-}--{-# RULES "foldrSShared/cons/buildS"- forall k z x- (g :: (a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK 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 :: Monad m- => ((m a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a)- -> StreamK m a-buildSM g = g consM nil--{-# RULES "foldrSM/buildSM"- forall k z- (g :: (m a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a).- foldrSM k z (buildSM g) = g k z #-}--{-# RULES "foldrSMShared/buildSM"- forall k z- (g :: (m a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK 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 :: Monad m- => (forall r. (a -> StreamK m a -> m r)- -> (a -> m r)- -> m r- -> m r- )- -> StreamK 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 sharedMWith #-}-sharedMWith :: Monad m- => (m a -> StreamK m a -> StreamK m a)- -> (forall r. (a -> StreamK m a -> m r)- -> (a -> m r)- -> m r- -> m r- )- -> StreamK m a-sharedMWith cns g = mkStream $ \st yld sng stp ->- g (\a r -> foldStreamShared st yld sng stp (return a `cns` r)) sng stp------------------------------------------------------------------------------------ augment----------------------------------------------------------------------------------{-# INLINE_NORMAL augmentS #-}-augmentS ::- ((a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a)- -> StreamK m a- -> StreamK m a-augmentS g xs = g cons xs--{-# RULES "augmentS/nil"- forall (g :: (a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a).- augmentS g nil = buildS g- #-}--{-# RULES "foldrS/augmentS"- forall k z xs- (g :: (a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a).- foldrS k z (augmentS g xs) = g k (foldrS k z xs)- #-}--{-# RULES "augmentS/buildS"- forall (g :: (a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a)- (h :: (a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a).- augmentS g (buildS h) = buildS (\c n -> g c (h c n))- #-}--{-# INLINE_NORMAL augmentSM #-}-augmentSM :: Monad m =>- ((m a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a)- -> StreamK m a -> StreamK m a-augmentSM g xs = g consM xs--{-# RULES "augmentSM/nil"- forall- (g :: (m a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a).- augmentSM g nil = buildSM g- #-}--{-# RULES "foldrSM/augmentSM"- forall k z xs- (g :: (m a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a).- foldrSM k z (augmentSM g xs) = g k (foldrSM k z xs)- #-}--{-# RULES "augmentSM/buildSM"- forall- (g :: (m a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a)- (h :: (m a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK 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 :: (a -> m b -> m b) -> m b -> StreamK 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 StreamK m a- -> (a -> StreamK m a -> m b)- -> (a -> m b)- -> m b- -> StreamK m a- -> m b)- -> (a -> m b -> m b)- -> m b- -> ((a -> StreamK m a -> m b) -> (a -> m b) -> m b -> m b)- -> m b-foldrMKWith f step acc = go- 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 -> StreamK 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 -> StreamK m a -> m r)- -> (a -> m r)- -> m r- -> m r- )).- foldrM step acc (sharedM g) = foldrMKWith foldStreamShared step acc g- #-}--}----------------------------------------------------------------------------------- Left fold----------------------------------------------------------------------------------- | 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 m a b x. Monad m- => (x -> a -> x) -> x -> (x -> b) -> StreamK m a -> m b-foldlx' step begin done m = get $ go m begin- where- {-# NOINLINE get #-}- get :: StreamK 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 :: StreamK m a -> x -> StreamK 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' :: Monad m => (b -> a -> b) -> b -> StreamK m a -> m b-foldl' step begin = foldlx' step begin id----------------------------------------------------------------------------------- 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 => StreamK 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 :: Monad m => StreamK 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----------------------------------------------------------------------------------- Semigroup---------------------------------------------------------------------------------infixr 6 `append`---- | Appends two streams sequentially, yielding all elements from the first--- stream, and then all elements from the second stream.------ >>> s1 = StreamK.fromStream $ Stream.fromList [1,2]--- >>> s2 = StreamK.fromStream $ Stream.fromList [3,4]--- >>> Stream.fold Fold.toList $ StreamK.toStream $ s1 `StreamK.append` s2--- [1,2,3,4]------ This has O(n) append performance where @n@ is the number of streams. It can--- be used to efficiently fold an infinite lazy container of streams using--- 'concatMapWith' et. al.----{-# INLINE append #-}-append :: StreamK m a -> StreamK m a -> StreamK 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-append 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 :: Monad m => StreamK m a -> StreamK m a -> StreamK m a-conjoin xs = augmentSM (\c n -> foldrSM c n xs)--instance Semigroup (StreamK m a) where- (<>) = append----------------------------------------------------------------------------------- Monoid---------------------------------------------------------------------------------instance Monoid (StreamK m a) where- mempty = nil- mappend = (<>)------------------------------------------------------------------------------------ Functor------------------------------------------------------------------------------------ IMPORTANT: This is eta expanded on purpose. This should not be eta--- reduced. This will cause a lot of regressions, probably because of some--- rewrite rules. Ideally don't run hlint on this file.-{-# INLINE_LATE mapFB #-}-mapFB :: forall b m a.- (b -> StreamK m b -> StreamK m b)- -> (a -> b)- -> a- -> StreamK m b- -> StreamK m b-mapFB c f = \x ys -> c (f x) ys--{-# 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 :: (a -> b) -> StreamK m a -> StreamK 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 = c (x >>= f)--{-# 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--}---- 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 :: Monad m => (a -> m b) -> StreamK m a -> StreamK m b-mapMSerial f xs = buildSM (\c n -> foldrSMShared (mapMFB c f) n xs)--{-# INLINE mapMWith #-}-mapMWith ::- (m b -> StreamK m b -> StreamK m b)- -> (a -> m b)- -> StreamK m a- -> StreamK m b-mapMWith cns f = foldrSShared (\x xs -> f x `cns` xs) nil--{---- See note under map definition above.-mapMWith cns f = go- where- go m1 = mkStream $ \st yld sng stp ->- let single a = f a >>= sng- yieldk a r = foldStreamShared st yld sng stp $ f a `cns` go r- in foldStream (adaptState st) yieldk single stp m1--}---- XXX in fact use the Stream type everywhere and only use polymorphism in the--- high level modules/prelude.-instance Monad m => Functor (StreamK m) where- fmap = map----------------------------------------------------------------------------------- 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?--instance IsList (StreamK Identity a) where- type (Item (StreamK Identity a)) = a-- {-# INLINE fromList #-}- fromList = fromFoldable-- {-# INLINE toList #-}- toList = Data.Foldable.foldr (:) []---- XXX Fix these-{--instance Eq a => Eq (StreamK Identity a) where- {-# INLINE (==) #-}- (==) xs ys = runIdentity $ eqBy (==) xs ys--instance Ord a => Ord (StreamK Identity a) where- {-# INLINE compare #-}- compare xs ys = runIdentity $ 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 (StreamK Identity a) where- showsPrec p dl = showParen (p > 10) $- showString "fromList " . shows (toList dl)--instance Read a => Read (StreamK Identity a) where- readPrec = parens $ prec 10 $ do- Ident "fromList" <- lexP- fromList <$> readPrec-- readListPrec = readListPrecDefault--instance (a ~ Char) => IsString (StreamK Identity a) where- {-# INLINE fromString #-}- fromString = fromList------------------------------------------------------------------------------------ Foldable------------------------------------------------------------------------------------ | Lazy right associative fold.-{-# INLINE foldr #-}-foldr :: Monad m => (a -> b -> b) -> b -> StreamK m a -> m b-foldr step acc = foldrM (\x xs -> xs >>= \b -> return (step x b)) (return acc)---- 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.--instance (Foldable m, Monad m) => Foldable (StreamK m) where-- {-# INLINE foldMap #-}- foldMap f =- fold- . Streamly.Internal.Data.Stream.StreamK.Type.foldr (mappend . f) mempty-- {-# INLINE foldr #-}- foldr f z t = appEndo (foldMap (Endo #. f) t) z-- {-# INLINE foldl' #-}- foldl' f z0 xs = Data.Foldable.foldr f' id xs z0- where f' x k = oneShot $ \z -> k $! f z x-- {-# INLINE length #-}- length = Data.Foldable.foldl' (\n _ -> n + 1) 0-- {-# INLINE elem #-}- elem = any . (==)-- {-# INLINE maximum #-}- maximum =- fromMaybe (errorWithoutStackTrace "maximum: empty stream")- . toMaybe- . Data.Foldable.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- . Data.Foldable.foldl' getMin Nothing'-- where-- getMin Nothing' x = Just' x- getMin (Just' mn) x = Just' $! min mn x-- {-# INLINE sum #-}- sum = Data.Foldable.foldl' (+) 0-- {-# INLINE product #-}- product = Data.Foldable.foldl' (*) 1------------------------------------------------------------------------------------ Traversable----------------------------------------------------------------------------------instance Traversable (StreamK Identity) where- {-# INLINE traverse #-}- traverse f xs =- runIdentity- $ Streamly.Internal.Data.Stream.StreamK.Type.foldr- consA (pure mempty) xs-- where-- consA x ys = liftA2 cons (f x) ys------------------------------------------------------------------------------------ Nesting------------------------------------------------------------------------------------ | Detach a stream from an SVar-{-# INLINE unShare #-}-unShare :: StreamK m a -> StreamK m a-unShare x = mkStream $ \st yld sng stp ->- foldStream st yld sng stp x---- XXX the function stream and value stream can run in parallel-{-# INLINE crossApplyWith #-}-crossApplyWith ::- (StreamK m b -> StreamK m b -> StreamK m b)- -> StreamK m (a -> b)- -> StreamK m a- -> StreamK m b-crossApplyWith par fstream stream = go1 fstream-- where-- go1 m =- mkStream $ \st yld sng stp ->- let foldShared = foldStreamShared st yld sng stp- single f = foldShared $ unShare (go2 f stream)- yieldk f r = foldShared $ unShare (go2 f stream) `par` go1 r- in foldStream (adaptState st) yieldk single stp m-- go2 f m =- mkStream $ \st yld sng stp ->- let single a = sng (f a)- yieldk a r = yld (f a) (go2 f r)- in foldStream (adaptState st) yieldk single stp m---- | Apply a stream of functions to a stream of values and flatten the results.------ Note that the second stream is evaluated multiple times.------ Definition:------ >>> crossApply = StreamK.crossApplyWith StreamK.append--- >>> crossApply = Stream.crossWith id----{-# INLINE crossApply #-}-crossApply ::- StreamK m (a -> b)- -> StreamK m a- -> StreamK m b-crossApply fstream stream = go1 fstream-- where-- go1 m =- mkStream $ \st yld sng stp ->- let foldShared = foldStreamShared st yld sng stp- single f = foldShared $ go3 f stream- yieldk f r = foldShared $ go2 f r stream- in foldStream (adaptState st) yieldk single stp m-- go2 f r1 m =- mkStream $ \st yld sng stp ->- let foldShared = foldStreamShared st yld sng stp- stop = foldShared $ go1 r1- single a = yld (f a) (go1 r1)- yieldk a r = yld (f a) (go2 f r1 r)- in foldStream (adaptState st) yieldk single stop m-- go3 f m =- mkStream $ \st yld sng stp ->- let single a = sng (f a)- yieldk a r = yld (f a) (go3 f r)- in foldStream (adaptState st) yieldk single stp m--{-# INLINE crossApplySnd #-}-crossApplySnd ::- StreamK m a- -> StreamK m b- -> StreamK m b-crossApplySnd fstream stream = go1 fstream-- where-- go1 m =- mkStream $ \st yld sng stp ->- let foldShared = foldStreamShared st yld sng stp- single _ = foldShared stream- yieldk _ r = foldShared $ go2 r stream- in foldStream (adaptState st) yieldk single stp m-- go2 r1 m =- mkStream $ \st yld sng stp ->- let foldShared = foldStreamShared st yld sng stp- stop = foldShared $ go1 r1- single a = yld a (go1 r1)- yieldk a r = yld a (go2 r1 r)- in foldStream st yieldk single stop m--{-# INLINE crossApplyFst #-}-crossApplyFst ::- StreamK m a- -> StreamK m b- -> StreamK m a-crossApplyFst fstream stream = go1 fstream-- where-- go1 m =- mkStream $ \st yld sng stp ->- let foldShared = foldStreamShared st yld sng stp- single f = foldShared $ go3 f stream- yieldk f r = foldShared $ go2 f r stream- in foldStream st yieldk single stp m-- go2 f r1 m =- mkStream $ \st yld sng stp ->- let foldShared = foldStreamShared st yld sng stp- stop = foldShared $ go1 r1- single _ = yld f (go1 r1)- yieldk _ r = yld f (go2 f r1 r)- in foldStream (adaptState st) yieldk single stop m-- go3 f m =- mkStream $ \st yld sng stp ->- let single _ = sng f- yieldk _ r = yld f (go3 f r)- in foldStream (adaptState st) yieldk single stp m---- |--- Definition:------ >>> crossWith f m1 m2 = fmap f m1 `StreamK.crossApply` m2------ Note that the second stream is evaluated multiple times.----{-# INLINE crossWith #-}-crossWith :: Monad m => (a -> b -> c) -> StreamK m a -> StreamK m b -> StreamK m c-crossWith f m1 m2 = fmap f m1 `crossApply` m2---- | Given a @StreamK m a@ and @StreamK m b@ generate a stream with all possible--- combinations of the tuple @(a, b)@.------ Definition:------ >>> cross = StreamK.crossWith (,)------ The second stream is evaluated multiple times. If that is not desired it can--- be cached in an 'Data.Array.Array' and then generated from the array before--- calling this function. Caching may also improve performance if the stream is--- expensive to evaluate.------ See 'Streamly.Internal.Data.Unfold.cross' for a much faster fused--- alternative.------ Time: O(m x n)------ /Pre-release/-{-# INLINE cross #-}-cross :: Monad m => StreamK m a -> StreamK m b -> StreamK m (a, b)-cross = crossWith (,)---- XXX This is just concatMapWith 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 ::- (StreamK m b -> StreamK m b -> StreamK m b)- -> StreamK m a- -> (a -> StreamK m b)- -> StreamK 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.----{-# INLINE concatMapWith #-}-concatMapWith- ::- (StreamK m b -> StreamK m b -> StreamK m b)- -> (a -> StreamK m b)- -> StreamK m a- -> StreamK m b-concatMapWith par f xs = bindWith par xs f--{-# INLINE concatMap #-}-concatMap :: (a -> StreamK m b) -> StreamK m a -> StreamK m b-concatMap = concatMapWith append--{---- 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)--}---- | Combine streams in pairs using a binary combinator, the resulting streams--- are then combined again in pairs recursively until we get to a single--- combined stream. The composition would thus form a binary tree.------ For example, you can sort a stream using merge sort like this:------ >>> s = StreamK.fromStream $ Stream.fromList [5,1,7,9,2]--- >>> generate = StreamK.fromPure--- >>> combine = StreamK.mergeBy compare--- >>> Stream.fold Fold.toList $ StreamK.toStream $ StreamK.mergeMapWith combine generate s--- [1,2,5,7,9]------ Note that if the stream length is not a power of 2, the binary tree composed--- by mergeMapWith would not be balanced, which may or may not be important--- depending on what you are trying to achieve.------ /Caution: the stream of streams must be finite/------ /Pre-release/----{-# INLINE mergeMapWith #-}-mergeMapWith- ::- (StreamK m b -> StreamK m b -> StreamK m b)- -> (a -> StreamK m b)- -> StreamK m a- -> StreamK m b-mergeMapWith combine f str = go (leafPairs str)-- where-- go stream =- mkStream $ \st yld sng stp ->- let foldShared = foldStreamShared st yld sng stp- single a = foldShared $ unShare a- yieldk a r = foldShared $ go1 a r- in foldStream (adaptState st) yieldk single stp stream-- go1 a1 stream =- mkStream $ \st yld sng stp ->- let foldShared = foldStreamShared st yld sng stp- stop = foldShared $ unShare a1- single a = foldShared $ unShare a1 `combine` a- yieldk a r =- foldShared $ go $ combine a1 a `cons` nonLeafPairs r- in foldStream (adaptState st) yieldk single stop stream-- -- Exactly the same as "go" except that stop continuation extracts the- -- stream.- leafPairs stream =- mkStream $ \st yld sng stp ->- let foldShared = foldStreamShared st yld sng stp- single a = sng (f a)- yieldk a r = foldShared $ leafPairs1 a r- in foldStream (adaptState st) yieldk single stp stream-- leafPairs1 a1 stream =- mkStream $ \st yld sng _ ->- let stop = sng (f a1)- single a = sng (f a1 `combine` f a)- yieldk a r = yld (f a1 `combine` f a) $ leafPairs r- in foldStream (adaptState st) yieldk single stop stream-- -- Exactly the same as "leafPairs" except that it does not map "f"- nonLeafPairs stream =- mkStream $ \st yld sng stp ->- let foldShared = foldStreamShared st yld sng stp- single a = sng a- yieldk a r = foldShared $ nonLeafPairs1 a r- in foldStream (adaptState st) yieldk single stp stream-- nonLeafPairs1 a1 stream =- mkStream $ \st yld sng _ ->- let stop = sng a1- single a = sng (a1 `combine` a)- yieldk a r = yld (a1 `combine` a) $ nonLeafPairs r- in foldStream (adaptState st) yieldk single stop stream--{--instance Monad m => Applicative (StreamK m) where- {-# INLINE pure #-}- pure = fromPure-- {-# INLINE (<*>) #-}- (<*>) = crossApply-- {-# INLINE liftA2 #-}- liftA2 f x = (<*>) (fmap f x)-- {-# INLINE (*>) #-}- (*>) = crossApplySnd-- {-# INLINE (<*) #-}- (<*) = crossApplyFst---- 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 (StreamK m) where- {-# INLINE return #-}- return = pure-- {-# INLINE (>>=) #-}- (>>=) = flip concatMap--}--{---- Like concatMap but generates stream using an unfold function. Similar to--- unfoldMany but for StreamK.-concatUnfoldr :: IsStream t- => (b -> t m (Maybe (a, b))) -> t m b -> t m a-concatUnfoldr = undefined--}----------------------------------------------------------------------------------- concatIterate - Map and flatten Trees of Streams----------------------------------------------------------------------------------- | Yield an input element in the output stream, map a stream generator on it--- and repeat the process on the resulting stream. Resulting streams are--- flattened using the 'concatMapWith' combinator. This can be used for a depth--- first style (DFS) traversal of a tree like structure.------ Example, list a directory tree using DFS:------ >>> f = StreamK.fromStream . either Dir.readEitherPaths (const Stream.nil)--- >>> input = StreamK.fromPure (Left ".")--- >>> ls = StreamK.concatIterateWith StreamK.append f input------ Note that 'iterateM' is a special case of 'concatIterateWith':------ >>> iterateM f = StreamK.concatIterateWith StreamK.append (StreamK.fromEffect . f) . StreamK.fromEffect------ /Pre-release/----{-# INLINE concatIterateWith #-}-concatIterateWith ::- (StreamK m a -> StreamK m a -> StreamK m a)- -> (a -> StreamK m a)- -> StreamK m a- -> StreamK m a-concatIterateWith combine f = iterateStream-- where-- iterateStream = concatMapWith combine generate-- generate x = x `cons` iterateStream (f x)---- | Like 'concatIterateWith' but uses the pairwise flattening combinator--- 'mergeMapWith' for flattening the resulting streams. This can be used for a--- balanced traversal of a tree like structure.------ Example, list a directory tree using balanced traversal:------ >>> f = StreamK.fromStream . either Dir.readEitherPaths (const Stream.nil)--- >>> input = StreamK.fromPure (Left ".")--- >>> ls = StreamK.mergeIterateWith StreamK.interleave f input------ /Pre-release/----{-# INLINE mergeIterateWith #-}-mergeIterateWith ::- (StreamK m a -> StreamK m a -> StreamK m a)- -> (a -> StreamK m a)- -> StreamK m a- -> StreamK m a-mergeIterateWith combine f = iterateStream-- where-- iterateStream = mergeMapWith combine generate-- generate x = x `cons` iterateStream (f x)----------------------------------------------------------------------------------- Flattening Graphs----------------------------------------------------------------------------------- To traverse graphs we need a state to be carried around in the traversal.--- For example, we can use a hashmap to store the visited status of nodes.---- | Like 'iterateMap' but carries a state in the stream generation function.--- This can be used to traverse graph like structures, we can remember the--- visited nodes in the state to avoid cycles.------ Note that a combination of 'iterateMap' and 'usingState' can also be used to--- traverse graphs. However, this function provides a more localized state--- instead of using a global state.------ See also: 'mfix'------ /Pre-release/----{-# INLINE concatIterateScanWith #-}-concatIterateScanWith- :: Monad m- => (StreamK m a -> StreamK m a -> StreamK m a)- -> (b -> a -> m (b, StreamK m a))- -> m b- -> StreamK m a- -> StreamK m a-concatIterateScanWith combine f initial stream =- concatEffect $ do- b <- initial- iterateStream (b, stream)-- where-- iterateStream (b, s) = pure $ concatMapWith combine (generate b) s-- generate b a = a `cons` feedback b a-- feedback b a = concatEffect $ f b a >>= iterateStream----------------------------------------------------------------------------------- Either streams----------------------------------------------------------------------------------- Keep concating either streams as long as rights are generated, stop as soon--- as a left is generated and concat the left stream.------ See also: 'handle'------ /Unimplemented/----{--concatMapEitherWith- :: (forall x. t m x -> t m x -> t m x)- -> (a -> t m (Either (StreamK m b) b))- -> StreamK m a- -> StreamK m b-concatMapEitherWith = undefined--}---- XXX We should prefer using the Maybe stream returning signatures over this.--- This API should perhaps be removed in favor of those.---- | In an 'Either' stream iterate on 'Left's. This is a special case of--- 'concatIterateWith':------ >>> concatIterateLeftsWith combine f = StreamK.concatIterateWith combine (either f (const StreamK.nil))------ To traverse a directory tree:------ >>> input = StreamK.fromPure (Left ".")--- >>> ls = StreamK.concatIterateLeftsWith StreamK.append (StreamK.fromStream . Dir.readEither) input------ /Pre-release/----{-# INLINE concatIterateLeftsWith #-}-concatIterateLeftsWith- :: (b ~ Either a c)- => (StreamK m b -> StreamK m b -> StreamK m b)- -> (a -> StreamK m b)- -> StreamK m b- -> StreamK m b-concatIterateLeftsWith combine f =- concatIterateWith combine (either f (const nil))----------------------------------------------------------------------------------- Interleaving---------------------------------------------------------------------------------infixr 6 `interleave`---- 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.---- | 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.------ When joining many streams in a left associative manner earlier streams will--- get exponential priority than the ones joining later. Because of exponential--- weighting it can be used with 'concatMapWith' even on a large number of--- streams.----{-# INLINE interleave #-}-interleave :: StreamK m a -> StreamK m a -> StreamK m a-interleave 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 (interleave m2 r)- foldStream st yieldk single stop m1--infixr 6 `interleaveFst`---- | Like `interleave` but stops interleaving as soon as the first stream stops.----{-# INLINE interleaveFst #-}-interleaveFst :: StreamK m a -> StreamK m a -> StreamK m a-interleaveFst 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 (interleave s1 r)- in foldStream st yieldk single stop s2--infixr 6 `interleaveMin`---- | Like `interleave` but stops interleaving as soon as any of the two streams--- stops.----{-# INLINE interleaveMin #-}-interleaveMin :: StreamK m a -> StreamK m a -> StreamK m a-interleaveMin m1 m2 = mkStream $ \st yld _ stp -> do- let stop = stp- -- "single a" is defined as "yld a (interleaveMin m2 nil)" instead of- -- "sng a" to keep the behaviour consistent with the yield- -- continuation.- single a = yld a (interleaveMin m2 nil)- yieldk a r = yld a (interleaveMin m2 r)- foldStream st yieldk single stop m1------------------------------------------------------------------------------------ Generation----------------------------------------------------------------------------------{-# INLINE unfoldr #-}-unfoldr :: (b -> Maybe (a, b)) -> b -> StreamK 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 unfoldrMWith #-}-unfoldrMWith :: Monad m =>- (m a -> StreamK m a -> StreamK m a)- -> (b -> m (Maybe (a, b)))- -> b- -> StreamK m a-unfoldrMWith cns step = go-- where-- go s = sharedMWith cns $ \yld _ stp -> do- r <- step s- case r of- Just (a, b) -> yld a (go b)- Nothing -> stp--{-# INLINE unfoldrM #-}-unfoldrM :: Monad m => (b -> m (Maybe (a, b))) -> b -> StreamK m a-unfoldrM = unfoldrMWith consM---- | Generate an infinite stream by repeating a pure value.------ /Pre-release/-{-# INLINE repeat #-}-repeat :: a -> StreamK m a-repeat a = let x = cons a x in x---- | Like 'repeatM' but takes a stream 'cons' operation to combine the actions--- in a stream specific manner. A serial cons would repeat the values serially--- while an async cons would repeat concurrently.------ /Pre-release/-repeatMWith :: (m a -> t m a -> t m a) -> m a -> t m a-repeatMWith cns = go-- where-- go m = m `cns` go m--{-# INLINE replicateMWith #-}-replicateMWith :: (m a -> StreamK m a -> StreamK m a) -> Int -> m a -> StreamK m a-replicateMWith cns n m = go n-- where-- go cnt = if cnt <= 0 then nil else m `cns` go (cnt - 1)--{-# INLINE fromIndicesMWith #-}-fromIndicesMWith ::- (m a -> StreamK m a -> StreamK m a) -> (Int -> m a) -> StreamK m a-fromIndicesMWith cns gen = go 0-- where-- go i = mkStream $ \st stp sng yld -> do- foldStreamShared st stp sng yld (gen i `cns` go (i + 1))--{-# INLINE iterateMWith #-}-iterateMWith :: Monad m =>- (m a -> StreamK m a -> StreamK m a) -> (a -> m a) -> m a -> StreamK m a-iterateMWith cns step = go-- where-- go s = mkStream $ \st stp sng yld -> do- !next <- s- foldStreamShared st stp sng yld (return next `cns` go (step next))--{-# INLINE headPartial #-}-headPartial :: Monad m => StreamK m a -> m a-headPartial = foldrM (\x _ -> return x) (error "head of nil")--{-# INLINE tailPartial #-}-tailPartial :: StreamK m a -> StreamK 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---- | We can define cyclic structures using @let@:------ >>> let (a, b) = ([1, b], head a) in (a, b)--- ([1,1],1)------ The function @fix@ defined as:------ >>> fix f = let x = f x in x------ ensures that the argument of a function and its output refer to the same--- lazy value @x@ i.e. the same location in memory. Thus @x@ can be defined--- in terms of itself, creating structures with cyclic references.------ >>> f ~(a, b) = ([1, b], head a)--- >>> fix f--- ([1,1],1)------ 'Control.Monad.mfix' is essentially the same as @fix@ but for monadic--- values.------ Using 'mfix' for streams we can construct a stream in which each element of--- the stream is defined in a cyclic fashion. The argument of the function--- being fixed represents the current element of the stream which is being--- returned by the stream monad. Thus, we can use the argument to construct--- itself.------ In the following example, the argument @action@ of the function @f@--- represents the tuple @(x,y)@ returned by it in a given iteration. We define--- the first element of the tuple in terms of the second.------ >>> import System.IO.Unsafe (unsafeInterleaveIO)------ >>> :{--- main = Stream.fold (Fold.drainMapM print) $ StreamK.toStream $ StreamK.mfix f--- where--- f action = StreamK.unCross $ do--- let incr n act = fmap ((+n) . snd) $ unsafeInterleaveIO act--- x <- StreamK.mkCross $ StreamK.fromStream $ Stream.sequence $ Stream.fromList [incr 1 action, incr 2 action]--- y <- StreamK.mkCross $ StreamK.fromStream $ Stream.fromList [4,5]--- return (x, y)--- :}------ Note: you cannot achieve this by just changing the order of the monad--- statements because that would change the order in which the stream elements--- are generated.------ Note that the function @f@ must be lazy in its argument, that's why we use--- 'unsafeInterleaveIO' on @action@ because IO monad is strict.------ /Pre-release/-{-# INLINE mfix #-}-mfix :: Monad m => (m a -> StreamK m a) -> StreamK 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-- -- fix the head element of the stream- xs = fix (f . headPartial)-- -- now fix the tail recursively- ys = mfix (tailPartial . f)------------------------------------------------------------------------------------ Conversions------------------------------------------------------------------------------------ |--- >>> fromFoldable = Prelude.foldr StreamK.cons StreamK.nil------ Construct a stream from a 'Foldable' containing pure values:----{-# INLINE fromFoldable #-}-fromFoldable :: Foldable f => f a -> StreamK m a-fromFoldable = Prelude.foldr cons nil--{-# INLINE fromFoldableM #-}-fromFoldableM :: (Foldable f, Monad m) => f (m a) -> StreamK m a-fromFoldableM = Prelude.foldr consM nil------------------------------------------------------------------------------------ Deconstruction----------------------------------------------------------------------------------{-# INLINE uncons #-}-uncons :: Applicative m => StreamK m a -> m (Maybe (a, StreamK m a))-uncons m =- let stop = pure Nothing- single a = pure (Just (a, nil))- yieldk a r = pure (Just (a, r))- in foldStream defState yieldk single stop m--{-# INLINE tail #-}-tail :: Applicative m => StreamK m a -> m (Maybe (StreamK m a))-tail =- let stop = pure Nothing- single _ = pure $ Just nil- yieldk _ r = pure $ Just r- in foldStream defState yieldk single stop---- | Extract all but the last element of the stream, if any.------ Note: This will end up buffering the entire stream.------ /Pre-release/-{-# INLINE init #-}-init :: Applicative m => StreamK m a -> m (Maybe (StreamK m a))-init = go1- where- go1 m1 = do- (\case- Nothing -> Nothing- Just (h, t) -> Just $ go h t) <$> uncons m1- 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----------------------------------------------------------------------------------- Reordering----------------------------------------------------------------------------------- | Lazy left fold to a stream.-{-# INLINE foldlS #-}-foldlS ::- (StreamK m b -> a -> StreamK m b) -> StreamK m b -> StreamK m a -> StreamK m b-foldlS step = go- 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--{-# INLINE reverse #-}-reverse :: StreamK m a -> StreamK m a-reverse = foldlS (flip cons) nil----------------------------------------------------------------------------------- Running effects----------------------------------------------------------------------------------- | Run an action before evaluating the stream.-{-# INLINE before #-}-before :: Monad m => m b -> StreamK m a -> StreamK m a-before action stream =- mkStream $ \st yld sng stp ->- action >> foldStreamShared st yld sng stp stream---- | concat . fromEffect-{-# INLINE concatEffect #-}-concatEffect :: Monad m => m (StreamK m a) -> StreamK m a-concatEffect action =- mkStream $ \st yld sng stp ->- action >>= foldStreamShared st yld sng stp--{-# INLINE concatMapEffect #-}-concatMapEffect :: Monad m => (b -> StreamK m a) -> m b -> StreamK m a-concatMapEffect f action =- mkStream $ \st yld sng stp ->- action >>= foldStreamShared st yld sng stp . f----------------------------------------------------------------------------------- Stream with a cross product style monad instance----------------------------------------------------------------------------------- | A newtype wrapper for the 'StreamK' type adding a cross product style--- monad instance.------ A 'Monad' bind behaves like a @for@ loop:------ >>> :{--- Stream.fold Fold.toList $ StreamK.toStream $ StreamK.unCross $ do--- x <- StreamK.mkCross $ StreamK.fromStream $ Stream.fromList [1,2]--- -- Perform the following actions for each x in the stream--- return x--- :}--- [1,2]------ Nested monad binds behave like nested @for@ loops:------ >>> :{--- Stream.fold Fold.toList $ StreamK.toStream $ StreamK.unCross $ do--- x <- StreamK.mkCross $ StreamK.fromStream $ Stream.fromList [1,2]--- y <- StreamK.mkCross $ StreamK.fromStream $ Stream.fromList [3,4]--- -- Perform the following actions for each x, for each y--- return (x, y)--- :}--- [(1,3),(1,4),(2,3),(2,4)]----newtype CrossStreamK m a = CrossStreamK {unCrossStreamK :: StreamK m a}- deriving (Functor, Semigroup, Monoid, Foldable)---- | Wrap the 'StreamK' type in a 'CrossStreamK' newtype to enable cross--- product style applicative and monad instances.------ This is a type level operation with no runtime overhead.-{-# INLINE mkCross #-}-mkCross :: StreamK m a -> CrossStreamK m a-mkCross = CrossStreamK---- | Unwrap the 'StreamK' type from 'CrossStreamK' newtype.------ This is a type level operation with no runtime overhead.-{-# INLINE unCross #-}-unCross :: CrossStreamK m a -> StreamK m a-unCross = unCrossStreamK---- Pure (Identity monad) stream instances-deriving instance Traversable (CrossStreamK Identity)-deriving instance IsList (CrossStreamK Identity a)-deriving instance (a ~ Char) => IsString (CrossStreamK Identity a)--- deriving instance Eq a => Eq (CrossStreamK Identity a)--- deriving instance Ord a => Ord (CrossStreamK Identity a)---- Do not use automatic derivation for this to show as "fromList" rather than--- "fromList Identity".-instance Show a => Show (CrossStreamK Identity a) where- {-# INLINE show #-}- show (CrossStreamK xs) = show xs--instance Read a => Read (CrossStreamK Identity a) where- {-# INLINE readPrec #-}- readPrec = fmap CrossStreamK readPrec----------------------------------------------------------------------------------- Applicative----------------------------------------------------------------------------------- Note: we need to define all the typeclass operations because we want to--- INLINE them.-instance Monad m => Applicative (CrossStreamK m) where- {-# INLINE pure #-}- pure x = CrossStreamK (fromPure x)-- {-# INLINE (<*>) #-}- (CrossStreamK s1) <*> (CrossStreamK s2) =- CrossStreamK (crossApply s1 s2)-- {-# INLINE liftA2 #-}- liftA2 f x = (<*>) (fmap f x)-- {-# INLINE (*>) #-}- (CrossStreamK s1) *> (CrossStreamK s2) =- CrossStreamK (crossApplySnd s1 s2)-- {-# INLINE (<*) #-}- (CrossStreamK s1) <* (CrossStreamK s2) =- CrossStreamK (crossApplyFst s1 s2)----------------------------------------------------------------------------------- Monad---------------------------------------------------------------------------------instance Monad m => Monad (CrossStreamK m) where- return = pure-- -- Benchmarks better with CPS bind and pure:- -- Prime sieve (25x)- -- n binds, breakAfterSome, filterAllIn, state transformer (~2x)- --- {-# INLINE (>>=) #-}- (>>=) (CrossStreamK m) f =- CrossStreamK (bindWith append m (unCrossStreamK . f))-- {-# INLINE (>>) #-}- (>>) = (*>)----------------------------------------------------------------------------------- Transformers---------------------------------------------------------------------------------instance (MonadIO m) => MonadIO (CrossStreamK m) where- liftIO x = CrossStreamK (fromEffect $ liftIO x)--instance MonadTrans CrossStreamK where- {-# INLINE lift #-}- lift x = CrossStreamK (fromEffect x)--instance (MonadThrow m) => MonadThrow (CrossStreamK m) where- throwM = lift . throwM
+ src/Streamly/Internal/Data/Stream/Top.hs view
@@ -0,0 +1,457 @@+{-# LANGUAGE CPP #-}+-- |+-- Module : Streamly.Internal.Data.Stream.Top+-- Copyright : (c) 2020 Composewell Technologies+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+-- Top level module that can depend on all other lower level Stream modules.+--+-- Design notes:+--+-- The order of arguments in the join operations should ideally be opposite. It+-- should be such that the infinite stream is the last one. The transformation+-- should be on the last argument, so if you curry the functions with all other+-- arguments we get a @Stream -> Stream@ function. The first stream argument+-- may be considered as a config or modifier for the operation.+--+-- Benefit of changing the order is that we get a more intuitive Stream ->+-- Stream transformation after currying all other arguments. The inner loop+-- streams become arguments for the transformation, more like local modifiers+-- for the global outer stream as the last argument. Thus we can continue using+-- transformations on the outer stream in a composed pipeline. Otherwise we can+-- use flip to flip the order.+--+-- The fact that the inner stream can be used in the loop multiple times also+-- tells that this is not the real effectful stream, it is more like a pure+-- stream or an array. In fact we may consider using an Identity streams as+-- inner streams in which case these functions will not look nice.+--+-- Downsides:+--+-- * Maybe less intuitive to think about, because we usually think the first+-- stream as the outer loop and second as the inner.+-- * Zip and merge operations will continue using the opposite order.+-- * Need to change the order of cross, crossWith operations as well+-- * It will be inconsistent with Data.List. The functions cannot be used as+-- intuitive operators.+--+-- The choice is similar to concatMap vs bind. concatMap is pipeline+-- composition friendly but bind is user intuition friendly. Another option is+-- to have other functions with a different argument order e.g. flippedCross+-- instead of cross.+--+-- If we change the order we have to make sure that we have a consistent+-- convention for set-like and the cross join operations.++module Streamly.Internal.Data.Stream.Top+ (+ -- * Straight Joins+ -- | These are set-like operations but not exactly set operations because+ -- streams are not necessarily sets, they may have duplicated elements.+ -- These operations are generic i.e. they work on streams of unconstrained+ -- types, therefore, they have quadratic performance characterstics. For+ -- better performance using Set or Map structures see the+ -- Streamly.Internal.Data.Stream.Container module.+ intersectBy+ , deleteFirstsBy+ , unionBy++ -- Set like operations on sorted streams+ , sortedIntersectBy+ , sortedDeleteFirstsBy+ , sortedUnionBy++ -- * Cross Joins+ , innerJoin++ -- Joins on sorted stream+ , innerSortedJoin+ , leftSortedJoin+ , outerSortedJoin+ )+where++#include "inline.hs"++import Control.Monad.IO.Class (MonadIO(..))+import Data.IORef (newIORef, readIORef, modifyIORef')+import Streamly.Internal.Data.Fold.Type (Fold)+import Streamly.Internal.Data.Stream.Type (Stream(..), Step(..), cross)++import qualified Data.List as List+import qualified Streamly.Internal.Data.Fold as Fold+import qualified Streamly.Internal.Data.Scanl as Scanl+import qualified Streamly.Internal.Data.Stream.Type as Stream+import qualified Streamly.Internal.Data.Stream.Transform as Stream++import Prelude hiding (filter, zipWith, concatMap, concat)++#include "DocTestDataStream.hs"++------------------------------------------------------------------------------+-- SQL Joins+------------------------------------------------------------------------------+--+-- Some references:+-- * https://en.wikipedia.org/wiki/Relational_algebra+-- * https://en.wikipedia.org/wiki/Join_(SQL)++-- TODO: OrdSet/IntSet/hashmap based versions of these. With Eq only+-- constraint, the best would be to use an Array with linear search. If the+-- second stream is sorted we can also use a binary search, using Ord+-- constraint or an ordering function.+--+-- For Storables we can cache the second stream into an unboxed array for+-- possibly faster access/compact representation?+--+-- If we do not want to keep the stream in memory but always read it from the+-- source (disk/network) every time we iterate through it then we can do that+-- too by reading the stream every time, the stream must have immutable state+-- in that case and the user is responsible for the behavior if the stream+-- source changes during iterations. We can also use an Unfold instead of+-- stream. We probably need a way to distinguish streams that can be read+-- mutliple times without any interference (e.g. unfolding a stream using an+-- immutable handle would work i.e. using pread/pwrite instead of maintaining+-- an offset in the handle).++-- XXX We can do this concurrently.+-- XXX If the second stream is sorted and passed as an Array we could use+-- binary search if we have an Ord instance or Ordering returning function. The+-- time complexity would then become (m x log n).++-- | Like 'cross' but emits only those tuples where @a == b@ using the supplied+-- equality predicate. This is essentially a @cross intersection@ of two+-- streams.+--+-- Definition:+--+-- >>> innerJoin eq s1 s2 = Stream.filter (\(a, b) -> a `eq` b) $ Stream.cross s1 s2+--+-- The second (inner) stream must be finite. Moreover, it must be either pure+-- or capable of multiple evaluations. If not then the caller should cache it+-- in an 'Data.Array.Array', if the type does not have an 'Unbox' instance then+-- use the Generic 'Data.Array.Generic.Array'. Convert the array to stream+-- before calling this function. Caching may also improve performance if the+-- stream is expensive to evaluate.+--+-- If you care about performance this function should be your last choice among+-- all inner joins. 'Streamly.Internal.Data.Unfold.innerJoin' is a much faster+-- fused alternative. 'innerSortedJoin' is a faster alternative when streams+-- are sorted. 'innerOrdJoin' is an order of magnitude faster alternative when+-- the type has an 'Ord' instance.+--+-- Note: Conceptually, this is a commutative operation. Result includes all the+-- elements from the left and the right stream. The order of streams can be+-- changed without affecting results, except for the ordering within the tuple.+--+-- Time: O(m x n)+--+-- /Pre-release/+{-# INLINE innerJoin #-}+innerJoin :: Monad m =>+ (a -> b -> Bool) -> Stream m a -> Stream m b -> Stream m (a, b)+innerJoin eq s1 s2 = Stream.filter (\(a, b) -> a `eq` b) $ cross s1 s2+{-+innerJoin eq s1 s2 = do+ -- ConcatMap works faster than bind+ Stream.concatMap (\a ->+ Stream.concatMap (\b ->+ if a `eq` b+ then Stream.fromPure (a, b)+ else Stream.nil+ ) s2+ ) s1+-}++-- | A more efficient 'innerJoin' for sorted streams.+--+-- Space: O(1)+--+-- Time: O(m + n)+--+-- /Unimplemented/+{-# INLINE innerSortedJoin #-}+innerSortedJoin ::+ (a -> b -> Ordering) -> Stream m a -> Stream m b -> Stream m (a, b)+innerSortedJoin = undefined++-- | A more efficient 'leftJoin' for sorted streams.+--+-- Space: O(1)+--+-- Time: O(m + n)+--+-- /Unimplemented/+{-# INLINE leftSortedJoin #-}+leftSortedJoin :: -- Monad m =>+ (a -> b -> Ordering) -> Stream m a -> Stream m b -> Stream m (a, Maybe b)+leftSortedJoin _eq _s1 _s2 = undefined++-- | A more efficient 'outerJoin' for sorted streams.+--+-- Space: O(1)+--+-- Time: O(m + n)+--+-- /Unimplemented/+{-# INLINE outerSortedJoin #-}+outerSortedJoin :: -- Monad m =>+ (a -> b -> Ordering)+ -> Stream m a+ -> Stream m b+ -> Stream m (Maybe a, Maybe b)+outerSortedJoin _eq _s1 _s2 = undefined++------------------------------------------------------------------------------+-- Set operations (special joins)+------------------------------------------------------------------------------+--+-- TODO: OrdSet/IntSet/hashmap based versions of these. With Eq only constraint+-- the best would be to use an Array with linear search. If the second stream+-- is sorted we can also use a binary search, using Ord constraint.++-- | Keep only those elements in the first stream that are present in the+-- second stream too. The second stream is folded to a container using the+-- supplied fold and then the elements in the container are looked up using the+-- supplied lookup function.+--+-- The first stream must be finite and must not block.+{-# INLINE filterStreamWith #-}+filterStreamWith :: Monad m =>+ Fold m a (f a)+ -> (a -> f a -> Bool)+ -> Stream m a+ -> Stream m a+ -> Stream m a+filterStreamWith fld member s1 s2 =+ Stream.concatEffect+ $ do+ xs <- Stream.fold fld s2+ return $ Stream.filter (`member` xs) s1++-- XXX instead of folding the second stream to a list we could use it directly.+-- If the user wants they can generate the stream from an array and also call+-- uniq or nub on it. We can provide a convenience Stream -> Stream to cache+-- a finite stream in an array and serve it from the cache. The user can decide+-- what is best based on the context. They can also choose to use a boxed or+-- unboxed array for caching. To force caching we can make the second stream+-- monad type Identity. But that may be less flexible. One option is to use+-- cachedIntersectBy etc for automatic caching.++-- | 'intersectBy' returns a subsequence of the first stream which intersects+-- with the second stream. Note that this is not a commutative operation unlike+-- a set intersection, because of duplicate elements in the stream the order of+-- the streams matters. This is similar to 'Data.List.intersectBy'. Note that+-- intersectBy is a special case of 'innerJoin'.+--+-- >>> f s1 s2 = Stream.fold Fold.toList $ Stream.intersectBy (==) (Stream.fromList s1) (Stream.fromList s2)+-- >>> f [1,3,4,4,5] [2,3,4,5,5]+-- [3,4,4,5]+--+-- First stream can be infinite, the second stream must be finite and must be+-- capable of multiple evaluations.+--+-- Space: O(n) where @n@ is the number of elements in the second stream.+--+-- Time: O(m x n) where @m@ is the number of elements in the first stream and+-- @n@ is the number of elements in the second stream.+--+-- /Pre-release/+{-# INLINE intersectBy #-}+intersectBy :: Monad m =>+ (a -> a -> Bool) -> Stream m a -> Stream m a -> Stream m a+intersectBy eq =+ -- XXX Use an (unboxed) array instead.+ filterStreamWith+ (Fold.postscanlMaybe (Scanl.uniqBy eq) Fold.toListRev)+ (List.any . eq)++-------------------------------------------------------------------------------+-- Intersection of sorted streams+-------------------------------------------------------------------------------++-- XXX The sort order is not important as long both the streams have the same+-- sort order. We need to move only in one direction in each stream.+-- XXX Fix the argument order to use the same behavior as intersectBy.++-- | Like 'intersectBy' but assumes that the input streams are sorted in+-- ascending order. To use it on streams sorted in descending order pass an+-- inverted comparison function returning GT for less than and LT for greater+-- than.+--+-- Both streams can be infinite.+--+-- Space: O(1)+--+-- Time: O(m+n)+--+-- /Pre-release/+{-# INLINE_NORMAL sortedIntersectBy #-}+sortedIntersectBy :: Monad m =>+ (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a+sortedIntersectBy cmp (Stream stepa ta) (Stream stepb tb) =+ Stream step+ ( ta -- left stream state+ , tb -- right stream state+ , Nothing -- left value+ , Nothing -- right value+ )++ where++ {-# INLINE_LATE step #-}+ -- step 1, fetch the first value+ step gst (sa, sb, Nothing, b) = do+ r <- stepa gst sa+ return $ case r of+ Yield a sa' -> Skip (sa', sb, Just a, b) -- step 2/3+ Skip sa' -> Skip (sa', sb, Nothing, b)+ Stop -> Stop++ -- step 2, fetch the second value+ step gst (sa, sb, a@(Just _), Nothing) = do+ r <- stepb gst sb+ return $ case r of+ Yield b sb' -> Skip (sa, sb', a, Just b) -- step 3+ Skip sb' -> Skip (sa, sb', a, Nothing)+ Stop -> Stop++ -- step 3, compare the two values+ step _ (sa, sb, Just a, Just b) = do+ let res = cmp a b+ return $ case res of+ GT -> Skip (sa, sb, Just a, Nothing) -- step 2+ LT -> Skip (sa, sb, Nothing, Just b) -- step 1+ EQ -> Yield a (sa, sb, Nothing, Just b) -- step 1++-- | Returns a subsequence of the first stream, deleting first occurrences of+-- those elements that are present in the second stream. Note that this is not+-- a commutative operation. This is similar to the 'Data.List.deleteFirstsBy'.+--+-- >>> f xs ys = Stream.fold Fold.toList $ Stream.deleteFirstsBy (==) (Stream.fromList xs) (Stream.fromList ys)+-- >>> f [1,2,2,3,3,5] [1,2,2,3,4]+-- [3,5]+--+-- The following holds:+--+-- > deleteFirstsBy (==) (Stream.ordNub s2 `append` s1) s2 === s1+-- > deleteFirstsBy (==) (Stream.ordNub s2 `interleave` s1) s2 === s1+--+-- First stream can be infinite, second stream must be finite.+--+-- Space: O(m) where @m@ is the number of elements in the first stream.+--+-- Time: O(m x n) where @m@ is the number of elements in the first stream and+-- @n@ is the number of elements in the second stream.+--+-- /Pre-release/+{-# INLINE deleteFirstsBy #-}+deleteFirstsBy :: Monad m =>+ (a -> a -> Bool) -> Stream m a -> Stream m a -> Stream m a+deleteFirstsBy eq s2 s1 =+ -- XXX s2 can be a sorted mutable array and we can use binary+ -- search to find. Mark the element deleted, count the deletions+ -- and reconsolidate the array when a min number of elements is+ -- deleted.++ -- XXX Use StreamK or list as second argument instead of Stream to avoid+ -- concatEffect?+ Stream.concatEffect $ do+ xs <- Stream.toList s1+ -- It reverses the list but that is fine.+ let del x =+ List.foldl' (\(ys,res) y ->+ if not res && x `eq` y+ then (ys, True)+ else (y:ys, res)) ([], False)+ g (ys,_) x =+ let (ys1, deleted) = del x ys+ in if deleted+ then (ys1, Nothing)+ else (ys1, Just x)+ in return+ $ Stream.catMaybes+ $ fmap snd+ $ Stream.postscanl' g (xs, Nothing) s2++-- | A more efficient 'deleteFirstsBy' for streams sorted in ascending order.+--+-- Both streams can be infinite.+--+-- Space: O(1)+--+-- /Unimplemented/+{-# INLINE sortedDeleteFirstsBy #-}+sortedDeleteFirstsBy :: -- (Monad m) =>+ (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a+sortedDeleteFirstsBy _eq _s1 _s2 = undefined++-- XXX Remove the MonadIO constraint. We can just cache one stream and then+-- implement using differenceEqBy.++-- | Returns the first stream appended with those unique elements from the+-- second stream that are not already present in the first stream. Note that+-- this is not a commutative operation unlike a set union, argument order+-- matters. The behavior is similar to 'Data.List.unionBy'.+--+-- Equivalent to the following except that @s2@ is evaluated only once:+--+-- >>> unionBy eq s1 s2 = s1 `Stream.append` Stream.deleteFirstsBy eq s1 (Stream.ordNub s2)+--+-- Example:+--+-- >>> f s1 s2 = Stream.fold Fold.toList $ Stream.unionBy (==) (Stream.fromList s1) (Stream.fromList s2)+-- >>> f [1,2,2,4] [1,1,2,3,3]+-- [1,2,2,4,3]+--+-- First stream can be infinite, but second stream must be finite. Note that if+-- the first stream is infinite the union means just the first stream. Thus+-- union is useful only when both streams are finite. See 'sortedUnionBy' where+-- union can work on infinite streams if they are sorted.+--+-- Space: O(n)+--+-- Time: O(m x n)+--+-- /Pre-release/+{-# INLINE unionBy #-}+unionBy :: MonadIO m =>+ (a -> a -> Bool) -> Stream m a -> Stream m a -> Stream m a+unionBy eq s2 s1 =+ Stream.concatEffect+ $ do+ -- XXX use a rewrite rule such that if a list converted to stream+ -- is passed to unionBy then this becomes an identity operation.+ xs <- Stream.fold Fold.toList s1+ -- XXX we can use postscanlMAfter' instead of IORef+ ref <- liftIO $ newIORef $! List.nubBy eq xs+ let f x = do+ liftIO $ modifyIORef' ref (List.deleteBy eq x)+ return x+ s3 = Stream.concatEffect+ $ do+ xs1 <- liftIO $ readIORef ref+ return $ Stream.fromList xs1+ return $ Stream.mapM f s2 `Stream.append` s3++-- | A more efficient 'unionBy' for sorted streams.+--+-- Note that the behavior is different from 'unionBy'. In 'unionBy' we append+-- the unique elements from second stream only after exhausting the first one+-- whereas in sorted streams we can determine unique elements early even when+-- we are going through the first stream. Thus the result is an interleaving of+-- the two streams, merging those elements from the second stream that are not+-- present in the first.+--+-- Space: O(1)+--+-- Both streams can be infinite.+--+-- /Unimplemented/+{-# INLINE sortedUnionBy #-}+sortedUnionBy :: -- (Monad m) =>+ (a -> a -> Ordering) -> Stream m a -> Stream m a -> Stream m a+sortedUnionBy _eq _s1 _s2 = undefined
src/Streamly/Internal/Data/Stream/Transform.hs view
@@ -1,1056 +1,2289 @@--- |--- Module : Streamly.Internal.Data.Stream.Transform--- Copyright : (c) 2017 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC--module Streamly.Internal.Data.Stream.Transform- (- -- * Piping- -- | Pass through a 'Pipe'.- transform-- -- * Folding- , foldrS-- -- * Mapping- -- | Stateless one-to-one maps.- , sequence- , mapM-- -- * Mapping Side Effects (Observation)- -- | See also the intersperse*_ combinators.- , trace- , trace_- , tap-- -- * Scanning- , scan- , scanMany- , postscan- , smapM- , scanlMAfter'-- -- * Filtering- -- | Produce a subset of the stream using criteria based on the values of- -- the elements. We can use a concatMap and scan for filtering but these- -- combinators are more efficient and convenient.-- -- mapMaybeM is a general filtering combinator as we can map the stream to- -- Just/Nothing using any stateful fold and then use this to filter out.- , mapMaybeM- , mapMaybe- , catMaybes- , scanMaybe-- , with- , deleteBy- , filter- , filterM-- -- Stateful/scanning filters- , uniq- , uniqBy- , prune- , repeated-- -- * Trimming- -- | Produce a subset of the stream trimmed at ends.-- , take- , takeWhile- , takeWhileM- , takeWhileLast- , takeWhileAround- , drop- , dropLast- , dropWhile- , dropWhileM- , dropWhileLast- , dropWhileAround-- -- * Position Indexing- , indexed- , indexedR-- -- * Time Indexing- , timestamped- , timestampWith- , timeIndexed- , timeIndexWith-- -- * Searching- , findIndices -- XXX indicesBy- , elemIndices -- XXX indicesOf-- -- * Rolling map- -- | Map using the previous element.- , rollingMapM- , rollingMap- , rollingMap2-- -- Merge-- -- * Inserting Elements- -- | Produce a superset of the stream. This is the opposite of- -- filtering/sampling. We can always use concatMap and scan for inserting- -- but these combinators are more efficient and convenient.-- -- Element agnostic (Opposite of sampling)- , intersperse- , intersperseM -- XXX naming- , intersperseMWith-- , intersperseMSuffix- , intersperseMSuffixWith-- -- , interspersePrefix- -- , interspersePrefixBySpan-- -- * Inserting Side Effects/Time- , intersperseM_ -- XXX naming- , delay- , intersperseMSuffix_- , delayPost- , intersperseMPrefix_- , delayPre-- -- * Element Aware Insertion- -- | Opposite of filtering- , insertBy- -- , intersperseByBefore- -- , intersperseByAfter-- -- Fold and Unfold, Buffering-- -- * Reordering- , reverse- , reverse'- , reassembleBy-- -- * Either Streams- -- Move these to Streamly.Data.Either.Stream?- , catLefts- , catRights- , catEithers- )-where--#include "inline.hs"--import Control.Concurrent (threadDelay)-import Control.Monad (void)-import Control.Monad.IO.Class (MonadIO (liftIO))-import Data.Either (fromLeft, isLeft, isRight, fromRight)-import Data.Maybe (isJust, fromJust)--import Streamly.Internal.Data.Fold.Type (Fold)-import Streamly.Internal.Data.Pipe (Pipe)-import Streamly.Internal.Data.Time.Units (AbsTime, RelTime64)--import qualified Streamly.Internal.Data.Fold as FL--- import qualified Streamly.Internal.Data.Fold.Window as Window-import qualified Streamly.Internal.Data.Stream.StreamD.Transform as D-import qualified Streamly.Internal.Data.Stream.StreamD.Type as D-import qualified Streamly.Internal.Data.Stream.StreamK.Type as K---import Streamly.Internal.Data.Stream.Bottom-import Streamly.Internal.Data.Stream.Type--import Prelude hiding- ( filter, drop, dropWhile, take, takeWhile, foldr, map, mapM, sequence- , reverse, foldr1 , repeat, scanl, scanl1, zipWith)------- $setup--- >>> :m--- >>> import Control.Concurrent (threadDelay)--- >>> import Control.Monad (void)--- >>> import Control.Monad.IO.Class (MonadIO (liftIO))--- >>> import Data.Either (fromLeft, fromRight, isLeft, isRight, either)--- >>> import Data.Function ((&))--- >>> import Data.Maybe (fromJust, isJust)--- >>> import Prelude hiding (filter, drop, dropWhile, take, takeWhile, foldr, map, mapM, sequence, reverse, foldr1 , scanl, scanl1)--- >>> import Streamly.Internal.Data.Stream (Stream)--- >>> import qualified Streamly.Data.Fold as Fold--- >>> import qualified Streamly.Data.Unfold as Unfold--- >>> import qualified Streamly.Internal.Data.Fold as Fold (filtering)--- >>> import qualified Streamly.Internal.Data.Fold.Window as Window--- >>> import qualified Streamly.Internal.Data.Stream as Stream--- >>> import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))------ >>> hSetBuffering stdout LineBuffering---- XXX because of the use of D.cons for appending, folds and scans have--- quadratic complexity when iterated over a stream. We should use StreamK for--- linear performance on iteration.----------------------------------------------------------------------------------- Piping----------------------------------------------------------------------------------- | Use a 'Pipe' to transform a stream.------ /Pre-release/----{-# INLINE transform #-}-transform :: Monad m => Pipe m a b -> Stream m a -> Stream m b-transform pipe xs = fromStreamD $ D.transform pipe (toStreamD xs)----------------------------------------------------------------------------------- Transformation Folds----------------------------------------------------------------------------------- | Right fold to a streaming monad.------ > foldrS Stream.cons Stream.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.------ >>> input = Stream.fromList [1..5]--- >>> Stream.fold Fold.toList $ Stream.foldrS Stream.cons Stream.nil input--- [1,2,3,4,5]------ Find if any element in the stream is 'True':------ >>> step x xs = if odd x then Stream.fromPure True else xs--- >>> input = Stream.fromList (2:4:5:undefined) :: Stream IO Int--- >>> Stream.fold Fold.toList $ Stream.foldrS step (Stream.fromPure False) input--- [True]------ Map (+2) on odd elements and filter out the even elements:------ >>> step x xs = if odd x then (x + 2) `Stream.cons` xs else xs--- >>> input = Stream.fromList [1..5] :: Stream IO Int--- >>> Stream.fold Fold.toList $ Stream.foldrS step Stream.nil input--- [3,5,7]------ /Pre-release/-{-# INLINE foldrS #-}-foldrS ::- (a -> Stream m b -> Stream m b)- -> Stream m b- -> Stream m a- -> Stream m b-foldrS f z xs =- fromStreamK- $ K.foldrS- (\y ys -> toStreamK $ f y (fromStreamK ys))- (toStreamK z)- (toStreamK xs)----------------------------------------------------------------------------------- Transformation by Mapping----------------------------------------------------------------------------------- |--- >>> mapM f = Stream.sequence . fmap f------ Apply a monadic function to each element of the stream and replace it with--- the output of the resulting action.------ >>> s = Stream.fromList ["a", "b", "c"]--- >>> Stream.fold Fold.drain $ Stream.mapM putStr s--- abc----{-# INLINE mapM #-}-mapM :: Monad m => (a -> m b) -> Stream m a -> Stream m b-mapM f m = fromStreamK $ D.toStreamK $ D.mapM f $ toStreamD m---- |--- >>> sequence = Stream.mapM id------ Replace the elements of a stream of monadic actions with the outputs of--- those actions.------ >>> s = Stream.fromList [putStr "a", putStr "b", putStrLn "c"]--- >>> Stream.fold Fold.drain $ Stream.sequence s--- abc----{-# INLINE sequence #-}-sequence :: Monad m => Stream m (m a) -> Stream m a-sequence = mapM id----------------------------------------------------------------------------------- Mapping side effects----------------------------------------------------------------------------------- | 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 = Stream.enumerateFromTo 1 2--- >>> Stream.fold Fold.drain $ Stream.tap (Fold.drainMapM print) s--- 1--- 2------ Compare with 'trace'.----{-# INLINE tap #-}-tap :: Monad m => FL.Fold m a b -> Stream m a -> Stream m a-tap f xs = fromStreamD $ D.tap f (toStreamD xs)---- | Apply a monadic function to each element flowing through the stream and--- discard the results.------ >>> s = Stream.enumerateFromTo 1 2--- >>> Stream.fold Fold.drain $ Stream.trace print s--- 1--- 2------ Compare with 'tap'.----{-# INLINE trace #-}-trace :: Monad m => (a -> m b) -> Stream m a -> Stream m a-trace f = mapM (\x -> void (f x) >> return x)---- | Perform a side effect before yielding each element of the stream and--- discard the results.------ >>> s = Stream.enumerateFromTo 1 2--- >>> Stream.fold Fold.drain $ Stream.trace_ (print "got here") s--- "got here"--- "got here"------ Same as 'intersperseMPrefix_' but always serial.------ See also: 'trace'------ /Pre-release/-{-# INLINE trace_ #-}-trace_ :: Monad m => m b -> Stream m a -> Stream m a-trace_ eff = fromStreamD . D.mapM (\x -> eff >> return x) . toStreamD------------------------------------------------------------------------------------ Scanning------------------------------------------------------------------------------------ | @scanlMAfter' accumulate initial done stream@ is like 'scanlM'' except--- that it provides an additional @done@ function to be applied on the--- accumulator when the stream stops. The result of @done@ is also emitted in--- the stream.------ This function can be used to allocate a resource in the beginning of the--- scan and release it when the stream ends or to flush the internal state of--- the scan at the end.------ /Pre-release/----{-# INLINE scanlMAfter' #-}-scanlMAfter' ::- Monad m- => (b -> a -> m b)- -> m b- -> (b -> m b)- -> Stream m a- -> Stream m b-scanlMAfter' step initial done stream =- fromStreamD $ D.scanlMAfter' step initial done $ toStreamD stream----------------------------------------------------------------------------------- Scanning with a Fold----------------------------------------------------------------------------------- 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.---- | Strict left scan. Scan a stream using the given monadic fold.------ >>> s = Stream.fromList [1..10]--- >>> Stream.fold Fold.toList $ Stream.takeWhile (< 10) $ Stream.scan Fold.sum s--- [0,1,3,6]------ See also: 'usingStateT'------- EXPLANATION:--- >>> scanl' step z = Stream.scan (Fold.foldl' step z)------ Like 'map', 'scanl'' too is a one to one transformation,--- however it adds an extra element.------ >>> s = Stream.fromList [1,2,3,4]--- >>> Stream.fold Fold.toList $ scanl' (+) 0 s--- [0,1,3,6,10]------ >>> Stream.fold Fold.toList $ scanl' (flip (:)) [] s--- [[],[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'@:------ >>> foldl' step z = Stream.fold (Fold.foldl' step z)--- >>> foldl' (\(s, p) x -> (s + x, p * x)) (0,1) s--- (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:------ >>> :{--- foldl' (\(_, p) (s, x) -> (s, p * x)) (0,1)--- $ scanl' (\(s, _) x -> (s + x, x)) (0,1)--- $ Stream.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.----{-# INLINE scan #-}-scan :: Monad m => Fold m a b -> Stream m a -> Stream m b-scan fld m = fromStreamD $ D.scan fld $ toStreamD m---- | Like 'scan' but restarts scanning afresh when the scanning fold--- terminates.----{-# INLINE scanMany #-}-scanMany :: Monad m => Fold m a b -> Stream m a -> Stream m b-scanMany fld m = fromStreamD $ D.scanMany fld $ toStreamD m----------------------------------------------------------------------------------- Filtering----------------------------------------------------------------------------------- | Modify a @Stream m a -> Stream m a@ stream transformation that accepts a--- predicate @(a -> b)@ to accept @((s, a) -> b)@ instead, provided a--- transformation @Stream m a -> Stream m (s, a)@. Convenient to filter with--- index or time.------ >>> filterWithIndex = Stream.with Stream.indexed Stream.filter------ /Pre-release/-{-# INLINE with #-}-with :: Monad m =>- (Stream m a -> Stream m (s, a))- -> (((s, a) -> b) -> Stream m (s, a) -> Stream m (s, a))- -> (((s, a) -> b) -> Stream m a -> Stream m a)-with f comb g = fmap snd . comb g . f---- | Include only those elements that pass a predicate.------ >>> filter p = Stream.filterM (return . p)--- >>> filter p = Stream.mapMaybe (\x -> if p x then Just x else Nothing)--- >>> filter p = Stream.scanMaybe (Fold.filtering p)----{-# INLINE filter #-}-filter :: Monad m => (a -> Bool) -> Stream m a -> Stream m a--- filter p = scanMaybe (FL.filtering p)-filter p m = fromStreamD $ D.filter p $ toStreamD m---- | Same as 'filter' but with a monadic predicate.------ >>> f p x = p x >>= \r -> return $ if r then Just x else Nothing--- >>> filterM p = Stream.mapMaybeM (f p)----{-# INLINE filterM #-}-filterM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a-filterM p m = fromStreamD $ D.filterM p $ toStreamD m---- | Drop repeated elements that are adjacent to each other using the supplied--- comparison function.------ >>> uniq = Stream.uniqBy (==)------ To strip duplicate path separators:------ >>> input = Stream.fromList "//a//b"--- >>> f x y = x == '/' && y == '/'--- >>> Stream.fold Fold.toList $ Stream.uniqBy f input--- "/a/b"------ Space: @O(1)@------ /Pre-release/----{-# INLINE uniqBy #-}-uniqBy :: Monad m =>- (a -> a -> Bool) -> Stream m a -> Stream m a--- uniqBy eq = scanMaybe (FL.uniqBy eq)-uniqBy eq = catMaybes . rollingMap f-- where-- f pre curr =- case pre of- Nothing -> Just curr- Just x -> if x `eq` curr then Nothing else Just curr---- | Drop repeated elements that are adjacent to each other.------ >>> uniq = Stream.uniqBy (==)----{-# INLINE uniq #-}-uniq :: (Eq a, Monad m) => Stream m a -> Stream m a--- uniq = scanMaybe FL.uniq-uniq = fromStreamD . D.uniq . toStreamD---- | Strip all leading and trailing occurrences of an element passing a--- predicate and make all other consecutive occurrences uniq.------ >> prune p = Stream.dropWhileAround p $ Stream.uniqBy (x y -> p x && p y)------ @--- > Stream.prune isSpace (Stream.fromList " hello world! ")--- "hello world!"------ @------ Space: @O(1)@------ /Unimplemented/-{-# INLINE prune #-}-prune ::- -- (Monad m, Eq a) =>- (a -> Bool) -> Stream m a -> Stream m a-prune = error "Not implemented yet!"---- Possible implementation:--- @repeated =--- Stream.catMaybes . Stream.parseMany (Parser.groupBy (==) Fold.repeated)@------ 'Fold.repeated' should return 'Just' when repeated, and 'Nothing' for a--- single element.---- | Emit only repeated elements, once.------ /Unimplemented/-repeated :: -- (Monad m, Eq a) =>- Stream m a -> Stream m a-repeated = undefined---- | Deletes the first occurrence of the element in the stream that satisfies--- the given equality predicate.------ >>> input = Stream.fromList [1,3,3,5]--- >>> Stream.fold Fold.toList $ Stream.deleteBy (==) 3 input--- [1,3,5]----{-# INLINE deleteBy #-}-deleteBy :: Monad m => (a -> a -> Bool) -> a -> Stream m a -> Stream m a--- deleteBy cmp x = scanMaybe (FL.deleteBy cmp x)-deleteBy cmp x m = fromStreamD $ D.deleteBy cmp x (toStreamD m)----------------------------------------------------------------------------------- Trimming----------------------------------------------------------------------------------- | Same as 'takeWhile' but with a monadic predicate.----{-# INLINE takeWhileM #-}-takeWhileM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a--- takeWhileM p = scanMaybe (FL.takingEndByM_ (\x -> not <$> p x))-takeWhileM p m = fromStreamD $ D.takeWhileM p $ toStreamD m---- | Take all consecutive elements at the end of the stream for which the--- predicate is true.------ O(n) space, where n is the number elements taken.------ /Unimplemented/-{-# INLINE takeWhileLast #-}-takeWhileLast :: -- Monad m =>- (a -> Bool) -> Stream m a -> Stream m a-takeWhileLast = undefined -- fromStreamD $ D.takeWhileLast n $ toStreamD m---- | Like 'takeWhile' and 'takeWhileLast' combined.------ O(n) space, where n is the number elements taken from the end.------ /Unimplemented/-{-# INLINE takeWhileAround #-}-takeWhileAround :: -- Monad m =>- (a -> Bool) -> Stream m a -> Stream m a-takeWhileAround = undefined -- fromStreamD $ D.takeWhileAround n $ toStreamD m---- | Drop elements in the stream as long as the predicate succeeds and then--- take the rest of the stream.----{-# INLINE dropWhile #-}-dropWhile :: Monad m => (a -> Bool) -> Stream m a -> Stream m a--- dropWhile p = scanMaybe (FL.droppingWhile p)-dropWhile p m = fromStreamD $ D.dropWhile p $ toStreamD m---- | Same as 'dropWhile' but with a monadic predicate.----{-# INLINE dropWhileM #-}-dropWhileM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a--- dropWhileM p = scanMaybe (FL.droppingWhileM p)-dropWhileM p m = fromStreamD $ D.dropWhileM p $ toStreamD m---- | Drop @n@ elements at the end of the stream.------ O(n) space, where n is the number elements dropped.------ /Unimplemented/-{-# INLINE dropLast #-}-dropLast :: -- Monad m =>- Int -> Stream m a -> Stream m a-dropLast = undefined -- fromStreamD $ D.dropLast n $ toStreamD m---- | Drop all consecutive elements at the end of the stream for which the--- predicate is true.------ O(n) space, where n is the number elements dropped.------ /Unimplemented/-{-# INLINE dropWhileLast #-}-dropWhileLast :: -- Monad m =>- (a -> Bool) -> Stream m a -> Stream m a-dropWhileLast = undefined -- fromStreamD $ D.dropWhileLast n $ toStreamD m---- | Like 'dropWhile' and 'dropWhileLast' combined.------ O(n) space, where n is the number elements dropped from the end.------ /Unimplemented/-{-# INLINE dropWhileAround #-}-dropWhileAround :: -- Monad m =>- (a -> Bool) -> Stream m a -> Stream m a-dropWhileAround = undefined -- fromStreamD $ D.dropWhileAround n $ toStreamD m----------------------------------------------------------------------------------- Inserting Elements----------------------------------------------------------------------------------- | @insertBy cmp elem stream@ inserts @elem@ before the first element in--- @stream@ that is less than @elem@ when compared using @cmp@.------ >>> insertBy cmp x = Stream.mergeBy cmp (Stream.fromPure x)------ >>> input = Stream.fromList [1,3,5]--- >>> Stream.fold Fold.toList $ Stream.insertBy compare 2 input--- [1,2,3,5]----{-# INLINE insertBy #-}-insertBy ::Monad m => (a -> a -> Ordering) -> a -> Stream m a -> Stream m a-insertBy cmp x m = fromStreamD $ D.insertBy cmp x (toStreamD m)---- | Insert a pure value between successive elements of a stream.------ >>> input = Stream.fromList "hello"--- >>> Stream.fold Fold.toList $ Stream.intersperse ',' input--- "h,e,l,l,o"----{-# INLINE intersperse #-}-intersperse :: Monad m => a -> Stream m a -> Stream m a-intersperse a = fromStreamD . D.intersperse a . toStreamD---- | Insert a side effect before consuming an element of a stream except the--- first one.------ >>> input = Stream.fromList "hello"--- >>> Stream.fold Fold.drain $ Stream.trace putChar $ Stream.intersperseM_ (putChar '.') input--- h.e.l.l.o------ /Pre-release/-{-# INLINE intersperseM_ #-}-intersperseM_ :: Monad m => m b -> Stream m a -> Stream m a-intersperseM_ m = fromStreamD . D.intersperseM_ m . toStreamD---- | Intersperse a monadic action into the input stream after every @n@--- elements.------ >> input = Stream.fromList "hello"--- >> Stream.fold Fold.toList $ Stream.intersperseMWith 2 (return ',') input--- "he,ll,o"------ /Unimplemented/-{-# INLINE intersperseMWith #-}-intersperseMWith :: -- Monad m =>- Int -> m a -> Stream m a -> Stream m a-intersperseMWith _n _f _xs = undefined---- | Insert an effect and its output after consuming an element of a stream.------ >>> input = Stream.fromList "hello"--- >>> Stream.fold Fold.toList $ Stream.trace putChar $ Stream.intersperseMSuffix (putChar '.' >> return ',') input--- h.,e.,l.,l.,o.,"h,e,l,l,o,"------ /Pre-release/-{-# INLINE intersperseMSuffix #-}-intersperseMSuffix :: Monad m => m a -> Stream m a -> Stream m a-intersperseMSuffix m = fromStreamD . D.intersperseMSuffix m . toStreamD---- | Insert a side effect after consuming an element of a stream.------ >>> input = Stream.fromList "hello"--- >>> Stream.fold Fold.toList $ Stream.intersperseMSuffix_ (threadDelay 1000000) input--- "hello"------ /Pre-release/----{-# INLINE intersperseMSuffix_ #-}-intersperseMSuffix_ :: Monad m => m b -> Stream m a -> Stream m a-intersperseMSuffix_ m = fromStreamD . D.intersperseMSuffix_ m . toStreamD---- XXX Use an offset argument, like tapOffsetEvery---- | Like 'intersperseMSuffix' but intersperses an effectful action into the--- input stream after every @n@ elements and after the last element.------ >>> input = Stream.fromList "hello"--- >>> Stream.fold Fold.toList $ Stream.intersperseMSuffixWith 2 (return ',') input--- "he,ll,o,"------ /Pre-release/----{-# INLINE intersperseMSuffixWith #-}-intersperseMSuffixWith :: Monad m- => Int -> m a -> Stream m a -> Stream m a-intersperseMSuffixWith n eff =- fromStreamD . D.intersperseMSuffixWith n eff . toStreamD---- | Insert a side effect before consuming an element of a stream.------ Definition:------ >>> intersperseMPrefix_ m = Stream.mapM (\x -> void m >> return x)------ >>> input = Stream.fromList "hello"--- >>> Stream.fold Fold.toList $ Stream.trace putChar $ Stream.intersperseMPrefix_ (putChar '.' >> return ',') input--- .h.e.l.l.o"hello"------ Same as 'trace_'.------ /Pre-release/----{-# INLINE intersperseMPrefix_ #-}-intersperseMPrefix_ :: Monad m => m b -> Stream m a -> Stream m a-intersperseMPrefix_ m = mapM (\x -> void m >> return x)----------------------------------------------------------------------------------- Inserting Time----------------------------------------------------------------------------------- XXX This should be in Prelude, should we export this as a helper function?---- | Block the current thread for specified number of seconds.-{-# INLINE sleep #-}-sleep :: MonadIO m => Double -> m ()-sleep n = liftIO $ threadDelay $ round $ n * 1000000---- | Introduce a delay of specified seconds between elements of the stream.------ Definition:------ >>> sleep n = liftIO $ threadDelay $ round $ n * 1000000--- >>> delay = Stream.intersperseM_ . sleep------ Example:------ >>> input = Stream.enumerateFromTo 1 3--- >>> Stream.fold (Fold.drainMapM print) $ Stream.delay 1 input--- 1--- 2--- 3----{-# INLINE delay #-}-delay :: MonadIO m => Double -> Stream m a -> Stream m a-delay = intersperseM_ . sleep---- | Introduce a delay of specified seconds after consuming an element of a--- stream.------ Definition:------ >>> sleep n = liftIO $ threadDelay $ round $ n * 1000000--- >>> delayPost = Stream.intersperseMSuffix_ . sleep------ Example:------ >>> input = Stream.enumerateFromTo 1 3--- >>> Stream.fold (Fold.drainMapM print) $ Stream.delayPost 1 input--- 1--- 2--- 3------ /Pre-release/----{-# INLINE delayPost #-}-delayPost :: MonadIO m => Double -> Stream m a -> Stream m a-delayPost n = intersperseMSuffix_ $ liftIO $ threadDelay $ round $ n * 1000000---- | Introduce a delay of specified seconds before consuming an element of a--- stream.------ Definition:------ >>> sleep n = liftIO $ threadDelay $ round $ n * 1000000--- >>> delayPre = Stream.intersperseMPrefix_. sleep------ Example:------ >>> input = Stream.enumerateFromTo 1 3--- >>> Stream.fold (Fold.drainMapM print) $ Stream.delayPre 1 input--- 1--- 2--- 3------ /Pre-release/----{-# INLINE delayPre #-}-delayPre :: MonadIO m => Double -> Stream m a -> Stream m a-delayPre = intersperseMPrefix_. sleep----------------------------------------------------------------------------------- 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.------ /Unimplemented/----{-# INLINE reassembleBy #-}-reassembleBy- :: -- Monad m =>- Fold m a b- -> (a -> a -> Int)- -> Stream m a- -> Stream m b-reassembleBy = undefined----------------------------------------------------------------------------------- Position Indexing----------------------------------------------------------------------------------- |--- >>> f = Fold.foldl' (\(i, _) x -> (i + 1, x)) (-1,undefined)--- >>> indexed = Stream.postscan f--- >>> indexed = Stream.zipWith (,) (Stream.enumerateFrom 0)--- >>> indexedR n = fmap (\(i, a) -> (n - i, a)) . indexed------ Pair each element in a stream with its index, starting from index 0.------ >>> Stream.fold Fold.toList $ Stream.indexed $ Stream.fromList "hello"--- [(0,'h'),(1,'e'),(2,'l'),(3,'l'),(4,'o')]----{-# INLINE indexed #-}-indexed :: Monad m => Stream m a -> Stream m (Int, a)--- indexed = scanMaybe FL.indexing-indexed = fromStreamD . D.indexed . toStreamD---- |--- >>> f n = Fold.foldl' (\(i, _) x -> (i - 1, x)) (n + 1,undefined)--- >>> indexedR n = Stream.postscan (f n)------ >>> s n = Stream.enumerateFromThen n (n - 1)--- >>> indexedR n = Stream.zipWith (,) (s n)------ Pair each element in a stream with its index, starting from the--- given index @n@ and counting down.------ >>> Stream.fold Fold.toList $ Stream.indexedR 10 $ Stream.fromList "hello"--- [(10,'h'),(9,'e'),(8,'l'),(7,'l'),(6,'o')]----{-# INLINE indexedR #-}-indexedR :: Monad m => Int -> Stream m a -> Stream m (Int, a)--- indexedR n = scanMaybe (FL.indexingRev n)-indexedR n = fromStreamD . D.indexedR n . toStreamD------------------------------------------------------------------------------------ Time Indexing------------------------------------------------------------------------------------ Note: The timestamp stream must be the second stream in the zip so that the--- timestamp is generated after generating the stream element and not before.--- If we do not do that then the following example will generate the same--- timestamp for first two elements:------ Stream.fold Fold.toList $ Stream.timestamped $ Stream.delay $ Stream.enumerateFromTo 1 3------ | Pair each element in a stream with an absolute timestamp, using a clock of--- specified granularity. The timestamp is generated just before the element--- is consumed.------ >>> Stream.fold Fold.toList $ Stream.timestampWith 0.01 $ Stream.delay 1 $ Stream.enumerateFromTo 1 3--- [(AbsTime (TimeSpec {sec = ..., nsec = ...}),1),(AbsTime (TimeSpec {sec = ..., nsec = ...}),2),(AbsTime (TimeSpec {sec = ..., nsec = ...}),3)]------ /Pre-release/----{-# INLINE timestampWith #-}-timestampWith :: (MonadIO m)- => Double -> Stream m a -> Stream m (AbsTime, a)-timestampWith g stream = zipWith (flip (,)) stream (absTimesWith g)---- TBD: check performance vs a custom implementation without using zipWith.------ /Pre-release/----{-# INLINE timestamped #-}-timestamped :: (MonadIO m)- => Stream m a -> Stream m (AbsTime, a)-timestamped = timestampWith 0.01---- | Pair each element in a stream with relative times starting from 0, using a--- clock with the specified granularity. The time is measured just before the--- element is consumed.------ >>> Stream.fold Fold.toList $ Stream.timeIndexWith 0.01 $ Stream.delay 1 $ Stream.enumerateFromTo 1 3--- [(RelTime64 (NanoSecond64 ...),1),(RelTime64 (NanoSecond64 ...),2),(RelTime64 (NanoSecond64 ...),3)]------ /Pre-release/Monad----{-# INLINE timeIndexWith #-}-timeIndexWith :: (MonadIO m)- => Double -> Stream m a -> Stream m (RelTime64, a)-timeIndexWith g stream = zipWith (flip (,)) stream (relTimesWith g)---- | Pair each element in a stream with relative times starting from 0, using a--- 10 ms granularity clock. The time is measured just before the element is--- consumed.------ >>> Stream.fold Fold.toList $ Stream.timeIndexed $ Stream.delay 1 $ Stream.enumerateFromTo 1 3--- [(RelTime64 (NanoSecond64 ...),1),(RelTime64 (NanoSecond64 ...),2),(RelTime64 (NanoSecond64 ...),3)]------ /Pre-release/----{-# INLINE timeIndexed #-}-timeIndexed :: (MonadIO m)- => Stream m a -> Stream m (RelTime64, a)-timeIndexed = timeIndexWith 0.01----------------------------------------------------------------------------------- Searching----------------------------------------------------------------------------------- | Find all the indices where the value of the element in the stream is equal--- to the given value.------ >>> elemIndices a = Stream.findIndices (== a)----{-# INLINE elemIndices #-}-elemIndices :: (Monad m, Eq a) => a -> Stream m a -> Stream m Int-elemIndices a = findIndices (== a)----------------------------------------------------------------------------------- Rolling map----------------------------------------------------------------------------------- XXX this is not a one-to-one map so calling it map may not be right.--- We can perhaps call it zipWithTail or rollWith.---- | Apply a function on every two successive elements of a stream. The first--- argument of the map function is the previous element and the second argument--- is the current element. When the current element is the first element, the--- previous element is 'Nothing'.------ /Pre-release/----{-# INLINE rollingMap #-}-rollingMap :: Monad m => (Maybe a -> a -> b) -> Stream m a -> Stream m b--- rollingMap f = scanMaybe (FL.slide2 $ Window.rollingMap f)-rollingMap f m = fromStreamD $ D.rollingMap f $ toStreamD m---- | Like 'rollingMap' but with an effectful map function.------ /Pre-release/----{-# INLINE rollingMapM #-}-rollingMapM :: Monad m => (Maybe a -> a -> m b) -> Stream m a -> Stream m b--- rollingMapM f = scanMaybe (FL.slide2 $ Window.rollingMapM f)-rollingMapM f m = fromStreamD $ D.rollingMapM f $ toStreamD m---- | Like 'rollingMap' but requires at least two elements in the stream,--- returns an empty stream otherwise.------ This is the stream equivalent of the list idiom @zipWith f xs (tail xs)@.------ /Pre-release/----{-# INLINE rollingMap2 #-}-rollingMap2 :: Monad m => (a -> a -> b) -> Stream m a -> Stream m b-rollingMap2 f m = fromStreamD $ D.rollingMap2 f $ toStreamD m----------------------------------------------------------------------------------- Maybe Streams----------------------------------------------------------------------------------- | 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 = Stream.catMaybes . fmap f----{-# INLINE mapMaybe #-}-mapMaybe :: Monad m => (a -> Maybe b) -> Stream m a -> Stream m b-mapMaybe f m = fromStreamD $ D.mapMaybe f $ toStreamD m---- | Like 'mapMaybe' but maps a monadic function.------ Equivalent to:------ >>> mapMaybeM f = Stream.catMaybes . Stream.mapM f------ >>> mapM f = Stream.mapMaybeM (\x -> Just <$> f x)----{-# INLINE_EARLY mapMaybeM #-}-mapMaybeM :: Monad m- => (a -> m (Maybe b)) -> Stream m a -> Stream m b-mapMaybeM f = fmap fromJust . filter isJust . mapM f----------------------------------------------------------------------------------- Either streams----------------------------------------------------------------------------------- | Discard 'Right's and unwrap 'Left's in an 'Either' stream.------ >>> catLefts = fmap (fromLeft undefined) . Stream.filter isLeft------ /Pre-release/----{-# INLINE catLefts #-}-catLefts :: Monad m => Stream m (Either a b) -> Stream m a-catLefts = fmap (fromLeft undefined) . filter isLeft---- | Discard 'Left's and unwrap 'Right's in an 'Either' stream.------ >>> catRights = fmap (fromRight undefined) . Stream.filter isRight------ /Pre-release/----{-# INLINE catRights #-}-catRights :: Monad m => Stream m (Either a b) -> Stream m b-catRights = fmap (fromRight undefined) . filter isRight---- | Remove the either wrapper and flatten both lefts and as well as rights in--- the output stream.------ >>> catEithers = fmap (either id id)------ /Pre-release/----{-# INLINE catEithers #-}-catEithers :: Monad m => Stream m (Either a a) -> Stream m a-catEithers = fmap (either id id)+{-# LANGUAGE CPP #-}+-- |+-- Module : Streamly.Internal.Data.Stream.Transform+-- Copyright : (c) 2018 Composewell Technologies+-- (c) Roman Leshchinskiy 2008-2010+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC++-- A few functions in this module have been adapted from the vector package+-- (c) Roman Leshchinskiy. See the notes in specific combinators.++module Streamly.Internal.Data.Stream.Transform+ (+ -- * Mapping+ -- | Stateless one-to-one maps.+ sequence++ -- * Mapping Effects+ , tap+ , tapOffsetEvery+ , trace+ , trace_++ -- * Folding+ , foldrS+ , foldlS++ -- * Composable Scans+ , postscanl+ , scanl+ , scanlMany+ , scanr+ , pipe++ -- * Splitting+ , splitSepBy_++ -- * Ad-hoc Scans+ -- | Left scans. Stateful, mostly one-to-one maps.+ , scanlM'+ , scanlMAfter'+ , scanl'+ , scanlM+ , scanlBy+ , scanl1M'+ , scanl1'+ , scanl1M+ , scanl1++ , prescanl'+ , prescanlM'++ , postscanlBy+ , postscanlM+ , postscanl'+ , postscanlM'+ , postscanlMAfter'++ , postscanlx'+ , postscanlMx'+ , scanlMx'+ , scanlx'++ -- * Filtering+ -- delete is for once like insert, filter is for many like intersperse.++ -- | Produce a subset of the stream.+ , with+ , postscanlMaybe+ , filter -- retainBy+ , filterM+ , deleteBy -- deleteOnceBy/deleteFirstBy?+ , uniqBy+ , uniq+ , prune+ , repeated++ -- * Sampling+ -- | Value agnostic filtering.+ , sampleFromThen+ -- keepEvery/filterEvery -- sampling+ -- deleteEvery/dropEvery/removeEvery -- dual of intersperseEvery+ -- deintersperse - drop infixed elements++ -- * Trimming+ -- | Produce a subset of the stream trimmed at ends.+ , initNonEmpty+ , tailNonEmpty+ , drop+ , dropWhile+ , dropWhileM++ -- * Trimming from end+ -- | RingArray array based or buffering operations.+ --+ , takeWhileLast+ , takeWhileAround+ , dropLast+ , dropWhileLast+ , dropWhileAround++ -- * Inserting Elements+ -- insert is for once like delete, intersperse is for many like filter+ -- | Produce a superset of the stream. Value agnostic insertion.+ , intersperse+ , intersperseM+ , intersperseEveryM+ , intersperseEndByM+ , intersperseEndByEveryM++ -- Value aware insertion.+ , insertBy -- insertCmpBy+ -- insertBeforeBy+ -- insertAfterBy+ -- intersperseBeforeBy+ -- intersperseAfterBy++ -- * Inserting Side Effects+ , intersperseM_+ , intersperseEndByM_+ , intersperseBeginByM_++ , delay+ , delayPre+ , delayPost++ -- * Reordering+ -- | Produce strictly the same set but reordered.+ , reverse+ , reverseUnbox+ , reassembleBy++ -- * Position Indexing+ , indexed+ , indexedR++ -- * Time Indexing+ , timestampWith+ , timestamped+ , timeIndexWith+ , timeIndexed++ -- * Searching+ , findIndices+ , elemIndices++ -- * Rolling map+ -- | Map using the previous element.+ , rollingMap+ , rollingMapM+ , rollingMap2++ -- * Maybe Streams+ , mapMaybe+ , mapMaybeM+ , catMaybes++ -- * Either Streams+ , catLefts+ , catRights+ , catEithers++ -- * Deprecated+ , postscan+ , scan+ , scanMany+ , scanMaybe+ , intersperseMSuffix+ , intersperseMSuffixWith+ , intersperseMSuffix_+ , intersperseMPrefix_+ , strideFromThen+ , splitOn+ )+where++#include "deprecation.h"+#include "inline.hs"++import Control.Concurrent (threadDelay)+import Control.Monad (void)+import Control.Monad.IO.Class (MonadIO (liftIO))+import Data.Either (fromLeft, isLeft, isRight, fromRight)+import Data.Functor ((<&>))+import Data.Maybe (fromJust, isJust)+import Fusion.Plugin.Types (Fuse(..))++import Streamly.Internal.Data.Fold.Type (Fold(..))+import Streamly.Internal.Data.Pipe.Type (Pipe(..))+import Streamly.Internal.Data.Scanl.Type (Scanl(..))+import Streamly.Internal.Data.Scanr (Scanr(..))+import Streamly.Internal.Data.SVar.Type (adaptState)+import Streamly.Internal.Data.Time.Units (AbsTime, RelTime64)+import Streamly.Internal.Data.Unbox (Unbox)+import Streamly.Internal.System.IO (defaultChunkSize)++-- import qualified Data.List as List+import qualified Streamly.Internal.Data.Array.Type as A+import qualified Streamly.Internal.Data.Fold.Type as FL+import qualified Streamly.Internal.Data.Pipe.Type as Pipe+import qualified Streamly.Internal.Data.StreamK.Type as K++import Prelude hiding+ ( drop, dropWhile, filter, map, mapM, reverse+ , scanl, scanl1, scanr, sequence, take, takeWhile, zipWith)++import Streamly.Internal.Data.Stream.Generate+ (absTimesWith, relTimesWith)+import Streamly.Internal.Data.Stream.Type++#include "DocTestDataStream.hs"++------------------------------------------------------------------------------+-- Piping+------------------------------------------------------------------------------++{-# ANN type PipeState Fuse #-}+data PipeState st sc ps = PipeConsume st sc | PipeProduce st ps++-- | Use a 'Pipe' to transform a stream.+--+{-# INLINE_NORMAL pipe #-}+pipe :: Monad m => Pipe m a b -> Stream m a -> Stream m b+pipe (Pipe consume produce initial) (Stream stream_step state) =+ Stream step (PipeConsume state initial)++ where++ {-# INLINE goConsume #-}+ goConsume st cs x = do+ res <- consume cs x+ return+ $ case res of+ Pipe.YieldC s b -> Yield b (PipeConsume st s)+ Pipe.SkipC s -> Skip (PipeConsume st s)+ Pipe.Stop -> Stop+ Pipe.YieldP ps b -> Yield b (PipeProduce st ps)+ Pipe.SkipP ps -> Skip (PipeProduce st ps)++ {-# INLINE_LATE step #-}+ step gst (PipeConsume st cs) = do+ r <- stream_step (adaptState gst) st+ case r of+ Yield x s -> goConsume s cs x+ Skip s -> return $ Skip (PipeConsume s cs)+ Stop -> return Stop+ step _ (PipeProduce st ps) = do+ r <- produce ps+ return+ $ case r of+ Pipe.YieldC cs b -> Yield b (PipeConsume st cs)+ Pipe.SkipC cs -> Skip (PipeConsume st cs)+ Pipe.Stop -> Stop+ Pipe.YieldP ps1 b -> Yield b (PipeProduce st ps1)+ Pipe.SkipP ps1 -> Skip (PipeProduce st ps1)++{-# ANN type RunScanState Fuse #-}+data RunScanState st sc ps = ScanConsume st sc++-- | Use a lazy right 'Scanr' to transform a stream.+--+-- The following example extracts the input stream up to a point where the+-- running average of elements is no more than 10:+--+-- >>> import Data.Maybe (fromJust)+-- >>> let avg = Scanr.teeWith (/) Scanr.sum (fmap fromIntegral Scanr.length)+-- >>> s = Stream.enumerateFromTo 1.0 100.0+-- >>> :{+-- Stream.fold Fold.toList+-- $ fmap fst+-- $ Stream.takeWhile (\(_,x) -> x <= 10)+-- $ Stream.scanr (Scanr.tee Scanr.identity avg) s+-- :}+-- [1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0,17.0,18.0,19.0]+--+{-# INLINE_NORMAL scanr #-}+scanr :: Monad m => Scanr m a b -> Stream m a -> Stream m b+scanr (Scanr consume initial) (Stream stream_step state) =+ Stream step (ScanConsume state initial)++ where++ {-# INLINE_LATE step #-}+ step gst (ScanConsume st cs) = do+ r <- stream_step (adaptState gst) st+ case r of+ Yield x s -> do+ res <- consume cs x+ return+ $ case res of+ Yield b cs1 -> Yield b (ScanConsume s cs1)+ Skip cs1 -> Skip (ScanConsume s cs1)+ Stop -> Stop+ Skip s -> return $ Skip (ScanConsume s cs)+ Stop -> return Stop++------------------------------------------------------------------------------+-- Transformation Folds+------------------------------------------------------------------------------++-- 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++------------------------------------------------------------------------------+-- Transformation by Mapping+------------------------------------------------------------------------------++-- |+-- >>> sequence = Stream.mapM id+--+-- Replace the elements of a stream of monadic actions with the outputs of+-- those actions.+--+-- >>> s = Stream.fromList [putStr "a", putStr "b", putStrLn "c"]+-- >>> Stream.fold Fold.drain $ Stream.sequence s+-- abc+--+{-# 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++------------------------------------------------------------------------------+-- Mapping side effects+------------------------------------------------------------------------------++data TapState fs st a+ = TapInit | Tapping !fs st | TapDone st++-- XXX Multiple yield points++-- | 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 = Stream.enumerateFromTo 1 2+-- >>> Stream.fold Fold.drain $ Stream.tap (Fold.drainMapM print) s+-- 1+-- 2+--+-- Compare with 'trace'.+--+{-# INLINE tap #-}+tap :: Monad m => Fold m a b -> Stream m a -> Stream m a+tap (Fold fstep initial _ final) (Stream step state) = Stream step' TapInit++ where++ step' _ TapInit = do+ res <- initial+ return+ $ Skip+ $ case res of+ FL.Partial s -> Tapping s state+ FL.Done _ -> TapDone state+ step' gst (Tapping acc st) = do+ r <- step gst st+ case r of+ Yield x s -> do+ res <- fstep acc x+ return+ $ Yield x+ $ case res of+ FL.Partial fs -> Tapping fs s+ FL.Done _ -> TapDone s+ Skip s -> return $ Skip (Tapping acc s)+ Stop -> do+ void $ final acc+ return Stop+ step' gst (TapDone st) = do+ r <- step gst st+ return+ $ case r of+ Yield x s -> Yield x (TapDone s)+ Skip s -> Skip (TapDone s)+ Stop -> Stop++data TapOffState fs s a+ = TapOffInit+ | TapOffTapping !fs s Int+ | TapOffDone s++-- XXX Multiple yield points+{-# INLINE_NORMAL tapOffsetEvery #-}+tapOffsetEvery :: Monad m+ => Int -> Int -> Fold m a b -> Stream m a -> Stream m a+tapOffsetEvery offset n (Fold fstep initial _ final) (Stream step state) =+ Stream step' TapOffInit++ where++ {-# INLINE_LATE step' #-}+ step' _ TapOffInit = do+ res <- initial+ return+ $ Skip+ $ case res of+ FL.Partial s -> TapOffTapping s state (offset `mod` n)+ FL.Done _ -> TapOffDone state+ step' gst (TapOffTapping acc st count) = do+ r <- step gst st+ case r of+ Yield x s -> do+ next <-+ if count <= 0+ then do+ res <- fstep acc x+ return+ $ case res of+ FL.Partial sres ->+ TapOffTapping sres s (n - 1)+ FL.Done _ -> TapOffDone s+ else return $ TapOffTapping acc s (count - 1)+ return $ Yield x next+ Skip s -> return $ Skip (TapOffTapping acc s count)+ Stop -> do+ void $ final acc+ return Stop+ step' gst (TapOffDone st) = do+ r <- step gst st+ return+ $ case r of+ Yield x s -> Yield x (TapOffDone s)+ Skip s -> Skip (TapOffDone s)+ Stop -> Stop++-- | Apply a monadic function to each element flowing through the stream and+-- discard the results.+--+-- >>> s = Stream.enumerateFromTo 1 2+-- >>> Stream.fold Fold.drain $ Stream.trace print s+-- 1+-- 2+--+-- Compare with 'tap'.+--+{-# INLINE trace #-}+trace :: Monad m => (a -> m b) -> Stream m a -> Stream m a+trace f = mapM (\x -> void (f x) >> return x)++-- | Perform a side effect before yielding each element of the stream and+-- discard the results.+--+-- >>> s = Stream.enumerateFromTo 1 2+-- >>> Stream.fold Fold.drain $ Stream.trace_ (print "got here") s+-- "got here"+-- "got here"+--+-- Same as 'intersperseMPrefix_' but always serial.+--+-- See also: 'trace'+--+-- /Pre-release/+{-# INLINE trace_ #-}+trace_ :: Monad m => m b -> Stream m a -> Stream m a+trace_ eff = mapM (\x -> eff >> return x)++------------------------------------------------------------------------------+-- Scanning with a Fold+------------------------------------------------------------------------------++data ScanState s f = ScanInit s | ScanDo s !f | ScanDone++-- NOTE: Lazy postscans can be useful e.g. to use a lazy postscan on "latest".+-- We can keep the initial state undefined in lazy postscans which do not use+-- it at all. Otherwise we have to wrap the accumulator in a Maybe type.+-- Unfortunately, we cannot define lazy scans because the Partial constructor+-- itself is strict.++-- | Postscan a stream using the given fold. A postscan omits the initial+-- (default) value of the accumulator and includes the final value.+--+-- >>> Stream.toList $ Stream.postscanl Scanl.latest (Stream.fromList [])+-- []+--+-- Compare with 'scan' which includes the initial value as well:+--+-- >>> Stream.toList $ Stream.scanl Scanl.latest (Stream.fromList [])+-- [Nothing]+--+-- The following example extracts the input stream up to a point where the+-- running average of elements is no more than 10:+--+-- >>> import Data.Maybe (fromJust)+-- >>> let avg = Scanl.teeWith (/) Scanl.sum (fmap fromIntegral Scanl.length)+-- >>> s = Stream.enumerateFromTo 1.0 100.0+-- >>> :{+-- Stream.fold Fold.toList+-- $ fmap (fromJust . fst)+-- $ Stream.takeWhile (\(_,x) -> x <= 10)+-- $ Stream.postscanl (Scanl.tee Scanl.latest avg) s+-- :}+-- [1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0,17.0,18.0,19.0]+--+{-# INLINE_NORMAL postscanl #-}+postscanl :: Monad m => Scanl m a b -> Stream m a -> Stream m b+postscanl (Scanl fstep initial extract final) (Stream sstep state) =+ Stream step (ScanInit state)++ where++ {-# INLINE_LATE step #-}+ step _ (ScanInit st) = do+ res <- initial+ return+ $ case res of+ FL.Partial fs -> Skip $ ScanDo st fs+ FL.Done b -> Yield b ScanDone+ step gst (ScanDo st fs) = do+ res <- sstep (adaptState gst) st+ case res of+ Yield x s -> do+ r <- fstep fs x+ case r of+ FL.Partial fs1 -> do+ !b <- extract fs1+ return $ Yield b $ ScanDo s fs1+ FL.Done b -> return $ Yield b ScanDone+ Skip s -> return $ Skip $ ScanDo s fs+ Stop -> final fs >> return Stop+ step _ ScanDone = return Stop++{-# DEPRECATED postscan "Please use postscanl instead" #-}+{-# INLINE_NORMAL postscan #-}+postscan :: Monad m => FL.Fold m a b -> Stream m a -> Stream m b+postscan (FL.Fold fstep initial extract final) =+ postscanl (Scanl fstep initial extract final)++{-# INLINE scanlWith #-}+scanlWith :: Monad m+ => Bool -> Scanl m a b -> Stream m a -> Stream m b+scanlWith restart (Scanl fstep initial extract final) (Stream sstep state) =+ Stream step (ScanInit state)++ where++ {-# INLINE runStep #-}+ runStep st action = do+ res <- action+ case res of+ FL.Partial fs -> do+ !b <- extract fs+ return $ Yield b $ ScanDo st fs+ FL.Done b ->+ let next = if restart then ScanInit st else ScanDone+ in return $ Yield b next++ {-# INLINE_LATE step #-}+ step _ (ScanInit st) = runStep st initial+ step gst (ScanDo st fs) = do+ res <- sstep (adaptState gst) st+ case res of+ Yield x s -> runStep s (fstep fs x)+ Skip s -> return $ Skip $ ScanDo s fs+ Stop -> final fs >> return Stop+ step _ ScanDone = return Stop++{-# DEPRECATED scanWith "Please use scanlWith instead" #-}+{-# INLINE scanWith #-}+scanWith :: Monad m+ => Bool -> Fold m a b -> Stream m a -> Stream m b+scanWith restart (Fold fstep initial extract final) =+ scanlWith restart (Scanl fstep initial extract final)++-- 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.++-- | Strict left scan. Scan a stream using the given fold. Scan includes+-- the initial (default) value of the accumulator as well as the final value.+-- Compare with 'postscan' which omits the initial value.+--+-- >>> s = Stream.fromList [1..10]+-- >>> Stream.fold Fold.toList $ Stream.takeWhile (< 10) $ Stream.scanl Scanl.sum s+-- [0,1,3,6]+--+-- See also: 'usingStateT'+--++-- EXPLANATION:+-- >>> scanl' step z = Stream.scanl (Scanl.mkScanl step z)+--+-- Like 'map', 'scanl'' too is a one to one transformation,+-- however it adds an extra element.+--+-- >>> s = Stream.fromList [1,2,3,4]+-- >>> Stream.fold Fold.toList $ scanl' (+) 0 s+-- [0,1,3,6,10]+--+-- >>> Stream.fold Fold.toList $ scanl' (flip (:)) [] s+-- [[],[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'@:+--+-- >>> foldl' step z = Stream.fold (Scanl.mkScanl step z)+-- >>> foldl' (\(s, p) x -> (s + x, p * x)) (0,1) s+-- (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:+--+-- >>> :{+-- foldl' (\(_, p) (s, x) -> (s, p * x)) (0,1)+-- $ scanl' (\(s, _) x -> (s + x, x)) (0,1)+-- $ Stream.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.+--+{-# INLINE_NORMAL scanl #-}+scanl :: Monad m+ => Scanl m a b -> Stream m a -> Stream m b+scanl = scanlWith False++-- | Like 'scanl' but restarts scanning afresh when the scanning fold+-- terminates.+--+{-# INLINE_NORMAL scanlMany #-}+scanlMany :: Monad m+ => Scanl m a b -> Stream m a -> Stream m b+scanlMany = scanlWith True++{-# DEPRECATED scan "Please use scanl instead" #-}+{-# INLINE_NORMAL scan #-}+scan :: Monad m+ => FL.Fold m a b -> Stream m a -> Stream m b+scan = scanWith False++{-# DEPRECATED scanMany "Please use scanlMany instead" #-}+{-# INLINE_NORMAL scanMany #-}+scanMany :: Monad m+ => FL.Fold m a b -> Stream m a -> Stream m b+scanMany = scanWith True++------------------------------------------------------------------------------+-- Scanning - Prescans+------------------------------------------------------------------------------++-- Adapted from the vector package.+--+-- 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 =+ postscanlMx' (\b a -> return (fstep b a)) (return begin) (return . done)++-- 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 =+ scanlMx' (\b a -> return (fstep b a)) (return begin) (return . done)++------------------------------------------------------------------------------+-- postscans+------------------------------------------------------------------------------++-- Adapted from the vector package.+{-# INLINE_NORMAL postscanlM' #-}+postscanlM' :: Monad m => (b -> a -> m b) -> m b -> Stream m a -> Stream m b+postscanlM' fstep begin (Stream step state) =+ Stream step' Nothing+ where+ {-# INLINE_LATE step' #-}+ step' _ Nothing = do+ !x <- begin+ return $ Skip (Just (state, x))++ step' gst (Just (st, acc)) = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> do+ !y <- fstep acc x+ return $ Yield y (Just (s, y))+ Skip s -> return $ Skip (Just (s, acc))+ Stop -> return Stop++{-# INLINE_NORMAL postscanl' #-}+postscanl' :: Monad m => (a -> b -> a) -> a -> Stream m b -> Stream m a+postscanl' f seed = postscanlM' (\a b -> return (f a b)) (return seed)++{-# ANN type PScanAfterState Fuse #-}+data PScanAfterState m st acc =+ PScanAfterStep st (m acc)+ | PScanAfterYield acc (PScanAfterState m st acc)+ | PScanAfterStop++-- We can possibly have the "done" function as a Maybe to provide an option to+-- emit or not emit the accumulator when the stream stops.+--+-- TBD: use a single Yield point+--+{-# INLINE_NORMAL postscanlMAfter' #-}+postscanlMAfter' :: Monad m+ => (b -> a -> m b) -> m b -> (b -> m b) -> Stream m a -> Stream m b+postscanlMAfter' fstep initial done (Stream step1 state1) = do+ Stream step (PScanAfterStep state1 initial)++ where++ {-# INLINE_LATE step #-}+ step gst (PScanAfterStep st acc) = do+ r <- step1 (adaptState gst) st+ case r of+ Yield x s -> do+ !old <- acc+ !y <- fstep old x+ return (Skip $ PScanAfterYield y (PScanAfterStep s (return y)))+ Skip s -> return $ Skip $ PScanAfterStep s acc+ -- Strictness is important for fusion+ Stop -> do+ !v <- acc+ !res <- done v+ return (Skip $ PScanAfterYield res PScanAfterStop)+ step _ (PScanAfterYield acc next) = return $ Yield acc next+ step _ PScanAfterStop = return Stop++{-# INLINE_NORMAL postscanlM #-}+postscanlM :: Monad m => (b -> a -> m b) -> m b -> Stream m a -> Stream m b+postscanlM fstep begin (Stream step state) = Stream step' Nothing+ where+ {-# INLINE_LATE step' #-}+ step' _ Nothing = do+ r <- begin+ return $ Skip (Just (state, r))++ step' gst (Just (st, acc)) = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> do+ y <- fstep acc x+ return (Yield y (Just (s, y)))+ Skip s -> return $ Skip (Just (s, acc))+ Stop -> return Stop++{-# INLINE_NORMAL postscanlBy #-}+postscanlBy :: Monad m => (a -> b -> a) -> a -> Stream m b -> Stream m a+postscanlBy f seed = postscanlM (\a b -> return (f a b)) (return seed)++-- | Like 'scanl'' but with a monadic step function and a monadic seed.+--+{-# INLINE_NORMAL scanlM' #-}+scanlM' :: Monad m => (b -> a -> m b) -> m b -> Stream m a -> Stream m b+scanlM' fstep begin (Stream step state) = Stream step' Nothing+ where+ {-# INLINE_LATE step' #-}+ step' _ Nothing = do+ !x <- begin+ return $ Yield x (Just (state, x))+ step' gst (Just (st, acc)) = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> do+ !y <- fstep acc x+ return $ Yield y (Just (s, y))+ Skip s -> return $ Skip (Just (s, acc))+ Stop -> return Stop++-- | @scanlMAfter' accumulate initial done stream@ is like 'scanlM'' except+-- that it provides an additional @done@ function to be applied on the+-- accumulator when the stream stops. The result of @done@ is also emitted in+-- the stream.+--+-- This function can be used to allocate a resource in the beginning of the+-- scan and release it when the stream ends or to flush the internal state of+-- the scan at the end.+--+-- /Pre-release/+--+{-# INLINE scanlMAfter' #-}+scanlMAfter' :: Monad m+ => (b -> a -> m b) -> m b -> (b -> m b) -> Stream m a -> Stream m b+scanlMAfter' fstep initial done s =+ initial `consM` postscanlMAfter' fstep initial done s++-- >>> scanl' f z xs = z `Stream.cons` postscanl' f z xs++-- | Strict left scan. Like 'map', 'scanl'' too is a one to one transformation,+-- however it adds an extra element.+--+-- >>> Stream.toList $ Stream.scanl' (+) 0 $ Stream.fromList [1,2,3,4]+-- [0,1,3,6,10]+--+-- >>> Stream.toList $ Stream.scanl' (flip (:)) [] $ Stream.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'@:+--+-- >>> Stream.fold (Fold.foldl' (\(s, p) x -> (s + x, p * x)) (0,1)) $ Stream.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:+--+-- >>> :{+-- Stream.fold (Fold.foldl' (\(_, p) (s, x) -> (s, p * x)) (0,1))+-- $ Stream.scanl' (\(s, _) x -> (s + x, x)) (0,1)+-- $ Stream.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.+--+-- >>> scanl' step z = Stream.scanl (Scanl.mkScanl step z)+-- >>> scanl' f z xs = Stream.scanlM' (\a b -> return (f a b)) (return z) xs+--+-- See also: 'usingStateT'+--+{-# INLINE scanl' #-}+scanl' :: Monad m => (b -> a -> b) -> b -> Stream m a -> Stream m b+scanl' f seed = scanlM' (\a b -> return (f a b)) (return seed)++{-# INLINE_NORMAL scanlM #-}+scanlM :: Monad m => (b -> a -> m b) -> m b -> Stream m a -> Stream m b+scanlM fstep begin (Stream step state) = Stream step' Nothing+ where+ {-# INLINE_LATE step' #-}+ step' _ Nothing = do+ x <- begin+ return $ Yield x (Just (state, x))+ step' gst (Just (st, acc)) = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> do+ y <- fstep acc x+ return $ Yield y (Just (s, y))+ Skip s -> return $ Skip (Just (s, acc))+ Stop -> return Stop++{-# INLINE scanlBy #-}+scanlBy :: Monad m => (b -> a -> b) -> b -> Stream m a -> Stream m b+scanlBy f seed = scanlM (\a b -> return (f a b)) (return seed)++-- Adapted from the vector package+{-# 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))++-- Adapted from the vector package++-- | Like 'scanl1'' but with a monadic step function.+--+{-# 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++-- | 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.+--+-- >>> Stream.toList $ Stream.scanl1' (+) $ Stream.fromList [1,2,3,4]+-- [1,3,6,10]+--+{-# INLINE scanl1' #-}+scanl1' :: Monad m => (a -> a -> a) -> Stream m a -> Stream m a+scanl1' f = scanl1M' (\x y -> return (f x y))++-------------------------------------------------------------------------------+-- Filtering+-------------------------------------------------------------------------------++-- | Modify a @Stream m a -> Stream m a@ stream transformation that accepts a+-- predicate @(a -> b)@ to accept @((s, a) -> b)@ instead, provided a+-- transformation @Stream m a -> Stream m (s, a)@. Convenient to filter with+-- index or time.+--+-- >>> filterWithIndex = Stream.with Stream.indexed Stream.filter+--+-- /Pre-release/+{-# INLINE with #-}+with :: Monad m =>+ (Stream m a -> Stream m (s, a))+ -> (((s, a) -> b) -> Stream m (s, a) -> Stream m (s, a))+ -> (((s, a) -> b) -> Stream m a -> Stream m a)+with f comb g = fmap snd . comb g . f++-- Adapted from the vector package++-- | Same as 'filter' but with a monadic predicate.+--+-- >>> f p x = p x >>= \r -> return $ if r then Just x else Nothing+-- >>> filterM p = Stream.mapMaybeM (f p)+--+{-# 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++-- | Include only those elements that pass a predicate.+--+-- >>> filter p = Stream.filterM (return . p)+-- >>> filter p = Stream.mapMaybe (\x -> if p x then Just x else Nothing)+-- >>> filter p = Stream.postscanlMaybe (Scanl.filtering p)+--+{-# INLINE filter #-}+filter :: Monad m => (a -> Bool) -> Stream m a -> Stream m a+filter f = filterM (return . f)+-- filter p = scanMaybe (FL.filtering p)++-- | Drop repeated elements that are adjacent to each other using the supplied+-- comparison function.+--+-- >>> uniq = Stream.uniqBy (==)+--+-- To strip duplicate path separators:+--+-- >>> input = Stream.fromList "//a//b"+-- >>> f x y = x == '/' && y == '/'+-- >>> Stream.fold Fold.toList $ Stream.uniqBy f input+-- "/a/b"+--+-- Space: @O(1)@+--+-- /Pre-release/+--+{-# INLINE uniqBy #-}+uniqBy :: Monad m =>+ (a -> a -> Bool) -> Stream m a -> Stream m a+-- uniqBy eq = scanMaybe (FL.uniqBy eq)+uniqBy eq = catMaybes . rollingMap f++ where++ f pre curr =+ case pre of+ Nothing -> Just curr+ Just x -> if x `eq` curr then Nothing else Just curr++-- Adapted from the vector package++-- | Drop repeated elements that are adjacent to each other.+--+-- >>> uniq = Stream.uniqBy (==)+--+{-# INLINE_NORMAL uniq #-}+uniq :: (Eq a, Monad m) => Stream m a -> Stream m a+-- uniq = scanMaybe FL.uniq+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++-- | Deletes the first occurrence of the element in the stream that satisfies+-- the given equality predicate.+--+-- >>> input = Stream.fromList [1,3,3,5]+-- >>> Stream.fold Fold.toList $ Stream.deleteBy (==) 3 input+-- [1,3,5]+--+{-# INLINE_NORMAL deleteBy #-}+deleteBy :: Monad m => (a -> a -> Bool) -> a -> Stream m a -> Stream m a+-- deleteBy cmp x = scanMaybe (FL.deleteBy cmp x)+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++-- | Strip all leading and trailing occurrences of an element passing a+-- predicate and make all other consecutive occurrences uniq.+--+-- >> prune p = Stream.dropWhileAround p $ Stream.uniqBy (x y -> p x && p y)+--+-- @+-- > Stream.prune isSpace (Stream.fromList " hello world! ")+-- "hello world!"+--+-- @+--+-- Space: @O(1)@+--+-- /Unimplemented/+{-# INLINE prune #-}+prune ::+ -- (Monad m, Eq a) =>+ (a -> Bool) -> Stream m a -> Stream m a+prune = error "Not implemented yet!"++-- Possible implementation:+-- @repeated =+-- Stream.catMaybes . Stream.parseMany (Parser.groupBy (==) Fold.repeated)@+--+-- 'Fold.repeated' should return 'Just' when repeated, and 'Nothing' for a+-- single element.++-- | Emit only repeated elements, once.+--+-- /Unimplemented/+repeated :: -- (Monad m, Eq a) =>+ Stream m a -> Stream m a+repeated = undefined++------------------------------------------------------------------------------+-- Sampling+------------------------------------------------------------------------------++-- XXX We can implement this using addition instead of "mod" to make it more+-- efficient.++-- | @sampleFromThen offset stride@ takes the element at @offset@ index and+-- then every element at strides of @stride@.+--+-- >>> Stream.fold Fold.toList $ Stream.sampleFromThen 2 3 $ Stream.enumerateFromTo 0 10+-- [2,5,8]+--+{-# INLINE sampleFromThen #-}+sampleFromThen, strideFromThen :: Monad m =>+ Int -> Int -> Stream m a -> Stream m a+sampleFromThen offset stride =+ with indexed filter+ (\(i, _) -> i >= offset && (i - offset) `mod` stride == 0)++RENAME(strideFromThen,sampleFromThen)++------------------------------------------------------------------------------+-- Trimming+------------------------------------------------------------------------------++-- | init for non-empty streams, fails for empty stream case.+--+{-# INLINE initNonEmpty #-}+initNonEmpty :: Monad m => Stream m a -> Stream m a+initNonEmpty (Stream step1 state1) = Stream step (Nothing, state1)++ where++ step gst (Nothing, s1) = do+ r <- step1 (adaptState gst) s1+ return $+ case r of+ Yield x s -> Skip (Just x, s)+ Skip s -> Skip (Nothing, s)+ Stop -> error "initNonEmpty: empty Stream"++ step gst (Just a, s1) = do+ r <- step1 (adaptState gst) s1+ return $+ case r of+ Yield x s -> Yield a (Just x, s)+ Skip s -> Skip (Just a, s)+ Stop -> Stop++-- | tail for non-empty streams, fails for empty stream case.+--+-- See also 'tail' for a non-partial version of this function..+{-# INLINE tailNonEmpty #-}+tailNonEmpty :: Monad m => Stream m a -> Stream m a+tailNonEmpty (Stream step1 state1) = Stream step (Nothing, state1)++ where++ step gst (Nothing, s1) = do+ r <- step1 (adaptState gst) s1+ return $+ case r of+ Yield x s -> Skip (Just x, s)+ Skip s -> Skip (Nothing, s)+ Stop -> error "tailNonEmpty: empty Stream"++ step gst (Just a, s1) = do+ r <- step1 (adaptState gst) s1+ return $+ case r of+ Yield x s -> Yield x (Just x, s)+ Skip s -> Skip (Just a, s)+ Stop -> Stop++-- | Take all consecutive elements at the end of the stream for which the+-- predicate is true.+--+-- O(n) space, where n is the number elements taken.+--+-- /Unimplemented/+{-# INLINE takeWhileLast #-}+takeWhileLast :: -- Monad m =>+ (a -> Bool) -> Stream m a -> Stream m a+takeWhileLast = undefined -- fromStreamD $ D.takeWhileLast n $ toStreamD m++-- | Like 'takeWhile' and 'takeWhileLast' combined.+--+-- O(n) space, where n is the number elements taken from the end.+--+-- /Unimplemented/+{-# INLINE takeWhileAround #-}+takeWhileAround :: -- Monad m =>+ (a -> Bool) -> Stream m a -> Stream m a+takeWhileAround = undefined -- fromStreamD $ D.takeWhileAround n $ toStreamD m++-- Adapted from the vector package++-- | Discard first 'n' elements from the stream and take the rest.+--+{-# 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++-- Adapted from the vector package+data DropWhileState s a+ = DropWhileDrop s+ | DropWhileYield a s+ | DropWhileNext s++-- | Same as 'dropWhile' but with a monadic predicate.+--+{-# INLINE_NORMAL dropWhileM #-}+dropWhileM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a+-- dropWhileM p = scanMaybe (FL.droppingWhileM p)+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)++-- | Drop elements in the stream as long as the predicate succeeds and then+-- take the rest of the stream.+--+{-# INLINE dropWhile #-}+dropWhile :: Monad m => (a -> Bool) -> Stream m a -> Stream m a+-- dropWhile p = scanMaybe (FL.droppingWhile p)+dropWhile f = dropWhileM (return . f)++-- | Drop @n@ elements at the end of the stream.+--+-- O(n) space, where n is the number elements dropped.+--+-- /Unimplemented/+{-# INLINE dropLast #-}+dropLast :: -- Monad m =>+ Int -> Stream m a -> Stream m a+dropLast = undefined -- fromStreamD $ D.dropLast n $ toStreamD m++-- | Drop all consecutive elements at the end of the stream for which the+-- predicate is true.+--+-- O(n) space, where n is the number elements dropped.+--+-- /Unimplemented/+{-# INLINE dropWhileLast #-}+dropWhileLast :: -- Monad m =>+ (a -> Bool) -> Stream m a -> Stream m a+dropWhileLast = undefined -- fromStreamD $ D.dropWhileLast n $ toStreamD m++-- | Like 'dropWhile' and 'dropWhileLast' combined.+--+-- O(n) space, where n is the number elements dropped from the end.+--+-- /Unimplemented/+{-# INLINE dropWhileAround #-}+dropWhileAround :: -- Monad m =>+ (a -> Bool) -> Stream m a -> Stream m a+dropWhileAround = undefined -- fromStreamD $ D.dropWhileAround n $ toStreamD m++------------------------------------------------------------------------------+-- Inserting Elements+------------------------------------------------------------------------------++-- | @insertBy cmp elem stream@ inserts @elem@ before the first element in+-- @stream@ that is less than @elem@ when compared using @cmp@.+--+-- >>> insertBy cmp x = Stream.mergeBy cmp (Stream.fromPure x)+--+-- >>> input = Stream.fromList [1,3,5]+-- >>> Stream.fold Fold.toList $ Stream.insertBy compare 2 input+-- [1,2,3,5]+--+{-# 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)++data LoopState x s = FirstYield s+ | InterspersingYield s+ | YieldAndCarry x s++-- | Effectful variant of 'intersperse'. Insert an effect and its output+-- between successive elements of a stream. It does nothing if stream has less+-- than two elements.+--+-- Definition:+--+-- >>> intersperseM x = Stream.interleaveSepBy (Stream.repeatM x)+--+{-# 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)++-- | Insert a pure value between successive elements of a stream. It does+-- nothing if stream has less than two elements.+--+-- Definition:+--+-- >>> intersperse x = Stream.intersperseM (return x)+-- >>> intersperse x = Stream.unfoldEachSepBy x Unfold.identity+-- >>> intersperse x = Stream.unfoldEachSepBySeq x Unfold.identity+-- >>> intersperse x = Stream.interleaveSepBy (Stream.repeat x)+--+-- Example:+--+-- >>> f x y = Stream.toList $ Stream.intersperse x $ Stream.fromList y+-- >>> f ',' "abc"+-- "a,b,c"+-- >>> f ',' "a"+-- "a"+--+{-# INLINE intersperse #-}+intersperse :: Monad m => a -> Stream m a -> Stream m a+intersperse a = intersperseM (return a)++-- | Perform a side effect between two successive elements of a stream. It does+-- nothing if the stream has less than two elements.+--+-- >>> f x y = Stream.fold Fold.drain $ Stream.trace putChar $ Stream.intersperseM_ x $ Stream.fromList y+-- >>> f (putChar '.') "abc"+-- a.b.c+-- >>> f (putChar '.') "a"+-- a+--+-- /Pre-release/+{-# INLINE_NORMAL intersperseM_ #-}+intersperseM_ :: Monad m => m b -> Stream m a -> Stream m a+intersperseM_ m (Stream step1 state1) = Stream step (Left (pure (), state1))+ where+ {-# INLINE_LATE step #-}+ step gst (Left (eff, st)) = do+ r <- step1 gst st+ case r of+ Yield x s -> eff >> return (Yield x (Right s))+ Skip s -> return $ Skip (Left (eff, s))+ Stop -> return Stop++ step _ (Right st) = return $ Skip $ Left (void m, st)++-- | Intersperse a monadic action into the input stream after every @n@+-- elements.+--+-- Definition:+--+-- >> intersperseEveryM n x = Stream.interleaveEverySepBy n (Stream.repeatM x)+--+-- Idioms:+--+-- >>> intersperseM = Stream.intersperseEveryM 1+-- >>> intersperse x = Stream.intersperseEveryM 1 (return x)+--+-- Usage:+--+-- >> input = Stream.fromList "hello"+-- >> Stream.toList $ Stream.intersperseEveryM 2 (return ',') input+-- "he,ll,o"+--+-- /Unimplemented/+{-# INLINE intersperseEveryM #-}+intersperseEveryM :: -- Monad m =>+ Int -> m a -> Stream m a -> Stream m a+intersperseEveryM _n _f _xs = undefined++data SuffixState s a+ = SuffixElem s+ | SuffixSuffix s+ | SuffixYield a (SuffixState s a)++-- | Insert an effect and its output after every element of a stream.+--+-- Definition:+--+-- >>> intersperseEndByM x = Stream.interleaveEndBy (Stream.repeatM x)+--+-- Usage:+--+-- >>> f x y = Stream.toList $ Stream.intersperseEndByM (pure x) $ Stream.fromList y+-- >>> f ',' "abc"+-- "a,b,c,"+-- >>> f ',' "a"+-- "a,"+--+-- /Pre-release/+{-# INLINE_NORMAL intersperseEndByM #-}+intersperseEndByM, intersperseMSuffix :: forall m a. Monad m =>+ m a -> Stream m a -> Stream m a+intersperseEndByM 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++RENAME(intersperseMSuffix,intersperseEndByM)++-- | Insert an effect after every element of a stream.+--+-- Example:+--+-- >>> f x y = Stream.fold Fold.drain $ Stream.trace putChar $ Stream.intersperseEndByM_ x $ Stream.fromList y+-- >>> f (putChar '.') "abc"+-- a.b.c.+-- >>> f (putChar '.') "a"+-- a.+--+-- /Pre-release/+--+{-# INLINE_NORMAL intersperseEndByM_ #-}+intersperseEndByM_, intersperseMSuffix_ :: Monad m => m b -> Stream m a -> Stream m a+intersperseEndByM_ m (Stream step1 state1) = Stream step (Left state1)+ where+ {-# INLINE_LATE step #-}+ step gst (Left st) = do+ r <- step1 gst st+ case r of+ Yield x s -> return $ Yield x (Right s)+ Skip s -> return $ Skip $ Left s+ Stop -> return Stop++ step _ (Right st) = m >> return (Skip (Left st))++RENAME(intersperseMSuffix_,intersperseEndByM_)++data SuffixSpanState s a+ = SuffixSpanElem s Int+ | SuffixSpanSuffix s+ | SuffixSpanYield a (SuffixSpanState s a)+ | SuffixSpanLast+ | SuffixSpanStop++-- | Like 'intersperseEndByM' but intersperses an effectful action into the+-- input stream after every @n@ elements and also after the last element.+--+-- Example:+--+-- >>> input = Stream.fromList "hello"+-- >>> Stream.toList $ Stream.intersperseEndByEveryM 2 (return ',') input+-- "he,ll,o,"+-- >>> f n x y = Stream.toList $ Stream.intersperseEndByEveryM n (pure x) $ Stream.fromList y+-- >>> f 2 ',' "abcdef"+-- "ab,cd,ef,"+-- >>> f 2 ',' "abcdefg"+-- "ab,cd,ef,g,"+-- >>> f 2 ',' "a"+-- "a,"+--+-- /Pre-release/+--+{-# INLINE_NORMAL intersperseEndByEveryM #-}+intersperseEndByEveryM, intersperseMSuffixWith :: forall m a. Monad m+ => Int -> m a -> Stream m a -> Stream m a+intersperseEndByEveryM 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++RENAME(intersperseMSuffixWith,intersperseEndByEveryM)++-- | Insert a side effect before every element of a stream.+--+-- Definition:+--+-- >>> intersperseBeginByM_ = Stream.trace_+-- >>> intersperseBeginByM_ m = Stream.mapM (\x -> void m >> return x)+--+-- Usage:+--+-- >>> f x y = Stream.fold Fold.drain $ Stream.trace putChar $ Stream.intersperseBeginByM_ x $ Stream.fromList y+-- >>> f (putChar '.') "abc"+-- .a.b.c+--+-- Same as 'trace_'.+--+-- /Pre-release/+--+{-# INLINE intersperseBeginByM_ #-}+intersperseBeginByM_, intersperseMPrefix_ :: Monad m =>+ m b -> Stream m a -> Stream m a+intersperseBeginByM_ m = mapM (\x -> void m >> return x)++RENAME(intersperseMPrefix_,intersperseBeginByM_)++------------------------------------------------------------------------------+-- Inserting Time+------------------------------------------------------------------------------++-- XXX This should be in Prelude, should we export this as a helper function?++-- | Block the current thread for specified number of seconds.+{-# INLINE sleep #-}+sleep :: MonadIO m => Double -> m ()+sleep n = liftIO $ threadDelay $ round $ n * 1000000++-- | Introduce a delay of specified seconds between elements of the stream.+--+-- Definition:+--+-- >>> sleep n = liftIO $ threadDelay $ round $ n * 1000000+-- >>> delay = Stream.intersperseM_ . sleep+--+-- Example:+--+-- >>> input = Stream.enumerateFromTo 1 3+-- >>> Stream.fold (Fold.drainMapM print) $ Stream.delay 1 input+-- 1+-- 2+-- 3+--+{-# INLINE delay #-}+delay :: MonadIO m => Double -> Stream m a -> Stream m a+delay = intersperseM_ . sleep++-- | Introduce a delay of specified seconds after consuming an element of a+-- stream.+--+-- Definition:+--+-- >>> sleep n = liftIO $ threadDelay $ round $ n * 1000000+-- >>> delayPost = Stream.intersperseEndByM_ . sleep+--+-- Example:+--+-- >>> input = Stream.enumerateFromTo 1 3+-- >>> Stream.fold (Fold.drainMapM print) $ Stream.delayPost 1 input+-- 1+-- 2+-- 3+--+-- /Pre-release/+--+{-# INLINE delayPost #-}+delayPost :: MonadIO m => Double -> Stream m a -> Stream m a+delayPost n = intersperseMSuffix_ $ liftIO $ threadDelay $ round $ n * 1000000++-- | Introduce a delay of specified seconds before consuming an element of a+-- stream.+--+-- Definition:+--+-- >>> sleep n = liftIO $ threadDelay $ round $ n * 1000000+-- >>> delayPre = Stream.intersperseBeginByM_ . sleep+--+-- Example:+--+-- >>> input = Stream.enumerateFromTo 1 3+-- >>> Stream.fold (Fold.drainMapM print) $ Stream.delayPre 1 input+-- 1+-- 2+-- 3+--+-- /Pre-release/+--+{-# INLINE delayPre #-}+delayPre :: MonadIO m => Double -> Stream m a -> Stream m a+delayPre = intersperseMPrefix_. sleep++------------------------------------------------------------------------------+-- Reordering+------------------------------------------------------------------------------++-- | Returns the elements of the stream in reverse order. The stream must be+-- finite. Note that this necessarily buffers the entire stream in memory.+--+-- Definition:+--+-- >>> reverse m = Stream.concatEffect $ Stream.fold Fold.toListRev m >>= return . Stream.fromList+--+{-# INLINE_NORMAL reverse #-}+reverse :: Monad m => Stream m a -> Stream m a+reverse m = concatEffect $ fold FL.toListRev m <&> fromList+{-+reverse m = Stream step Nothing+ where+ {-# INLINE_LATE step #-}+ step _ Nothing = do+ xs <- foldl' (flip (:)) [] m+ return $ Skip (Just xs)+ step _ (Just (x:xs)) = return $ Yield x (Just xs)+ step _ (Just []) = return Stop+-}++-- | Like 'reverse' but several times faster, requires an 'Unbox' instance.+--+-- /O(n) space/+--+-- /Pre-release/+{-# INLINE reverseUnbox #-}+reverseUnbox :: (MonadIO m, Unbox a) => Stream m a -> Stream m a+reverseUnbox =+ A.concatRev -- unfoldMany A.readerRev+ . fromStreamK+ . K.reverse+ . toStreamK+ . A.chunksOf defaultChunkSize++-- | 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.+--+-- /Unimplemented/+--+{-# INLINE reassembleBy #-}+reassembleBy+ :: -- Monad m =>+ Fold m a b+ -> (a -> a -> Int)+ -> Stream m a+ -> Stream m b+reassembleBy = undefined++------------------------------------------------------------------------------+-- Position Indexing+------------------------------------------------------------------------------++-- Adapted from the vector package++-- |+-- >>> f = Scanl.mkScanl (\(i, _) x -> (i + 1, x)) (-1,undefined)+-- >>> indexed = Stream.postscanl f+-- >>> indexed = Stream.zipWith (,) (Stream.enumerateFrom 0)+-- >>> indexedR n = fmap (\(i, a) -> (n - i, a)) . indexed+--+-- Pair each element in a stream with its index, starting from index 0.+--+-- >>> Stream.fold Fold.toList $ Stream.indexed $ Stream.fromList "hello"+-- [(0,'h'),(1,'e'),(2,'l'),(3,'l'),(4,'o')]+--+{-# INLINE_NORMAL indexed #-}+indexed :: Monad m => Stream m a -> Stream m (Int, a)+-- indexed = scanMaybe FL.indexing+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++-- Adapted from the vector package++-- |+-- >>> f n = Scanl.mkScanl (\(i, _) x -> (i - 1, x)) (n + 1,undefined)+-- >>> indexedR n = Stream.postscanl (f n)+--+-- >>> s n = Stream.enumerateFromThen n (n - 1)+-- >>> indexedR n = Stream.zipWith (,) (s n)+--+-- Pair each element in a stream with its index, starting from the+-- given index @n@ and counting down.+--+-- >>> Stream.fold Fold.toList $ Stream.indexedR 10 $ Stream.fromList "hello"+-- [(10,'h'),(9,'e'),(8,'l'),(7,'l'),(6,'o')]+--+{-# INLINE_NORMAL indexedR #-}+indexedR :: Monad m => Int -> Stream m a -> Stream m (Int, a)+-- indexedR n = scanMaybe (FL.indexingRev n)+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++-------------------------------------------------------------------------------+-- Time Indexing+-------------------------------------------------------------------------------++-- Note: The timestamp stream must be the second stream in the zip so that the+-- timestamp is generated after generating the stream element and not before.+-- If we do not do that then the following example will generate the same+-- timestamp for first two elements:+--+-- Stream.fold Fold.toList $ Stream.timestamped $ Stream.delay $ Stream.enumerateFromTo 1 3++-- | Pair each element in a stream with an absolute timestamp, using a clock of+-- specified granularity. The timestamp is generated just before the element+-- is consumed.+--+-- >>> Stream.fold Fold.toList $ Stream.timestampWith 0.01 $ Stream.delay 1 $ Stream.enumerateFromTo 1 3+-- [(AbsTime (TimeSpec {sec = ..., nsec = ...}),1),(AbsTime (TimeSpec {sec = ..., nsec = ...}),2),(AbsTime (TimeSpec {sec = ..., nsec = ...}),3)]+--+-- /Pre-release/+--+{-# INLINE timestampWith #-}+timestampWith :: (MonadIO m)+ => Double -> Stream m a -> Stream m (AbsTime, a)+timestampWith g stream = zipWith (flip (,)) stream (absTimesWith g)++-- TBD: check performance vs a custom implementation without using zipWith.+--+-- /Pre-release/+--+{-# INLINE timestamped #-}+timestamped :: (MonadIO m)+ => Stream m a -> Stream m (AbsTime, a)+timestamped = timestampWith 0.01++-- | Pair each element in a stream with relative times starting from 0, using a+-- clock with the specified granularity. The time is measured just before the+-- element is consumed.+--+-- >>> Stream.fold Fold.toList $ Stream.timeIndexWith 0.01 $ Stream.delay 1 $ Stream.enumerateFromTo 1 3+-- [(RelTime64 (NanoSecond64 ...),1),(RelTime64 (NanoSecond64 ...),2),(RelTime64 (NanoSecond64 ...),3)]+--+-- /Pre-release/+--+{-# INLINE timeIndexWith #-}+timeIndexWith :: (MonadIO m)+ => Double -> Stream m a -> Stream m (RelTime64, a)+timeIndexWith g stream = zipWith (flip (,)) stream (relTimesWith g)++-- | Pair each element in a stream with relative times starting from 0, using a+-- 10 ms granularity clock. The time is measured just before the element is+-- consumed.+--+-- >>> Stream.fold Fold.toList $ Stream.timeIndexed $ Stream.delay 1 $ Stream.enumerateFromTo 1 3+-- [(RelTime64 (NanoSecond64 ...),1),(RelTime64 (NanoSecond64 ...),2),(RelTime64 (NanoSecond64 ...),3)]+--+-- /Pre-release/+--+{-# INLINE timeIndexed #-}+timeIndexed :: (MonadIO m)+ => Stream m a -> Stream m (RelTime64, a)+timeIndexed = timeIndexWith 0.01++------------------------------------------------------------------------------+-- Searching+------------------------------------------------------------------------------++-- | Find all the indices where the element in the stream satisfies the given+-- predicate.+--+-- >>> findIndices p = Stream.postscanlMaybe (Scanl.findIndices 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++-- | Find all the indices where the value of the element in the stream is equal+-- to the given value.+--+-- >>> elemIndices a = Stream.findIndices (== a)+--+{-# INLINE elemIndices #-}+elemIndices :: (Monad m, Eq a) => a -> Stream m a -> Stream m Int+elemIndices a = findIndices (== a)++------------------------------------------------------------------------------+-- Rolling map+------------------------------------------------------------------------------++data RollingMapState s a = RollingMapGo s a++-- | Like 'rollingMap' but with an effectful map function.+--+-- /Pre-release/+--+{-# INLINE rollingMapM #-}+rollingMapM :: Monad m => (Maybe a -> a -> m b) -> Stream m a -> Stream m b+-- rollingMapM f = scanMaybe (FL.slide2 $ Window.rollingMapM f)+rollingMapM f (Stream step1 state1) = Stream step (RollingMapGo state1 Nothing)++ where++ step gst (RollingMapGo s1 curr) = do+ r <- step1 (adaptState gst) s1+ case r of+ Yield x s -> do+ !res <- f curr x+ return $ Yield res $ RollingMapGo s (Just x)+ Skip s -> return $ Skip $ RollingMapGo s curr+ Stop -> return Stop++-- rollingMap is a special case of an incremental sliding fold. It can be+-- written as:+--+-- > fld f = slidingWindow 1 (Scanl.mkScanl (\_ (x,y) -> f y x)+-- > rollingMap f = Stream.postscan (fld f) undefined++-- | Apply a function on every two successive elements of a stream. The first+-- argument of the map function is the previous element and the second argument+-- is the current element. When the current element is the first element, the+-- previous element is 'Nothing'.+--+-- /Pre-release/+--+{-# INLINE rollingMap #-}+rollingMap :: Monad m => (Maybe a -> a -> b) -> Stream m a -> Stream m b+-- rollingMap f = scanMaybe (FL.slide2 $ Window.rollingMap f)+rollingMap f = rollingMapM (\x y -> return $ f x y)++-- | Like 'rollingMap' but requires at least two elements in the stream,+-- returns an empty stream otherwise.+--+-- This is the stream equivalent of the list idiom @zipWith f xs (tail xs)@.+--+-- /Pre-release/+--+{-# INLINE rollingMap2 #-}+rollingMap2 :: Monad m => (a -> a -> b) -> Stream m a -> Stream m b+rollingMap2 f = catMaybes . rollingMap g++ where++ g Nothing _ = Nothing+ g (Just x) y = Just (f x y)++------------------------------------------------------------------------------+-- Maybe Streams+------------------------------------------------------------------------------++-- XXX Will this always fuse properly?++-- | 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 = Stream.catMaybes . fmap f+--+{-# INLINE_NORMAL mapMaybe #-}+mapMaybe :: Monad m => (a -> Maybe b) -> Stream m a -> Stream m b+mapMaybe f = fmap fromJust . filter isJust . map f++-- | Like 'mapMaybe' but maps a monadic function.+--+-- Equivalent to:+--+-- >>> mapMaybeM f = Stream.catMaybes . Stream.mapM f+--+-- >>> mapM f = Stream.mapMaybeM (\x -> Just <$> f x)+--+{-# INLINE_NORMAL mapMaybeM #-}+mapMaybeM :: Monad m => (a -> m (Maybe b)) -> Stream m a -> Stream m b+mapMaybeM f = fmap fromJust . filter isJust . mapM f++-- | In a stream of 'Maybe's, discard 'Nothing's and unwrap 'Just's.+--+-- >>> catMaybes = Stream.mapMaybe id+-- >>> catMaybes = fmap fromJust . Stream.filter isJust+--+-- /Pre-release/+--+{-# INLINE catMaybes #-}+catMaybes :: Monad m => Stream m (Maybe a) -> Stream m a+-- catMaybes = fmap fromJust . filter isJust+catMaybes (Stream step state) = Stream step1 state++ where++ {-# INLINE_LATE step1 #-}+ step1 gst st = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> do+ return+ $ case x of+ Just a -> Yield a s+ Nothing -> Skip s+ Skip s -> return $ Skip s+ Stop -> return Stop++-- | Use a filtering scan on a stream.+--+-- >>> postscanlMaybe f = Stream.catMaybes . Stream.postscanl f+--+{-# INLINE postscanlMaybe #-}+postscanlMaybe :: Monad m => Scanl m a (Maybe b) -> Stream m a -> Stream m b+postscanlMaybe f = catMaybes . postscanl f++{-# DEPRECATED scanMaybe "Use postscanlMaybe instead" #-}+{-# INLINE scanMaybe #-}+scanMaybe :: Monad m => Fold m a (Maybe b) -> Stream m a -> Stream m b+scanMaybe f = catMaybes . postscan f++------------------------------------------------------------------------------+-- Either streams+------------------------------------------------------------------------------++-- | Discard 'Right's and unwrap 'Left's in an 'Either' stream.+--+-- >>> catLefts = fmap (fromLeft undefined) . Stream.filter isLeft+--+-- /Pre-release/+--+{-# INLINE catLefts #-}+catLefts :: Monad m => Stream m (Either a b) -> Stream m a+catLefts = fmap (fromLeft undefined) . filter isLeft++-- | Discard 'Left's and unwrap 'Right's in an 'Either' stream.+--+-- >>> catRights = fmap (fromRight undefined) . Stream.filter isRight+--+-- /Pre-release/+--+{-# INLINE catRights #-}+catRights :: Monad m => Stream m (Either a b) -> Stream m b+catRights = fmap (fromRight undefined) . filter isRight++-- | Remove the either wrapper and flatten both lefts and as well as rights in+-- the output stream.+--+-- >>> catEithers = fmap (either id id)+--+-- /Pre-release/+--+{-# INLINE catEithers #-}+catEithers :: Monad m => Stream m (Either a a) -> Stream m a+catEithers = fmap (either id id)++------------------------------------------------------------------------------+-- Splitting+------------------------------------------------------------------------------++-- Design note: If we use splitSepBy_ on an empty stream what should be the+-- result? Let's try the splitOn function in the "split" package:+--+-- > splitOn "a" ""+-- [""]+--+-- Round tripping the result through intercalate gives identity:+--+-- > intercalate "a" [""]+-- ""+--+-- Now let's try intercalate on empty list:+--+-- > intercalate "a" []+-- ""+--+-- Round tripping it with splitOn is not identity:+--+-- > splitOn "a" ""+-- [""]+--+-- Because intercalate flattens the two layers, both [] and [""] produce the+-- same result after intercalate. Therefore, inverse of intercalate is not+-- possible. We have to choose one of the two options for splitting an empty+-- stream.+--+-- Choosing empty stream as the result of splitting empty stream makes better+-- sense. This is different from the split package's choice. Splitting an empty+-- stream resulting into a non-empty stream seems a bit odd. Also, splitting+-- empty stream to empty stream is consistent with splitEndBy operation as+-- well.++{-# ANN type SplitSepBy Fuse #-}+data SplitSepBy s fs b a+ = SplitSepByInit s+ | SplitSepByInitFold0 s+ | SplitSepByInitFold1 s fs+ | SplitSepByCheck s a fs+ | SplitSepByNext s fs+ | SplitSepByYield b (SplitSepBy s fs b a)+ | SplitSepByDone++-- | Split on an infixed separator element, dropping the separator. The+-- supplied 'Fold' is applied on the split segments. Splits the stream on+-- separator elements determined by the supplied predicate, separator is+-- considered as infixed between two segments:+--+-- Definition:+--+--+-- Usage:+--+-- >>> splitOn p xs = Stream.fold Fold.toList $ Stream.splitSepBy_ p Fold.toList (Stream.fromList xs)+-- >>> splitOn (== '.') "a.b"+-- ["a","b"]+--+-- Splitting an empty stream results in an empty stream i.e. zero splits:+--+-- >>> splitOn (== '.') ""+-- []+--+-- If the stream does not contain the separator then it results in a single+-- split:+--+-- >>> splitOn (== '.') "abc"+-- ["abc"]+--+-- If one or both sides of the separator are missing then the empty segment on+-- that side is folded to the default output of the fold:+--+-- >>> splitOn (== '.') "."+-- ["",""]+--+-- >>> splitOn (== '.') ".a"+-- ["","a"]+--+-- >>> splitOn (== '.') "a."+-- ["a",""]+--+-- >>> splitOn (== '.') "a..b"+-- ["a","","b"]+--+-- 'splitSepBy_' is an inverse of 'unfoldEachSepBy':+--+-- > Stream.unfoldEachSepBy '.' Unfold.fromList . Stream.splitSepBy_ (== '.') Fold.toList === id+--+-- Assuming the input stream does not contain the separator:+--+-- > Stream.splitSepBy_ (== '.') Fold.toList . Stream.unfoldEachSepBy '.' Unfold.fromList === id+--+{-# INLINE splitSepBy_ #-}+splitSepBy_ :: Monad m => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+-- We can express the infix splitting in terms of optional suffix split+-- fold. After applying a suffix split fold repeatedly if the last segment+-- ends with a suffix then we need to return the default output of the fold+-- after that to make it an infix split.+--+-- Alternately, we can also express it using an optional prefix split fold.+-- If the first segment starts with a prefix then we need to emit the+-- default output of the fold before that to make it an infix split, and+-- then apply prefix split fold repeatedly.+--+splitSepBy_ predicate (Fold fstep initial _ final) (Stream step1 state1) =+ Stream step (SplitSepByInit state1)++ where++ -- Note: there is a question of whether we should initialize the fold+ -- before we run the stream or only after the stream yields an element. If+ -- we initialize it before then we may have to discard an effect if the+ -- stream does not yield anything. If we initialize it after then we may+ -- have to discard the stream element if the fold terminates without+ -- consuming anything. Though the state machine is simpler if we initialize+ -- the fold first. Also, in most common cases the fold is not effectful.+ -- On the other hand, in most cases the fold will not terminate without+ -- consuming anything. So both ways are similar.+ {-# INLINE_LATE step #-}+ step _ (SplitSepByInit st) = do+ fres <- initial+ return+ $ Skip+ $ case fres of+ FL.Done b -> SplitSepByYield b (SplitSepByInit st)+ FL.Partial fs -> SplitSepByInitFold1 st fs++ step _ (SplitSepByInitFold0 st) = do+ fres <- initial+ return+ $ Skip+ $ case fres of+ FL.Done b -> SplitSepByYield b (SplitSepByInitFold0 st)+ FL.Partial fs -> SplitSepByNext st fs++ step gst (SplitSepByInitFold1 st fs) = do+ r <- step1 (adaptState gst) st+ case r of+ Yield x s -> return $ Skip $ SplitSepByCheck s x fs+ Skip s -> return $ Skip (SplitSepByInitFold1 s fs)+ Stop -> final fs >> return Stop++ step _ (SplitSepByCheck st x fs) = do+ if predicate x+ then do+ b <- final fs+ return $ Skip $ SplitSepByYield b (SplitSepByInitFold0 st)+ else do+ fres <- fstep fs x+ return+ $ Skip+ $ case fres of+ FL.Done b -> SplitSepByYield b (SplitSepByInitFold0 st)+ FL.Partial fs1 -> SplitSepByNext st fs1++ step gst (SplitSepByNext st fs) = do+ r <- step1 (adaptState gst) st+ case r of+ Yield x s -> return $ Skip $ SplitSepByCheck s x fs+ Skip s -> return $ Skip (SplitSepByNext s fs)+ Stop -> do+ b <- final fs+ return $ Skip $ SplitSepByYield b SplitSepByDone++ step _ (SplitSepByYield b next) = return $ Yield b next+ step _ SplitSepByDone = return Stop++{-# DEPRECATED splitOn "Please use splitSepBy_ instead. Note the difference in behavior on splitting empty stream." #-}+{-# INLINE splitOn #-}+splitOn :: Monad m => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+splitOn predicate f =+ foldManyPost (FL.takeEndBy_ predicate f)
src/Streamly/Internal/Data/Stream/Transformer.hs view
@@ -1,44 +1,62 @@+{-# LANGUAGE CPP #-} -- | -- Module : Streamly.Internal.Data.Stream.Transformer--- Copyright : (c) 2019 Composewell Technologies+-- Copyright : (c) 2018 Composewell Technologies -- License : BSD-3-Clause -- Maintainer : streamly@composewell.com -- Stability : experimental -- Portability : GHC+--+-- Transform the underlying monad of a stream using a monad transfomer. module Streamly.Internal.Data.Stream.Transformer (+ -- * Fold to Transformer Monad foldlT , foldrT + -- * Inner Monad Operations , liftInner- , usingReaderT+ , runReaderT+ , usingReaderT+ , withReaderT+ , localReaderT+ , evalStateT- , usingStateT , runStateT+ , usingStateT ) where -import Control.Monad.Trans.Class (MonadTrans)+#include "inline.hs"++import Control.Monad.Trans.Class (MonadTrans(lift)) import Control.Monad.Trans.Reader (ReaderT) import Control.Monad.Trans.State.Strict (StateT)-import Streamly.Internal.Data.Stream.Type (Stream, fromStreamD, toStreamD)+import GHC.Types (SPEC(..))+import Streamly.Internal.Data.SVar.Type (defState, adaptState) -import qualified Streamly.Internal.Data.Stream.StreamD.Transformer as D+import qualified Control.Monad.Trans.Reader as Reader+import qualified Control.Monad.Trans.State.Strict as State --- $setup--- >>> :m--- >>> import Control.Monad.Trans.Class (lift)--- >>> import Control.Monad.Trans.Identity (runIdentityT)--- >>> import qualified Streamly.Internal.Data.Stream as Stream+import Streamly.Internal.Data.Stream.Type +#include "DocTestDataStream.hs"+ -- | Lazy left fold to a transformer monad. ---{-# INLINE foldlT #-}+{-# 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 f z s = D.foldlT f z (toStreamD s)+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 -- | Right fold to a transformer monad. This is the most general right fold -- function. 'foldrS' is a special case of 'foldrT', however 'foldrS'@@ -53,22 +71,38 @@ -- monads e.g. to a different streaming type. -- -- /Pre-release/-{-# INLINE foldrT #-}-foldrT :: (Monad m, Monad (s m), MonadTrans s)- => (a -> s m b -> s m b) -> s m b -> Stream m a -> s m b-foldrT f z s = D.foldrT f z (toStreamD s)+{-# INLINE_NORMAL foldrT #-}+foldrT :: (Monad m, Monad (t m), MonadTrans t)+ => (a -> t m b -> t m b) -> t m b -> Stream m a -> t m b+foldrT f final (Stream step state) = go SPEC state+ where+ {-# INLINE_LATE go #-}+ go !_ st = do+ r <- lift $ step defState st+ case r of+ Yield x s -> f x (go SPEC s)+ Skip s -> go SPEC s+ Stop -> final ---------------------------------------------------------------------------------- Add and remove a monad transformer-------------------------------------------------------------------------------+-------------------------------------------------------------------------------+-- Transform Inner Monad+------------------------------------------------------------------------------- -- | Lift the inner monad @m@ of @Stream m a@ to @t m@ where @t@ is a monad -- transformer. ---{-# INLINE liftInner #-}+{-# INLINE_NORMAL liftInner #-} liftInner :: (Monad m, MonadTrans t, Monad (t m)) => Stream m a -> Stream (t m) a-liftInner xs = fromStreamD $ D.liftInner (toStreamD xs)+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 ------------------------------------------------------------------------------ -- Sharing read only state in a stream@@ -76,16 +110,21 @@ -- | Evaluate the inner monad of a stream as 'ReaderT'. ---{-# INLINE runReaderT #-}+{-# INLINE_NORMAL runReaderT #-} runReaderT :: Monad m => m s -> Stream (ReaderT s m) a -> Stream m a-runReaderT s xs = fromStreamD $ D.runReaderT s (toStreamD xs)+runReaderT env (Stream step state) = Stream step' (state, env)+ where+ {-# INLINE_LATE step' #-}+ step' gst (st, action) = do+ sv <- action+ r <- Reader.runReaderT (step (adaptState gst) st) sv+ return $ case r of+ Yield x s -> Yield x (s, return sv)+ Skip s -> Skip (s, return sv)+ Stop -> Stop -- | Run a stream transformation using a given environment. ----- See also: 'Serial.map'------ / Internal/--- {-# INLINE usingReaderT #-} usingReaderT :: Monad m@@ -95,6 +134,28 @@ -> Stream m a usingReaderT r f xs = runReaderT r $ f $ liftInner xs +-- | Modify the environment of the underlying ReaderT monad.+{-# INLINABLE withReaderT #-}+withReaderT :: Monad m =>+ (r2 -> r1) -> Stream (ReaderT r1 m) a -> Stream (ReaderT r2 m) a+withReaderT f (Stream step state) = Stream step1 state++ where++ {-# INLINE_LATE step1 #-}+ step1 gst st = do+ r <- Reader.withReaderT f (step (adaptState gst) st)+ return $ case r of+ Yield x s -> Yield x s+ Skip s -> Skip s+ Stop -> Stop++-- | Modify the environment of the underlying ReaderT monad.+{-# INLINABLE localReaderT #-}+localReaderT :: Monad m =>+ (r -> r) -> Stream (ReaderT r m) a -> Stream (ReaderT r m) a+localReaderT = withReaderT+ ------------------------------------------------------------------------------ -- Sharing read write state in a stream ------------------------------------------------------------------------------@@ -103,12 +164,34 @@ -- -- >>> evalStateT s = fmap snd . Stream.runStateT s ----- / Internal/+{-# INLINE_NORMAL evalStateT #-}+evalStateT :: Monad m => m s -> Stream (StateT s m) a -> Stream m a+evalStateT initial (Stream step state) = Stream step' (state, initial)+ where+ {-# INLINE_LATE step' #-}+ step' gst (st, action) = do+ sv <- action+ (r, !sv') <- State.runStateT (step (adaptState gst) st) sv+ return $ case r of+ Yield x s -> Yield x (s, return sv')+ Skip s -> Skip (s, return sv')+ Stop -> Stop++-- | Evaluate the inner monad of a stream as 'StateT' and emit the resulting+-- state and value pair after each step. ---{-# INLINE evalStateT #-}-evalStateT :: Monad m => m s -> Stream (StateT s m) a -> Stream m a--- evalStateT s = fmap snd . runStateT s-evalStateT s xs = fromStreamD $ D.evalStateT s (toStreamD xs)+{-# INLINE_NORMAL runStateT #-}+runStateT :: Monad m => m s -> Stream (StateT s m) a -> Stream m (s, a)+runStateT initial (Stream step state) = Stream step' (state, initial)+ where+ {-# INLINE_LATE step' #-}+ step' gst (st, action) = do+ sv <- action+ (r, !sv') <- State.runStateT (step (adaptState gst) st) sv+ return $ case r of+ Yield x s -> Yield (sv', x) (s, return sv')+ Skip s -> Skip (s, return sv')+ Stop -> Stop -- | Run a stateful (StateT) stream transformation using a given state. --@@ -116,20 +199,11 @@ -- -- See also: 'scan' ----- / Internal/--- {-# INLINE usingStateT #-} usingStateT :: Monad m => m s- -> (Stream (StateT s m) a -> Stream (StateT s m) a)- -> Stream m a+ -> (Stream (StateT s m) a -> Stream (StateT s m) b) -> Stream m a+ -> Stream m b usingStateT s f = evalStateT s . f . liftInner---- | Evaluate the inner monad of a stream as 'StateT' and emit the resulting--- state and value pair after each step.----{-# INLINE runStateT #-}-runStateT :: Monad m => m s -> Stream (StateT s m) a -> Stream m (s, a)-runStateT s xs = fromStreamD $ D.runStateT s (toStreamD xs)
src/Streamly/Internal/Data/Stream/Type.hs view
@@ -1,491 +1,2704 @@-{-# LANGUAGE UndecidableInstances #-}---- |--- Module : Streamly.Internal.Data.Stream.Type--- Copyright : (c) 2017 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC----module Streamly.Internal.Data.Stream.Type- (- -- * Stream Type- Stream -- XXX To be removed- , StreamK-- -- * Type Conversion- , fromStreamK- , toStreamK- , fromStreamD- , toStreamD- , fromStream- , toStream- , Streamly.Internal.Data.Stream.Type.fromList-- -- * Construction- , cons- , consM- , nil- , nilM- , fromPure- , fromEffect-- -- * Applicative- , crossApply- , crossApplySnd- , crossApplyFst- , crossWith- , cross-- -- * Bind/Concat- , bindWith- , concatMapWith-- -- * Double folds- , eqBy- , cmpBy- )-where--#include "inline.hs"--import Control.Applicative (liftA2)-import Data.Foldable (Foldable(foldl'), fold)-import Data.Functor.Identity (Identity(..), runIdentity)-import Data.Maybe (fromMaybe)-import Data.Semigroup (Endo(..))-import GHC.Exts (IsList(..), IsString(..), oneShot)-import Streamly.Internal.BaseCompat ((#.))-import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)-import Text.Read- ( Lexeme(Ident), lexP, parens, prec, readPrec, readListPrec- , readListPrecDefault)--import qualified Streamly.Internal.Data.Stream.Common as P-import qualified Streamly.Internal.Data.Stream.StreamD.Type as D-import qualified Streamly.Internal.Data.Stream.StreamK.Type as K---- $setup--- >>> import qualified Streamly.Data.Fold as Fold--- >>> import qualified Streamly.Internal.Data.Unfold as Unfold--- >>> import qualified Streamly.Internal.Data.Stream as Stream----------------------------------------------------------------------------------- Stream----------------------------------------------------------------------------------- | Semigroup instance appends two streams:------ >>> (<>) = Stream.append----newtype StreamK m a = StreamK (K.StreamK m a)- -- XXX when deriving do we inherit an INLINE?- deriving (Semigroup, Monoid)--type Stream = StreamK----------------------------------------------------------------------------------- Conversions---------------------------------------------------------------------------------{-# INLINE_EARLY fromStreamK #-}-fromStreamK :: K.StreamK m a -> Stream m a-fromStreamK = StreamK--{-# INLINE_EARLY toStreamK #-}-toStreamK :: Stream m a -> K.StreamK m a-toStreamK (StreamK k) = k--{-# INLINE_EARLY fromStreamD #-}-fromStreamD :: Monad m => D.Stream m a -> Stream m a-fromStreamD = fromStreamK . D.toStreamK--{-# INLINE_EARLY toStreamD #-}-toStreamD :: Applicative m => Stream m a -> D.Stream m a-toStreamD = D.fromStreamK . toStreamK--{-# INLINE fromStream #-}-fromStream :: Monad m => D.Stream m a -> Stream m a-fromStream = fromStreamD--{-# INLINE toStream #-}-toStream :: Applicative m => Stream m a -> D.Stream m a-toStream = toStreamD----------------------------------------------------------------------------------- Generation----------------------------------------------------------------------------------- |--- >>> fromList = Prelude.foldr Stream.cons Stream.nil------ Construct a stream from a list of pure values. This is more efficient than--- 'fromFoldable'.----{-# INLINE fromList #-}-fromList :: Monad m => [a] -> Stream m a-fromList = fromStreamK . P.fromList----------------------------------------------------------------------------------- Comparison----------------------------------------------------------------------------------- | Compare two streams for equality----{-# INLINE eqBy #-}-eqBy :: Monad m =>- (a -> b -> Bool) -> Stream m a -> Stream m b -> m Bool-eqBy f m1 m2 = D.eqBy f (toStreamD m1) (toStreamD m2)---- | Compare two streams----{-# INLINE cmpBy #-}-cmpBy- :: Monad m- => (a -> b -> Ordering) -> Stream m a -> Stream m b -> m Ordering-cmpBy f m1 m2 = D.cmpBy f (toStreamD m1) (toStreamD m2)----------------------------------------------------------------------------------- Functor---------------------------------------------------------------------------------instance Monad m => Functor (Stream m) where- {-# INLINE fmap #-}- -- IMPORTANT: do not use eta reduction.- fmap f m = fromStreamD $ D.mapM (return . f) $ toStreamD m-- {-# INLINE (<$) #-}- (<$) = fmap . const----------------------------------------------------------------------------------- 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?--instance IsList (Stream Identity a) where- type (Item (Stream Identity a)) = a-- {-# INLINE fromList #-}- fromList xs = StreamK $ P.fromList xs-- {-# INLINE toList #-}- toList (StreamK xs) = runIdentity $ P.toList xs--instance Eq a => Eq (Stream Identity a) where- {-# INLINE (==) #-}- (==) (StreamK xs) (StreamK ys) = runIdentity $ P.eqBy (==) xs ys--instance Ord a => Ord (Stream Identity a) where- {-# INLINE compare #-}- compare (StreamK xs) (StreamK 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- Streamly.Internal.Data.Stream.Type.fromList <$> readPrec-- readListPrec = readListPrecDefault--instance (a ~ Char) => IsString (Stream Identity a) where- {-# INLINE fromString #-}- fromString xs = StreamK $ P.fromList xs------------------------------------------------------------------------------------ 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.--instance (Foldable m, Monad m) => Foldable (Stream m) where-- {-# INLINE foldMap #-}- foldMap f (StreamK xs) = fold $ P.foldr (mappend . f) mempty xs-- {-# 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 = oneShot $ \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----------------------------------------------------------------------------------instance Traversable (Stream Identity) where- {-# INLINE traverse #-}- traverse f (StreamK xs) =- fmap StreamK $ runIdentity $ P.foldr consA (pure mempty) xs-- where-- consA x ys = liftA2 K.cons (f x) ys------------------------------------------------------------------------------------ Construction----------------------------------------------------------------------------------infixr 5 `cons`---- | A right associative prepend operation to add a pure value at the head of--- an existing stream::------ >>> s = 1 `Stream.cons` 2 `Stream.cons` 3 `Stream.cons` Stream.nil--- >>> Stream.fold Fold.toList s--- [1,2,3]------ It can be used efficiently with 'Prelude.foldr':------ >>> fromFoldable = Prelude.foldr Stream.cons Stream.nil------ Same as the following but more efficient:------ >>> cons x xs = return x `Stream.consM` xs------ /CPS/----{-# INLINE_NORMAL cons #-}-cons :: a -> Stream m a -> Stream m a-cons x = fromStreamK . K.cons x . toStreamK--infixr 5 `consM`---- | A right associative prepend operation to add an effectful value at the--- head of an existing stream::------ >>> s = putStrLn "hello" `consM` putStrLn "world" `consM` Stream.nil--- >>> Stream.fold Fold.drain s--- hello--- world------ It can be used efficiently with 'Prelude.foldr':------ >>> fromFoldableM = Prelude.foldr Stream.consM Stream.nil------ Same as the following but more efficient:------ >>> consM x xs = Stream.fromEffect x `Stream.append` xs------ /CPS/----{-# INLINE consM #-}-{-# SPECIALIZE consM :: IO a -> Stream IO a -> Stream IO a #-}-consM :: Monad m => m a -> Stream m a -> Stream m a-consM m = fromStreamK . K.consM m . toStreamK---- | A stream that terminates without producing any output or side effect.------ >>> Stream.fold Fold.toList Stream.nil--- []----{-# INLINE_NORMAL nil #-}-nil :: Stream m a-nil = fromStreamK K.nil---- | A stream that terminates without producing any output, but produces a side--- effect.------ >>> Stream.fold Fold.toList (Stream.nilM (print "nil"))--- "nil"--- []------ /Pre-release/-{-# INLINE_NORMAL nilM #-}-nilM :: Monad m => m b -> Stream m a-nilM = fromStreamK . K.nilM---- | Create a singleton stream from a pure value.------ >>> fromPure a = a `cons` Stream.nil--- >>> fromPure = pure--- >>> fromPure = fromEffect . pure----{-# INLINE_NORMAL fromPure #-}-fromPure :: a -> Stream m a-fromPure = fromStreamK . K.fromPure---- | Create a singleton stream from a monadic action.------ >>> fromEffect m = m `consM` Stream.nil--- >>> fromEffect = Stream.sequence . Stream.fromPure------ >>> Stream.fold Fold.drain $ Stream.fromEffect (putStrLn "hello")--- hello----{-# INLINE_NORMAL fromEffect #-}-fromEffect :: Monad m => m a -> Stream m a-fromEffect = fromStreamK . K.fromEffect------------------------------------------------------------------------------------ Applicative------------------------------------------------------------------------------------ | Apply a stream of functions to a stream of values and flatten the results.------ Note that the second stream is evaluated multiple times.------ >>> crossApply = Stream.crossWith id----{-# INLINE crossApply #-}-crossApply :: Stream m (a -> b) -> Stream m a -> Stream m b-crossApply m1 m2 =- fromStreamK $ K.crossApply (toStreamK m1) (toStreamK m2)--{-# INLINE crossApplySnd #-}-crossApplySnd :: Stream m a -> Stream m b -> Stream m b-crossApplySnd m1 m2 =- fromStreamK $ K.crossApplySnd (toStreamK m1) (toStreamK m2)--{-# INLINE crossApplyFst #-}-crossApplyFst :: Stream m a -> Stream m b -> Stream m a-crossApplyFst m1 m2 =- fromStreamK $ K.crossApplyFst (toStreamK m1) (toStreamK m2)---- |--- Definition:------ >>> crossWith f m1 m2 = fmap f m1 `Stream.crossApply` m2------ Note that the second stream is evaluated multiple times.----{-# INLINE crossWith #-}-crossWith :: Monad m => (a -> b -> c) -> Stream m a -> Stream m b -> Stream m c-crossWith f m1 m2 = fmap f m1 `crossApply` m2---- | Given a @Stream m a@ and @Stream m b@ generate a stream with all possible--- combinations of the tuple @(a, b)@.------ Definition:------ >>> cross = Stream.crossWith (,)------ The second stream is evaluated multiple times. If that is not desired it can--- be cached in an 'Data.Array.Array' and then generated from the array before--- calling this function. Caching may also improve performance if the stream is--- expensive to evaluate.------ See 'Streamly.Internal.Data.Unfold.cross' for a much faster fused--- alternative.------ Time: O(m x n)------ /Pre-release/-{-# INLINE cross #-}-cross :: Monad m => Stream m a -> Stream m b -> Stream m (a, b)-cross = crossWith (,)------------------------------------------------------------------------------------ Bind/Concat------------------------------------------------------------------------------------ |------ /CPS/-{-# INLINE bindWith #-}-bindWith- :: (Stream m b -> Stream m b -> Stream m b)- -> Stream m a- -> (a -> Stream m b)- -> Stream m b-bindWith par m1 f =- fromStreamK- $ K.bindWith- (\s1 s2 -> toStreamK $ par (fromStreamK s1) (fromStreamK s2))- (toStreamK m1)- (toStreamK . f)---- | @concatMapWith mixer generator stream@ is a two dimensional looping--- combinator. The @generator@ function is used to generate streams from the--- elements in the input @stream@ and the @mixer@ function is used to merge--- those streams.------ /CPS/-{-# INLINE concatMapWith #-}-concatMapWith- :: (Stream m b -> Stream m b -> Stream m b)- -> (a -> Stream m b)- -> Stream m a- -> Stream m b-concatMapWith par f xs = bindWith par xs f+{-# LANGUAGE CPP #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE TypeFamilies #-}+-- Must come after TypeFamilies, otherwise it is re-enabled.+-- MonoLocalBinds enabled by TypeFamilies causes perf regressions in general.+{-# LANGUAGE NoMonoLocalBinds #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ViewPatterns #-}++-- |+-- Module : Streamly.Internal.Data.Stream.Type+-- Copyright : (c) 2018 Composewell Technologies+-- (c) Roman Leshchinskiy 2008-2010+-- License : BSD-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC++-- The stream type is inspired by the vector package. A few functions in this+-- module have been originally adapted from the vector package (c) Roman+-- Leshchinskiy. See the notes in specific functions.++module Streamly.Internal.Data.Stream.Type+ (+ -- * Type+ Step (..)+ -- XXX UnStream is exported to avoid a performance issue in some+ -- combinators if we use the pattern synonym "Stream".+ , Stream (Stream, UnStream)++ -- * Nested+ , Nested(..)++ -- * To StreamK+ , fromStreamK+ , toStreamK++ -- * From Unfold+ , unfold++ -- * Construction+ -- ** Primitives+ , nilM+ , consM++ -- ** From Values+ , fromPure+ , fromEffect++ -- ** From Containers+ , Streamly.Internal.Data.Stream.Type.fromList++ -- * Elimination+ -- ** Primitives+ , uncons++ -- ** Strict Left Folds+ , Streamly.Internal.Data.Stream.Type.fold+ , foldBreak+ , foldAddLazy+ , foldAdd+ , foldEither++ , Streamly.Internal.Data.Stream.Type.foldl'+ , foldlM'+ , foldlx'+ , foldlMx'++ -- ** Lazy Right Folds+ , foldrM+ , foldrMx+ , Streamly.Internal.Data.Stream.Type.foldr+ , foldrS++ -- ** Specific Folds+ , drain+ , head+ , headElse+ , Streamly.Internal.Data.Stream.Type.toList++ -- * Mapping+ , map+ , mapM++ -- * Stateful Filters+ , take+ , takeWhile+ , takeWhileM+ , takeEndBy_+ , takeEndBy+ , takeEndByM++ -- * Combining Two Streams+ -- ** Appending+ -- | Append a stream after another. A special case of concatMap or+ -- unfoldEach Note, appending more than two streams is called @concat@+ -- which could be called appendMany or appendAll in append terminology and+ -- is equivalent to @concatMap id@. Append is equivalent to @mergeBy fst@.+ , AppendState(..)+ , append++ -- ** Zipping+ -- | Zip corresponding elements of two streams.+ , zipWithM+ , zipWith++ -- ** Cross Product+ , crossApply+ , crossApplyFst+ , crossApplySnd+ , crossWith+ , cross+ , FairUnfoldState (..)+ , fairCrossWithM+ , fairCrossWith+ , fairCross+ , loop -- forEach+ , loopBy++ -- * Unfold Many+ , ConcatMapUState (..)+ , unfoldEach++ -- * UnfoldCross+ , unfoldCross++ -- * ConcatMap+ -- | Generate streams by mapping a stream generator on each element of an+ -- input stream, append the resulting streams and flatten.+ , concatEffect+ , concatMap+ , concatMapM+ , concat++ -- * ConcatFor+ , concatFor+ , concatForM++ -- * Unfold Iterate+ , unfoldIterate+ , bfsUnfoldIterate+ , altBfsUnfoldIterate++ -- * Concat Iterate+ , concatIterateScan+ , concatIterate+ , bfsConcatIterate+ , altBfsConcatIterate++ -- * Fold Many+ , FoldMany (..) -- for inspection testing+ , FoldManyPost (..)+ , foldMany+ , foldManyPost+ , foldManySepBy+ , groupsOf+ , refoldMany+ , refoldIterateM++ -- * Fold Iterate+ , bfsReduceIterate+ , bfsFoldIterate++ -- * Splitting+ , indexEndBy+ , indexEndBy_++ -- * Multi-stream folds+ -- | These should probably be expressed using zipping operations.+ , eqBy+ , cmpBy++ -- * Utilities+ , splitAt++ -- * Deprecated+ , sliceOnSuffix+ , unfoldMany+ , indexOnSuffix+ , CrossStream+ , mkCross+ , unCross+ , reduceIterateBfs+ , unfoldIterateDfs+ , unfoldIterateBfs+ , unfoldIterateBfsRev+ , concatIterateDfs+ , concatIterateBfs+ , concatIterateBfsRev+ )+where++#include "deprecation.h"+#include "inline.hs"++#if !MIN_VERSION_base(4,18,0)+import Control.Applicative (liftA2)+#endif+import Control.Monad.Catch (MonadThrow, throwM)+import Control.Monad.Trans.Class (MonadTrans(lift))+import Control.Monad.IO.Class (MonadIO(..))+import Data.Bifunctor (first)+import Data.Foldable (Foldable(foldl'), fold, foldr)+import Data.Functor (($>))+import Data.Functor.Identity (Identity(..))+#if __GLASGOW_HASKELL__ >= 810+import Data.Kind (Type)+#endif+import Data.Maybe (fromMaybe)+import Data.Semigroup (Endo(..))+import Fusion.Plugin.Types (Fuse(..))+import GHC.Base (build)+import GHC.Exts (IsList(..), IsString(..), oneShot)+import GHC.Types (SPEC(..))+import Prelude hiding+ (head, map, mapM, take, concatMap, takeWhile, zipWith, concat, splitAt)+import Text.Read+ ( Lexeme(Ident), lexP, parens, prec, readPrec, readListPrec+ , readListPrecDefault)++import Streamly.Internal.BaseCompat ((#.))+import Streamly.Internal.Data.Fold.Type (Fold(..))+import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)+import Streamly.Internal.Data.Refold.Type (Refold(..))+import Streamly.Internal.Data.Stream.Step (Step (..))+import Streamly.Internal.Data.SVar.Type (State, adaptState, defState)+import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))+import Streamly.Internal.Data.Unfold.Type (Unfold(..))++import qualified Streamly.Internal.Data.Fold.Type as FL hiding (foldr)+import qualified Streamly.Internal.Data.StreamK.Type as K+import qualified Streamly.Internal.Data.Unfold.Type as Unfold++#include "DocTestDataStream.hs"++------------------------------------------------------------------------------+-- The direct style stream type+------------------------------------------------------------------------------++-- gst = global state++-- | A stream consists of a step function that generates the next step given a+-- current state, and the current state.+data Stream m a =+ forall s. UnStream (State K.StreamK m a -> s -> m (Step s a)) s++-- XXX This causes perf trouble when pattern matching with "Stream" in a+-- recursive way, e.g. in uncons, foldBreak, concatMap. We need to get rid of+-- this.+unShare :: Stream m a -> Stream m a+unShare (UnStream step state) = UnStream step' state+ where step' gst = step (adaptState gst)++pattern Stream :: (State K.StreamK m a -> s -> m (Step s a)) -> s -> Stream m a+pattern Stream step state <- (unShare -> UnStream step state)+ where Stream = UnStream++{-# COMPLETE Stream #-}++------------------------------------------------------------------------------+-- Primitives+------------------------------------------------------------------------------++-- | A stream that terminates without producing any output, but produces a side+-- effect.+--+-- >>> nilM action = Stream.before action Stream.nil+-- >>> Stream.fold Fold.toList (Stream.nilM (print "nil"))+-- "nil"+-- []+--+-- /Pre-release/+{-# INLINE_NORMAL nilM #-}+nilM :: Applicative m => m b -> Stream m a+nilM m = Stream (\_ _ -> m $> Stop) ()++infixr 5 `consM`++-- XXX see https://github.com/composewell/streamly/issues/3126 - for using a+-- list or maybe an effect "m (Stream m a)" along with "step" in the stream+-- structure for better cons and append performance.++-- | Like 'cons' but fuses an effect instead of a pure value.+{-# INLINE_NORMAL consM #-}+consM :: Applicative m => m a -> Stream m a -> Stream m a+consM m (Stream step state) = Stream step1 Nothing++ where++ {-# INLINE_LATE step1 #-}+ step1 _ Nothing = (`Yield` Just state) <$> m+ step1 gst (Just st) = do+ (\case+ Yield a s -> Yield a (Just s)+ Skip s -> Skip (Just s)+ Stop -> Stop) <$> step gst st++-- | 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.+--+-- Properties:+--+-- >>> Nothing <- Stream.uncons Stream.nil+-- >>> Just ("a", t) <- Stream.uncons (Stream.cons "a" Stream.nil)+--+-- This can be used to consume the stream in an imperative manner one element+-- at a time, 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.+--+-- All the folds in this module can be expressed in terms of 'uncons', however,+-- this is generally less efficient than specific folds because it takes apart+-- the stream one element at a time, therefore, does not take adavantage of+-- stream fusion.+--+-- 'foldBreak' is a more general way of consuming a stream piecemeal.+--+-- >>> :{+-- uncons xs = do+-- r <- Stream.foldBreak Fold.one xs+-- return $ case r of+-- (Nothing, _) -> Nothing+-- (Just h, t) -> Just (h, t)+-- :}+--+{-# INLINE_NORMAL uncons #-}+uncons :: Monad m => Stream m a -> m (Maybe (a, Stream m a))+uncons (UnStream step state) = go SPEC state+ where+ go !_ st = do+ r <- step defState st+ case r of+ Yield x s -> return $ Just (x, Stream step s)+ Skip s -> go SPEC s+ Stop -> return Nothing++------------------------------------------------------------------------------+-- From 'Unfold'+------------------------------------------------------------------------------++data UnfoldState s = UnfoldNothing | UnfoldJust s++-- | Convert an 'Unfold' into a stream by supplying it an input seed.+--+-- >>> s = Stream.unfold Unfold.replicateM (3, putStrLn "hello")+-- >>> Stream.fold Fold.drain s+-- hello+-- hello+-- hello+--+{-# INLINE_NORMAL unfold #-}+unfold :: Applicative m => Unfold m a b -> a -> Stream m b+unfold (Unfold ustep inject) seed = Stream step UnfoldNothing++ where++ {-# INLINE_LATE step #-}+ step _ UnfoldNothing = Skip . UnfoldJust <$> inject seed+ step _ (UnfoldJust st) = do+ (\case+ Yield x s -> Yield x (UnfoldJust s)+ Skip s -> Skip (UnfoldJust s)+ Stop -> Stop) <$> ustep st++------------------------------------------------------------------------------+-- From Values+------------------------------------------------------------------------------++-- | Create a singleton stream from a pure value.+--+-- >>> fromPure a = a `Stream.cons` Stream.nil+-- >>> fromPure = pure+-- >>> fromPure = Stream.fromEffect . pure+--+{-# INLINE_NORMAL fromPure #-}+fromPure :: Applicative m => a -> Stream m a+fromPure x = Stream (\_ s -> pure $ step undefined s) True+ where+ {-# INLINE_LATE step #-}+ step _ True = Yield x False+ step _ False = Stop++-- | Create a singleton stream from a monadic action.+--+-- >>> fromEffect m = m `Stream.consM` Stream.nil+-- >>> fromEffect = Stream.sequence . Stream.fromPure+--+-- >>> Stream.fold Fold.drain $ Stream.fromEffect (putStrLn "hello")+-- hello+--+{-# INLINE_NORMAL fromEffect #-}+fromEffect :: Applicative m => m a -> Stream m a+fromEffect m = Stream step True++ where++ {-# INLINE_LATE step #-}+ step _ True = (`Yield` False) <$> m+ step _ False = pure Stop++------------------------------------------------------------------------------+-- From Containers+------------------------------------------------------------------------------++-- Adapted from the vector package.++-- | Construct a stream from a list of pure values.+{-# INLINE_LATE fromList #-}+fromList :: Applicative m => [a] -> Stream m a+#ifdef USE_UNFOLDS_EVERYWHERE+fromList = unfold Unfold.fromList+#else+fromList = Stream step+ where+ {-# INLINE_LATE step #-}+ step _ (x:xs) = pure $ Yield x xs+ step _ [] = pure Stop+#endif++------------------------------------------------------------------------------+-- Conversions From/To+------------------------------------------------------------------------------++-- | Convert a CPS encoded StreamK to direct style step encoded StreamD+{-# INLINE_LATE fromStreamK #-}+fromStreamK :: Applicative m => K.StreamK m a -> Stream m a+fromStreamK = Stream step+ where+ step gst m1 =+ let stop = pure Stop+ single a = pure $ Yield a K.nil+ yieldk a r = pure $ Yield a r+ in K.foldStreamShared gst yieldk single stop m1++-- | Convert a direct style step encoded StreamD to a CPS encoded StreamK+{-# INLINE_LATE toStreamK #-}+toStreamK :: Monad m => Stream m a -> K.StreamK m a+toStreamK (Stream step state) = go state+ where+ 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++{-# RULES "fromStreamK/toStreamK fusion"+ forall s. toStreamK (fromStreamK s) = s #-}+{-# RULES "toStreamK/fromStreamK fusion"+ forall s. fromStreamK (toStreamK s) = s #-}++------------------------------------------------------------------------------+-- Running a 'Fold'+------------------------------------------------------------------------------++-- | Fold resulting in either breaking the stream or continuation of the fold.+-- Instead of supplying the input stream in one go we can run the fold multiple+-- times, each time supplying the next segment of the input stream. If the fold+-- has not yet finished it returns a fold that can be run again otherwise it+-- returns the fold result and the residual stream.+--+-- /Internal/+{-# INLINE_NORMAL foldEither #-}+foldEither :: Monad m =>+ Fold m a b -> Stream m a -> m (Either (Fold m a b) (b, Stream m a))+foldEither (Fold fstep begin done final) (UnStream step state) = do+ res <- begin+ case res of+ FL.Partial fs -> go SPEC fs state+ FL.Done fb -> return $! Right (fb, Stream step state)++ where++ {-# INLINE go #-}+ go !_ !fs st = do+ r <- step defState st+ case r of+ Yield x s -> do+ res <- fstep fs x+ case res of+ FL.Done b -> return $! Right (b, Stream step s)+ FL.Partial fs1 -> go SPEC fs1 s+ Skip s -> go SPEC fs s+ Stop ->+ let f = Fold fstep (return $ FL.Partial fs) done final+ in return $! Left f++-- | Like 'fold' but also returns the remaining stream. The resulting stream+-- would be 'Stream.nil' if the stream finished before the fold.+--+{-# INLINE_NORMAL foldBreak #-}+foldBreak :: Monad m => Fold m a b -> Stream m a -> m (b, Stream m a)+foldBreak fld strm = do+ r <- foldEither fld strm+ case r of+ Right res -> return res+ Left (Fold _ initial _ final) -> do+ res <- initial+ case res of+ FL.Done _ -> error "foldBreak: unreachable state"+ FL.Partial s -> do+ b <- final s+ return (b, nil)++ where++ nil = Stream (\_ _ -> return Stop) ()++-- >>> fold f = Fold.extractM . Stream.foldAddLazy f+-- >>> fold f = Stream.fold Fold.one . Stream.foldMany0 f+-- >>> fold f = Fold.extractM <=< Stream.foldAdd f++-- | Fold a stream using the supplied left 'Fold' and reducing the resulting+-- expression strictly at each step. The behavior is similar to 'foldl''. A+-- 'Fold' can terminate early without consuming the full stream. See the+-- documentation of individual 'Fold's for termination behavior.+--+-- Definitions:+--+-- >>> fold f = fmap fst . Stream.foldBreak f+-- >>> fold f = Stream.parse (Parser.fromFold f)+--+-- Example:+--+-- >>> Stream.fold Fold.sum (Stream.enumerateFromTo 1 100)+-- 5050+--+{-# INLINE_NORMAL fold #-}+fold :: Monad m => Fold m a b -> Stream m a -> m b+fold fld strm = do+ (b, _) <- foldBreak fld strm+ return b++-- | Append a stream to a fold lazily to build an accumulator incrementally.+--+-- Example, to continue folding a list of streams on the same sum fold:+--+-- >>> streams = [Stream.fromList [1..5], Stream.fromList [6..10]]+-- >>> f = Prelude.foldl Stream.foldAddLazy Fold.sum streams+-- >>> Stream.fold f Stream.nil+-- 55+--+{-# INLINE_NORMAL foldAddLazy #-}+foldAddLazy :: Monad m => Fold m a b -> Stream m a -> Fold m a b+foldAddLazy (Fold fstep finitial fextract ffinal) (Stream sstep state) =+ Fold fstep initial fextract ffinal++ where++ initial = do+ res <- finitial+ case res of+ FL.Partial fs -> go SPEC fs state+ FL.Done fb -> return $ FL.Done fb++ {-# INLINE go #-}+ go !_ !fs st = do+ r <- sstep defState st+ case r of+ Yield x s -> do+ res <- fstep fs x+ case res of+ FL.Done b -> return $ FL.Done b+ FL.Partial fs1 -> go SPEC fs1 s+ Skip s -> go SPEC fs s+ Stop -> return $ FL.Partial fs++-- >>> foldAdd f = Stream.foldAddLazy f >=> Fold.reduce++-- |+-- >>> foldAdd = flip Fold.addStream+--+foldAdd :: Monad m => Fold m a b -> Stream m a -> m (Fold m a b)+foldAdd f =+ Streamly.Internal.Data.Stream.Type.fold (FL.duplicate f)++------------------------------------------------------------------------------+-- Right Folds+------------------------------------------------------------------------------++-- Adapted from the vector package.+--+-- XXX Use of SPEC constructor in folds causes 2x performance degradation in+-- one shot operations, but helps immensely in operations composed of multiple+-- combinators or the same combinator many times. There seems to be an+-- opportunity to optimize here, can we get both, better perf for single ops+-- as well as composed ops? Without SPEC, all single operation benchmarks+-- become 2x faster.++-- The way we want a left fold to be strict, dually we want the right fold to+-- be lazy. The correct signature of the fold function to keep it lazy must be+-- (a -> m b -> m b) instead of (a -> b -> m b). We were using the latter+-- earlier, which is incorrect. In the latter signature we have to feed the+-- value to the fold function after evaluating the monadic action, depending on+-- the bind behavior of the monad, the action may get evaluated immediately+-- introducing unnecessary strictness to the fold. If the implementation is+-- lazy the following example, must work:+--+-- S.foldrM (\x t -> if x then return t else return False) (return True)+-- (S.fromList [False,undefined] :: Stream IO Bool)++-- | 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 = Stream.fromList (2:4:5:undefined)+-- >>> step x xs = if odd x then return True else xs+-- >>> Stream.foldrM step (return False) s+-- True+--+-- >>> import Control.Monad (join)+-- >>> foldrM f z = join . Stream.foldr f z+--+{-# INLINE_NORMAL foldrM #-}+foldrM :: Monad m => (a -> m b -> m b) -> m b -> Stream m a -> m b+-- foldrM f z = join . Streamly.Internal.Data.Stream.StreamD.Type.foldr f z+foldrM f z (Stream step state) = go SPEC state+ where+ {-# INLINE_LATE go #-}+ go !_ st = do+ r <- step defState st+ case r of+ Yield x s -> f x (go SPEC s)+ Skip s -> go SPEC s+ Stop -> z++{-# INLINE_NORMAL foldrMx #-}+foldrMx :: Monad m+ => (a -> m x -> m x) -> m x -> (m x -> m b) -> Stream m a -> m b+foldrMx fstep final convert (Stream step state) = convert $ go SPEC state+ where+ {-# INLINE_LATE go #-}+ go !_ st = do+ r <- step defState st+ case r of+ Yield x s -> fstep x (go SPEC s)+ Skip s -> go SPEC s+ Stop -> final++-- XXX Should we make all argument strict wherever we use SPEC?++-- Note that foldr works on pure values, therefore it becomes necessarily+-- strict when the monad m is strict. In that case it cannot terminate early,+-- it would evaluate all of its input. Though, this should work fine with lazy+-- monads. For example, if "any" is implemented using "foldr" instead of+-- "foldrM" it performs the same with Identity monad but performs 1000x slower+-- with IO monad.++-- | 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.+--+-- >>> foldr f z = Stream.foldrM (\a b -> f a <$> b) (return z)+--+-- Note: This is similar to Fold.foldr' (the right fold via left fold), but+-- could be more efficient.+--+{-# INLINE_NORMAL foldr #-}+foldr :: Monad m => (a -> b -> b) -> b -> Stream m a -> m b+foldr f z = foldrM (liftA2 f . return) (return z)++-- this performs horribly, should not be used+{-# INLINE_NORMAL foldrS #-}+foldrS+ :: Monad m+ => (a -> Stream m b -> Stream m b)+ -> Stream m b+ -> Stream m a+ -> Stream m b+foldrS f final (Stream step state) = go SPEC state+ where+ {-# INLINE_LATE go #-}+ go !_ st = concatEffect $ fmap g $ step defState st++ g r =+ case r of+ Yield x s -> f x (go SPEC s)+ Skip s -> go SPEC s+ Stop -> final++------------------------------------------------------------------------------+-- Left Folds+------------------------------------------------------------------------------++-- XXX run begin action only if the stream is not empty.+{-# INLINE_NORMAL foldlMx' #-}+foldlMx' :: Monad m => (x -> a -> m x) -> m x -> (x -> m b) -> Stream m a -> m b+foldlMx' fstep begin done (Stream step state) =+ begin >>= \x -> go SPEC x state+ where+ -- XXX !acc?+ {-# INLINE_LATE go #-}+ go !_ acc st = acc `seq` do+ r <- step defState st+ case r of+ Yield x s -> do+ acc' <- fstep acc x+ go SPEC acc' s+ Skip s -> go SPEC acc s+ Stop -> done acc++{-# INLINE foldlx' #-}+foldlx' :: Monad m => (x -> a -> x) -> x -> (x -> b) -> Stream m a -> m b+foldlx' fstep begin done =+ foldlMx' (\b a -> return (fstep b a)) (return begin) (return . done)++-- Adapted from the vector package.+-- XXX implement in terms of foldlMx'?+{-# INLINE_NORMAL foldlM' #-}+foldlM' :: Monad m => (b -> a -> m b) -> m b -> Stream m a -> m b+foldlM' fstep mbegin (Stream step state) = do+ begin <- mbegin+ go SPEC begin state+ where+ {-# INLINE_LATE go #-}+ go !_ acc st = acc `seq` do+ r <- step defState st+ case r of+ Yield x s -> do+ acc' <- fstep acc x+ go SPEC acc' s+ Skip s -> go SPEC acc s+ Stop -> return acc++{-# INLINE foldl' #-}+foldl' :: Monad m => (b -> a -> b) -> b -> Stream m a -> m b+foldl' fstep begin = foldlM' (\b a -> return (fstep b a)) (return begin)++------------------------------------------------------------------------------+-- Special folds+------------------------------------------------------------------------------++-- >>> drain = mapM_ (\_ -> return ())++-- |+-- Definitions:+--+-- >>> drain = Stream.fold Fold.drain+-- >>> drain = Stream.foldrM (\_ xs -> xs) (return ())+--+-- Run a stream, discarding 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 head #-}+head :: Monad m => Stream m a -> m (Maybe a)+#ifdef USE_FOLDS_EVERYWHERE+head = fold Fold.one+#else+head = foldrM (\x _ -> return (Just x)) (return Nothing)+#endif++{-# INLINE_NORMAL headElse #-}+headElse :: Monad m => a -> Stream m a -> m a+headElse a = foldrM (\x _ -> return x) (return a)++------------------------------------------------------------------------------+-- To Containers+------------------------------------------------------------------------------++-- This toList impl is faster (30% on streaming-benchmarks) than the+-- corresponding left fold. The left fold retains an additional argument in the+-- recursive loop.+--+-- Core for the right fold loop:+--+-- main_$s$wgo1+-- = \ sc_s3e6 sc1_s3e5 ->+-- case ># sc1_s3e5 100000# of {+-- __DEFAULT ->+-- case main_$s$wgo1 sc_s3e6 (+# sc1_s3e5 1#) of+--+-- Core for the left fold loop:+--+-- main_$s$wgo1+-- = \ sc_s3oT sc1_s3oS sc2_s3oR ->+-- case sc2_s3oR of fs2_a2lw { __DEFAULT ->+-- case ># sc1_s3oS 100000# of {+-- __DEFAULT ->+-- let { wild_a2og = I# sc1_s3oS } in+-- main_$s$wgo1+-- sc_s3oT (+# sc1_s3oS 1#) (\ x_X9 -> fs2_a2lw (: wild_a2og x_X9));++-- |+-- Definitions:+--+-- >>> toList = Stream.foldr (:) []+-- >>> toList = Stream.fold Fold.toList+--+-- 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.Data.Array" instead.+--+-- Note that this could a bit more efficient compared to @Stream.fold+-- Fold.toList@, and it can fuse with pure list consumers.+--+{-# INLINE_NORMAL toList #-}+toList :: Monad m => Stream m a -> m [a]+toList = Streamly.Internal.Data.Stream.Type.foldr (:) []++-- Use foldr/build fusion to fuse with list consumers+-- This can be useful when using the IsList instance+{-# INLINE_LATE toListFB #-}+toListFB :: (a -> b -> b) -> b -> Stream Identity a -> b+toListFB c n (Stream step state) = go state+ where+ go st = case runIdentity (step defState st) of+ Yield x s -> x `c` go s+ Skip s -> go s+ Stop -> n++{-# RULES "toList Identity" Streamly.Internal.Data.Stream.Type.toList = toListId #-}+{-# INLINE_EARLY toListId #-}+toListId :: Stream Identity a -> Identity [a]+toListId s = Identity $ build (\c n -> toListFB c n s)++------------------------------------------------------------------------------+-- Multi-stream folds+------------------------------------------------------------------------------++-- Adapted from the vector package.++-- | Compare two streams for equality+{-# INLINE_NORMAL eqBy #-}+eqBy :: Monad m => (a -> b -> Bool) -> Stream m a -> Stream m b -> m Bool+eqBy eq (Stream step1 t1) (Stream step2 t2) = eq_loop0 SPEC t1 t2+ where+ eq_loop0 !_ s1 s2 = do+ r <- step1 defState s1+ case r of+ Yield x s1' -> eq_loop1 SPEC x s1' s2+ Skip s1' -> eq_loop0 SPEC s1' s2+ Stop -> eq_null s2++ eq_loop1 !_ x s1 s2 = do+ r <- step2 defState s2+ case r of+ Yield y s2'+ | eq x y -> eq_loop0 SPEC s1 s2'+ | otherwise -> return False+ Skip s2' -> eq_loop1 SPEC x s1 s2'+ Stop -> return False++ eq_null s2 = do+ r <- step2 defState s2+ case r of+ Yield _ _ -> return False+ Skip s2' -> eq_null s2'+ Stop -> return True++-- Adapted from the vector package.++-- | Compare two streams lexicographically.+{-# INLINE_NORMAL cmpBy #-}+cmpBy+ :: Monad m+ => (a -> b -> Ordering) -> Stream m a -> Stream m b -> m Ordering+cmpBy cmp (Stream step1 t1) (Stream step2 t2) = cmp_loop0 SPEC t1 t2+ where+ cmp_loop0 !_ s1 s2 = do+ r <- step1 defState s1+ case r of+ Yield x s1' -> cmp_loop1 SPEC x s1' s2+ Skip s1' -> cmp_loop0 SPEC s1' s2+ Stop -> cmp_null s2++ cmp_loop1 !_ x s1 s2 = do+ r <- step2 defState s2+ case r of+ Yield y s2' -> case x `cmp` y of+ EQ -> cmp_loop0 SPEC s1 s2'+ c -> return c+ Skip s2' -> cmp_loop1 SPEC x s1 s2'+ Stop -> return GT++ cmp_null s2 = do+ r <- step2 defState s2+ case r of+ Yield _ _ -> return LT+ Skip s2' -> cmp_null s2'+ Stop -> return EQ++------------------------------------------------------------------------------+-- Transformations+------------------------------------------------------------------------------++-- Adapted from the vector package.++-- |+-- >>> mapM f = Stream.sequence . fmap f+--+-- Apply a monadic function to each element of the stream and replace it with+-- the output of the resulting action.+--+-- >>> s = Stream.fromList ["a", "b", "c"]+-- >>> Stream.fold Fold.drain $ Stream.mapM putStr s+-- abc+--+-- This is functional equivalent of an imperative loop.+--+{-# INLINE_NORMAL mapM #-}+mapM :: Monad m => (a -> m b) -> Stream m a -> Stream m b+mapM f (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 -> f x >>= \a -> return $ Yield a s+ Skip s -> return $ Skip s+ Stop -> return Stop++{-# INLINE map #-}+map :: Monad m => (a -> b) -> Stream m a -> Stream m b+map f = mapM (return . f)++-- (Functor m) based implementation of fmap does not fuse well in+-- streaming-benchmarks. XXX need to investigate why.+instance Monad m => Functor (Stream m) where+ {-# INLINE fmap #-}+ fmap = map++ {-# INLINE (<$) #-}+ (<$) = fmap . const++------------------------------------------------------------------------------+-- Lists+------------------------------------------------------------------------------++-- 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?++instance IsList (Stream Identity a) where+ type (Item (Stream Identity a)) = a++ {-# INLINE fromList #-}+ fromList = Streamly.Internal.Data.Stream.Type.fromList++ {-# INLINE toList #-}+ toList = runIdentity . Streamly.Internal.Data.Stream.Type.toList++instance Eq a => Eq (Stream Identity a) where+ {-# INLINE (==) #-}+ (==) xs ys = runIdentity $ eqBy (==) xs ys++instance Ord a => Ord (Stream Identity a) where+ {-# INLINE compare #-}+ compare xs ys = runIdentity $ 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 (GHC.Exts.toList dl)++instance Read a => Read (Stream Identity a) where+ readPrec = parens $ prec 10 $ do+ Ident "fromList" <- lexP+ Streamly.Internal.Data.Stream.Type.fromList <$> readPrec++ readListPrec = readListPrecDefault++instance (a ~ Char) => IsString (Stream Identity a) where+ {-# INLINE fromString #-}+ fromString = Streamly.Internal.Data.Stream.Type.fromList++-------------------------------------------------------------------------------+-- 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.++-- There is no Traversable instance because, there is no scalable cons for+-- StreamD, use toList and fromList instead.++instance (Foldable m, Monad m) => Foldable (Stream m) where++ {-# INLINE foldMap #-}+ foldMap f =+ Data.Foldable.fold+ . Streamly.Internal.Data.Stream.Type.foldr (mappend . f) mempty++ {-# INLINE foldr #-}+ foldr f z t = appEndo (foldMap (Endo #. f) t) z++ {-# INLINE foldl' #-}+ foldl' f z0 xs = Data.Foldable.foldr f' id xs z0+ where f' x k = oneShot $ \z -> k $! f z x++ {-# INLINE length #-}+ length = Data.Foldable.foldl' (\n _ -> n + 1) 0++ {-# INLINE elem #-}+ elem = any . (==)++ {-# INLINE maximum #-}+ maximum =+ fromMaybe (errorWithoutStackTrace "maximum: empty stream")+ . toMaybe+ . Data.Foldable.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+ . Data.Foldable.foldl' getMin Nothing'++ where++ getMin Nothing' x = Just' x+ getMin (Just' mn) x = Just' $! min mn x++ {-# INLINE sum #-}+ sum = Data.Foldable.foldl' (+) 0++ {-# INLINE product #-}+ product = Data.Foldable.foldl' (*) 1++-------------------------------------------------------------------------------+-- Filtering+-------------------------------------------------------------------------------++-- Adapted from the vector package.++-- | Take first 'n' elements from the stream and discard the rest.+--+{-# INLINE_NORMAL take #-}+take :: Applicative m => Int -> Stream m a -> Stream m a+take n (Stream step state) = n `seq` Stream step' (state, 0)++ where++ {-# INLINE_LATE step' #-}+ step' gst (st, i) | i < n = do+ (\case+ Yield x s -> Yield x (s, i + 1)+ Skip s -> Skip (s, i)+ Stop -> Stop) <$> step gst st+ step' _ (_, _) = pure Stop++-- Adapted from the vector package.++-- | Same as 'takeWhile' but with a monadic predicate.+--+{-# INLINE_NORMAL takeWhileM #-}+takeWhileM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a+-- takeWhileM p = scanMaybe (FL.takingEndByM_ (\x -> not <$> p x))+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++-- | End the stream as soon as the predicate fails on an element.+--+{-# INLINE takeWhile #-}+takeWhile :: Monad m => (a -> Bool) -> Stream m a -> Stream m a+takeWhile f = takeWhileM (return . f)++-- Like takeWhile but with an inverted condition and also taking+-- the matching element.++{-# INLINE_NORMAL takeEndByM #-}+takeEndByM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a+takeEndByM f (Stream step state) = Stream step' (Just state)+ where+ {-# INLINE_LATE step' #-}+ step' gst (Just st) = do+ r <- step gst st+ case r of+ Yield x s -> do+ b <- f x+ return $+ if not b+ then Yield x (Just s)+ else Yield x Nothing+ Skip s -> return $ Skip (Just s)+ Stop -> return Stop++ step' _ Nothing = return Stop++{-# INLINE takeEndBy #-}+takeEndBy :: Monad m => (a -> Bool) -> Stream m a -> Stream m a+takeEndBy f = takeEndByM (return . f)++-- |+-- >>> takeEndBy_ f = Stream.takeWhile (not . f)+--+{-# INLINE takeEndBy_ #-}+takeEndBy_ :: Monad m => (a -> Bool) -> Stream m a -> Stream m a+takeEndBy_ f = takeWhile (not . f)++------------------------------------------------------------------------------+-- Appending+------------------------------------------------------------------------------++data AppendState s1 s2 = AppendFirst s1 | AppendSecond s2++-- Performance Note: From an implementation perspective,+-- StreamK.'Streamly.Data.StreamK.append' translates into a function call+-- whereas Stream.'append' translates into a conditional branch (jump).+-- However, the overhead of the function call in StreamK.append is incurred+-- only once, while the overhead of the conditional branch in fused append is+-- incurred for each element in the stream. As a result, StreamK.append has a+-- linear time complexity of O(n), while fused append has a quadratic time+-- complexity of O(n^2), where @n@ represents the number of 'append's used.++-- | WARNING! O(n^2) time complexity wrt number of streams. Suitable for+-- statically fusing a small number of streams. Use the O(n) complexity+-- StreamK.'Streamly.Data.StreamK.append' otherwise.+--+-- Fuses two streams sequentially, yielding all elements from the first+-- stream, and then all elements from the second stream.+--+-- >>> s1 = Stream.fromList [1,2]+-- >>> s2 = Stream.fromList [3,4]+-- >>> Stream.fold Fold.toList $ s1 `Stream.append` s2+-- [1,2,3,4]+--+{-# 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++------------------------------------------------------------------------------+-- Zipping+------------------------------------------------------------------------------++-- | Like 'zipWith' but using a monadic zipping function.+--+{-# 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++{-# RULES "zipWithM xs xs"+ forall f xs. zipWithM @Identity f xs xs = mapM (\x -> f x x) xs #-}++-- | WARNING! O(n^2) time complexity wrt number of streams. Suitable for+-- statically fusing a small number of streams. Use the O(n) complexity+-- StreamK.'Streamly.Data.StreamK.zipWith' otherwise.+--+-- Stream @a@ is evaluated first, followed by stream @b@, the resulting+-- elements @a@ and @b@ are then zipped using the supplied zip function and the+-- result @c@ is yielded to the consumer.+--+-- If stream @a@ or stream @b@ ends, the zipped stream ends. If stream @b@ ends+-- first, the element @a@ from previous evaluation of stream @a@ is discarded.+--+-- >>> s1 = Stream.fromList [1,2,3]+-- >>> s2 = Stream.fromList [4,5,6]+-- >>> Stream.fold Fold.toList $ Stream.zipWith (+) s1 s2+-- [5,7,9]+--+{-# 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))++------------------------------------------------------------------------------+-- Combine N Streams - concatAp+------------------------------------------------------------------------------++-- XXX unfoldApplyEach++-- | Apply a stream of functions to a stream of values and flatten the results.+--+-- Note that the second stream is evaluated multiple times.+--+-- >>> crossApply = Stream.crossWith id+--+{-# INLINE_NORMAL crossApply #-}+crossApply :: Functor f => Stream f (a -> b) -> Stream f a -> Stream f b+crossApply (Stream stepa statea) (Stream stepb stateb) =+ Stream step' (Left statea)++ where++ {-# INLINE_LATE step' #-}+ step' gst (Left st) = fmap+ (\case+ 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+ (\case+ 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)++-- This is shared by all fairUnfold, fairConcat combinators.+data FairUnfoldState o i =+ FairUnfoldInit o ([i] -> [i])+ | FairUnfoldNext o ([i] -> [i]) [i]+ | FairUnfoldDrain ([i] -> [i]) [i]++-- XXX will it perform better if we write it in the same way as crossApply?+-- crossApply is faster than unfoldCross in equation solving benchmarks.++-- | Like 'fairCrossWith' but with monadic function argument.+--+{-# INLINE_NORMAL fairCrossWithM #-}+fairCrossWithM :: Monad m =>+ (a -> b -> m c) -> Stream m a -> Stream m b -> Stream m c+fairCrossWithM f (Stream step1 state1) (Stream step2 state2) =+ Stream step (FairUnfoldInit state1 id)++ where++ {-# INLINE_LATE step #-}+ step gst (FairUnfoldInit o ls) = do+ r <- step1 (adaptState gst) o+ return $ case r of+ Yield b o' -> Skip (FairUnfoldNext o' id (ls [(b,state2)]))+ Skip o' -> Skip (FairUnfoldInit o' ls)+ Stop -> Skip (FairUnfoldDrain id (ls []))++ step _ (FairUnfoldNext o ys []) =+ return $ Skip (FairUnfoldInit o ys)++ step gst (FairUnfoldNext o ys ((b,st):ls)) = do+ r <- step2 (adaptState gst) st+ case r of+ Yield c s ->+ f b c >>= \x ->+ return $ Yield x (FairUnfoldNext o (ys . ((b, s) :)) ls)+ Skip s -> return $ Skip (FairUnfoldNext o ys ((b,s) : ls))+ Stop -> return $ Skip (FairUnfoldNext o ys ls)++ step _ (FairUnfoldDrain ys []) =+ case ys [] of+ [] -> return Stop+ xs -> return $ Skip (FairUnfoldDrain id xs)++ step gst (FairUnfoldDrain ys ((b,st):ls)) = do+ r <- step2 (adaptState gst) st+ case r of+ Yield c s ->+ f b c >>= \x ->+ return $ Yield x (FairUnfoldDrain (ys . ((b,s) :)) ls)+ Skip s -> return $ Skip (FairUnfoldDrain ys ((b,s) : ls))+ Stop -> return $ Skip (FairUnfoldDrain ys ls)++{-# INLINE_NORMAL crossApplySnd #-}+crossApplySnd :: Functor f => Stream f a -> Stream f b -> Stream f b+crossApplySnd (Stream stepa statea) (Stream stepb stateb) =+ Stream step (Left statea)++ where++ {-# INLINE_LATE step #-}+ step gst (Left st) =+ fmap+ (\case+ Yield _ s -> Skip (Right (s, stateb))+ Skip s -> Skip (Left s)+ Stop -> Stop)+ (stepa (adaptState gst) st)+ step gst (Right (ostate, st)) =+ fmap+ (\case+ Yield b s -> Yield b (Right (ostate, s))+ Skip s -> Skip (Right (ostate, s))+ Stop -> Skip (Left ostate))+ (stepb gst st)++{-# INLINE_NORMAL crossApplyFst #-}+crossApplyFst :: Functor f => Stream f a -> Stream f b -> Stream f a+crossApplyFst (Stream stepa statea) (Stream stepb stateb) =+ Stream step (Left statea)++ where++ {-# INLINE_LATE step #-}+ step gst (Left st) =+ fmap+ (\case+ Yield b s -> Skip (Right (s, stateb, b))+ Skip s -> Skip (Left s)+ Stop -> Stop)+ (stepa gst st)+ step gst (Right (ostate, st, b)) =+ fmap+ (\case+ Yield _ s -> Yield b (Right (ostate, s, b))+ Skip s -> Skip (Right (ostate, s, b))+ Stop -> Skip (Left ostate))+ (stepb (adaptState gst) st)++{-+instance Applicative f => Applicative (Stream f) where+ {-# INLINE pure #-}+ pure = fromPure++ {-# INLINE (<*>) #-}+ (<*>) = crossApply++ {-# INLINE liftA2 #-}+ liftA2 f x = (<*>) (fmap f x)++ {-# INLINE (*>) #-}+ (*>) = crossApplySnd++ {-# INLINE (<*) #-}+ (<*) = crossApplyFst+-}++-- XXX We can use @Stream Identity b@ as the second stream to avoid running+-- effects multiple times. Or it could be an array or an unfold i.e.+-- unfoldCross.++-- |+-- Definition:+--+-- >>> crossWith f m1 m2 = fmap f m1 `Stream.crossApply` m2+--+-- Note that the second stream is evaluated multiple times.+--+-- Also see "Streamly.Data.Unfold.crossWith" for fast fusible static cross+-- product option.+--+{-# INLINE crossWith #-}+crossWith :: Monad m => (a -> b -> c) -> Stream m a -> Stream m b -> Stream m c+crossWith f m1 m2 = fmap f m1 `crossApply` m2++-- | Like 'crossWith' but interleaves the outer and inner loops fairly. See+-- 'fairConcatFor' for more details.+--+{-# INLINE fairCrossWith #-}+fairCrossWith :: Monad m =>+ (a -> b -> c) -> Stream m a -> Stream m b -> Stream m c+fairCrossWith f = fairCrossWithM (\a b -> return $ f a b)++-- | Given a @Stream m a@ and @Stream m b@ generate a stream with all possible+-- combinations of the tuple @(a, b)@.+--+-- Definition:+--+-- >>> cross = Stream.crossWith (,)+--+-- The second stream is evaluated multiple times. If that is not desired it can+-- be cached in an 'Data.Array.Array' and then generated from the array before+-- calling this function. Caching may also improve performance if the stream is+-- expensive to evaluate.+--+-- Time: O(m x n)+--+-- /Pre-release/+{-# INLINE cross #-}+cross :: Monad m => Stream m a -> Stream m b -> Stream m (a, b)+cross = crossWith (,)++-- | Like 'cross' but interleaves the outer and inner loops fairly. See+-- 'fairConcatFor' for more details.+{-# INLINE fairCross #-}+fairCross :: Monad m => Stream m a -> Stream m b -> Stream m (a, b)+fairCross = fairCrossWith (,)++-- crossWith/cross should ideally use Stream m b as the first stream, because+-- we are transforming Stream m a using that. We provide loop with arguments+-- flipped.++-- crossMap or crossInner?++-- | Loop the supplied stream (first argument) around each element of the input+-- stream (second argument) generating tuples. This is an argument flipped+-- version of 'cross'.+{-# INLINE loop #-}+loop :: Monad m => Stream m b -> Stream m a -> Stream m (a, b)+loop = crossWith (\b a -> (a,b))++-- | Loop by unfold. Unfold a value into a stream and nest it with the input+-- stream. This is much faster than 'loop' due to stream fusion.+{-# INLINE loopBy #-}+loopBy :: Monad m => Unfold m x b -> x -> Stream m a -> Stream m (a, b)+loopBy u x s =+ let u1 = Unfold.lmap snd u+ u2 = Unfold.map (first fst) (Unfold.carry u1)+ in unfoldEach u2 $ fmap (, x) s++------------------------------------------------------------------------------+-- Combine N Streams - unfoldEach+------------------------------------------------------------------------------++{-# ANN type ConcatMapUState Fuse #-}+data ConcatMapUState o i =+ ConcatMapUOuter o+ | ConcatMapUInner o i++-- | @unfoldEach 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'.+--+-- 'unfoldEach' is equivalent in expressive power to 'concatMap'. However,+-- using it as concatMap — by lifting a function 'f :: a -> Stream m b' into+-- an 'Unfold' — results in the same degraded performance as 'concatMap':++-- | 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.+--+-- >>> concatMap f = Stream.unfoldEach (Unfold.lmap f Unfold.fromStream)+--+-- Here is an example of a two level nested loop much faster than+-- 'concatMap' based nesting.+--+-- >>> :{+-- outerLoop =+-- flip Stream.mapM (Stream.fromList [1,2,3]) $ \x -> do+-- liftIO $ putStrLn (show x)+-- return x+-- innerUnfold = Unfold.carry $ Unfold.lmap (const [4,5,6]) Unfold.fromList+-- innerLoop =+-- flip Unfold.mapM innerUnfold $ \(x, y) -> do+-- when (x == 1) $ liftIO $ putStrLn (show y)+-- pure $ (x, y)+-- :}+--+-- >>> Stream.toList $ Stream.unfoldEach innerLoop outerLoop+-- 1+-- 4+-- 5+-- 6+-- 2+-- 3+-- [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]+--+{-# INLINE_NORMAL unfoldEach #-}+unfoldEach, unfoldMany :: Monad m => Unfold m a b -> Stream m a -> Stream m b+unfoldEach (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)++RENAME(unfoldMany,unfoldEach)++-- XXX unfoldEach generates faster code than unfoldCross with+-- everything unboxed i.e. 0 allocations.++-- | Generates a cross product of two streams and then unfolds each tuple.+--+-- A two level nested loop much faster than 'concatMap' based nesting.+--+-- >>> :{+-- outerLoop =+-- flip Stream.mapM (Stream.fromList [1,2,3]) $ \x -> do+-- liftIO $ putStrLn (show x)+-- return x+-- innerLoop =+-- flip Stream.mapM (Stream.fromList [4,5,6]) $ \y -> do+-- -- liftIO $ putStrLn (show y)+-- return y+-- innerUnfold =+-- flip Unfold.mapM Unfold.identity $ \(x,y) -> do+-- when (x == 1) $ liftIO $ putStrLn (show y)+-- pure $ (x, y)+-- :}+--+-- >>> Stream.toList $ Stream.unfoldCross innerUnfold outerLoop innerLoop+-- 1+-- 4+-- 5+-- 6+-- 2+-- 3+-- [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]+--+-- Note: 'unfoldEach' may generate faster code, so use that when possible.+-- Also see "Streamly.Data.Unfold.cross" for fast fusible static cross product+-- option.+--+{-# INLINE unfoldCross #-}+unfoldCross :: Monad m =>+ Unfold m (a,b) c -> Stream m a -> Stream m b -> Stream m c+unfoldCross unf m1 m2 = unfoldEach unf $ crossWith (,) m1 m2++------------------------------------------------------------------------------+-- Combine N Streams - concatMap+------------------------------------------------------------------------------++-- Adapted from the vector package.++-- If we are iterating over n elements flat vs m nesting of n^{1/m} elements.+-- The total iterations in the nesting case will be+-- let x = n^{1/m} in {x + x^2 + x^3 + ... + x^m} = x * {(x^m - 1)/(x-1)}+-- i.e. (n-1)/(1-1/x) which is not very high. However, the decision tree in the+-- state of concatMap is traversed from root to leaf for each element, and the+-- state updates also updates the tree from leaf to root upon yielding an+-- element which makes the allocations as well as CPU performance quadratic.++-- | 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.+--+-- See 'unfoldEach' for a faster alternative.+--+{-# INLINE_NORMAL concatMapM #-}+concatMapM :: Monad m => (a -> m (Stream m b)) -> Stream m a -> Stream m b+concatMapM f (Stream step state) = Stream step' (Left state)+ where+ {-# INLINE_LATE step' #-}+ step' gst (Left st) = do+ r <- step (adaptState gst) st+ case r of+ Yield a s -> do+ b_stream <- f a+ return $ Skip (Right (b_stream, s))+ Skip s -> return $ Skip (Left s)+ Stop -> return Stop++ -- XXX using the pattern synonym "Stream" causes a major performance issue+ -- here even if the synonym does not include an adaptState call. Need to+ -- find out why. Is that something to be fixed in GHC?+ step' gst (Right (UnStream inner_step inner_st, st)) = do+ r <- inner_step (adaptState gst) inner_st+ case r of+ Yield b inner_s ->+ return $ Yield b (Right (Stream inner_step inner_s, st))+ Skip inner_s ->+ return $ Skip (Right (Stream inner_step inner_s, st))+ Stop -> return $ Skip (Left st)++-- | Map a stream producing function on each element of the stream and then+-- flatten the results into a single stream.+--+-- >>> concatMap f = Stream.concat . fmap f+-- >>> concatMap f = Stream.concatMapM (return . f)+-- >>> concatMap f = Stream.unfoldEach (Unfold.lmap f Unfold.fromStream)+--+-- See argument flipped version 'concatFor' for more detailed documentation.+--+-- NOTE: We recommend using 'unfoldEach' or 'unfoldCross' instead of+-- 'concatMap' especially in performance critical code. 'unfoldEach' is much+-- faster than 'concatMap' and matches its expressive power in terms of+-- generating dependent inner streams, there is one important distinction+-- though: the nesting structure when using 'unfoldEach' is fixed statically in+-- the code. In contrast, 'concatMap' allows dynamic and arbitrary nesting+-- through monadic composition. This means that deeply nested or+-- programmatically determined levels of nesting are easier to express and+-- compose with 'concatMap', though often at the cost of performance and+-- fusion.+--+{-# INLINE concatMap #-}+concatMap :: Monad m => (a -> Stream m b) -> Stream m a -> Stream m b+concatMap f = concatMapM (return . f)++-- XXX Add smap/sfor (mapAccum) as a more ergonomic substitute for scan, not+-- exposing the state in the output stream.+-- XXX add sconcatMap/sconcatFor as stateful alternatives.++-- | Map a stream generating function on each element of a stream and+-- concatenate the results. This is the same as the bind function of the monad+-- instance. It is just a flipped 'concatMap' but more convenient to use for+-- nested use case, feels like an imperative @for@ loop. It is in fact+-- equivalent to @concat . for@.+--+-- >>> concatFor = flip Stream.concatMap+--+-- A concatenating @for@ loop:+--+-- >>> :{+-- Stream.toList $+-- Stream.concatFor (Stream.fromList [1,2,3]) $ \x ->+-- Stream.fromPure x+-- :}+-- [1,2,3]+--+-- Use 'unfoldEach' instead of 'concatFor' where possible, unfoldEach is much+-- faster due to fusion.+--+-- Nested concatenating @for@ loops:+--+-- >>> :{+-- Stream.toList $+-- Stream.concatFor (Stream.fromList [1,2,3]) $ \x ->+-- Stream.concatFor (Stream.fromList [4,5,6]) $ \y ->+-- Stream.fromPure (x, y)+-- :}+-- [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]+--+-- If total iterations are kept the same, each increase in the nesting level+-- increases the cost by roughly 2 times.+--+-- For significantly faster multi-level nesting, prefer using the better+-- fusible, applicative-like 'crossWith' over 'concatFor' where possible.+--+-- 'concatFor' is monad-like: it allows expressing dependencies between the+-- outer and the inner loops of the nesting, it means that the stream generated+-- by the inner loop is dynamically governed by the outer loop. This expressive+-- power comes at a significant performance cost.+--+-- NOTE: We recommend using 'unfoldEach' or 'unfoldCross' instead of+-- 'concatFor' especially in performance critical code. 'unfoldEach' is much+-- faster than 'concatFor' and matches its expressive power in terms of+-- generating dependent inner streams, there is one important distinction+-- though: the nesting structure when using 'unfoldEach' is fixed statically in+-- the code. In contrast, 'concatFor' allows dynamic and arbitrary nesting+-- through monadic composition. This means that deeply nested or+-- programmatically determined levels of nesting are easier to express and+-- compose with 'concatFor', though often at the cost of performance and+-- fusion.+--+--+{-# INLINE concatFor #-}+concatFor :: Monad m => Stream m a -> (a -> Stream m b) -> Stream m b+concatFor = flip concatMap++-- NOTE: A monad instance can do with just concatMap because we lift an effect+-- explicitly using liftIO and then bind/applicative concats it. Not sure if it+-- gets fused and the overhead of additional concat removed. However, for+-- explicit use concatForM is more ergonomic as we do not need to lift and+-- concat, we can just use the do notation in the underlying monad..++-- | Like 'concatFor' but maps an effectful function. It allows conveniently+-- mixing monadic effects with streams.+--+-- >>> import Control.Monad.IO.Class (liftIO)+-- >>> :{+-- Stream.toList $+-- Stream.concatForM (Stream.fromList [1,2,3]) $ \x -> do+-- liftIO $ putStrLn (show x)+-- pure $ Stream.fromPure x+-- :}+-- 1+-- 2+-- 3+-- [1,2,3]+--+-- Nested concatentating @for@ loops:+--+-- >>> :{+-- Stream.toList $+-- Stream.concatForM (Stream.fromList [1,2,3]) $ \x -> do+-- liftIO $ putStrLn (show x)+-- pure $ Stream.concatForM (Stream.fromList [4,5,6]) $ \y -> do+-- when (x == 1) $ liftIO $ putStrLn (show y)+-- pure $ Stream.fromPure (x, y)+-- :}+-- 1+-- 4+-- 5+-- 6+-- 2+-- 3+-- [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]+--+{-# INLINE concatForM #-}+concatForM :: Monad m => Stream m a -> (a -> m (Stream m b)) -> Stream m b+concatForM = flip concatMapM++-- | Flatten a stream of streams to a single stream.+--+-- >>> concat = Stream.concatMap id+--+-- /Pre-release/+{-# INLINE concat #-}+concat :: Monad m => Stream m (Stream m a) -> Stream m a+concat = concatMap id++-- >>> concatEffect = Stream.concat . lift -- requires (MonadTrans t)+-- >>> concatEffect = join . lift -- requires (MonadTrans t, Monad (Stream m))++-- | Flatten a stream generated by an effect i.e. concat the effect monad with+-- the stream monad.+--+-- >>> concatEffect = Stream.concat . Stream.fromEffect+-- >>> concatEffect eff = Stream.concatMapM (\() -> eff) (Stream.fromPure ())+--+-- See also: 'concat', 'sequence'+--+{-# INLINE concatEffect #-}+concatEffect :: Monad m => m (Stream m a) -> Stream m a+concatEffect generator = concatMapM (\() -> generator) (fromPure ())++{-+-- 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+ {-# INLINE return #-}+ return = pure++ {-# INLINE (>>=) #-}+ (>>=) = flip concatMap++ {-# INLINE (>>) #-}+ (>>) = (*>)+-}++------------------------------------------------------------------------------+-- Traversing a tree top down+------------------------------------------------------------------------------++-- Next stream is to be generated by the return value of the previous stream. A+-- general intuitive way of doing that could be to use an appending monad+-- instance for streams where the result of the previous stream is used to+-- generate the next one. In the first pass we can just emit the values in the+-- stream and keep building a buffered list/stream, once done we can then+-- process the buffered stream.++-- | Generate a stream from an initial state, scan and concat the stream,+-- generate a stream again from the final state of the previous scan and repeat+-- the process.+{-# INLINE_NORMAL concatIterateScan #-}+concatIterateScan :: Monad m =>+ (b -> a -> m b)+ -> (b -> m (Maybe (b, Stream m a)))+ -> b+ -> Stream m a+concatIterateScan scanner generate initial = Stream step (Left initial)++ where++ {-# INLINE_LATE step #-}+ step _ (Left acc) = do+ r <- generate acc+ case r of+ Nothing -> return Stop+ Just v -> return $ Skip (Right v)++ step gst (Right (st, UnStream inner_step inner_st)) = do+ r <- inner_step (adaptState gst) inner_st+ case r of+ Yield b inner_s -> do+ acc <- scanner st b+ return $ Yield b (Right (acc, Stream inner_step inner_s))+ Skip inner_s ->+ return $ Skip (Right (st, Stream inner_step inner_s))+ Stop -> return $ Skip (Left st)++-- Note: The iterate function returns a Maybe Stream instead of returning a nil+-- stream for indicating a leaf node. This is to optimize so that we do not+-- have to store any state. This makes the stored state proportional to the+-- number of non-leaf nodes rather than total number of nodes.++-- | Same as 'concatIterateBfs' except that the traversal of the last+-- element on a level is emitted first and then going backwards up to the first+-- element (reversed ordering). This may be slightly faster than+-- 'concatIterateBfs'.+--+{-# INLINE_NORMAL altBfsConcatIterate #-}+altBfsConcatIterate, concatIterateBfsRev :: Monad m =>+ (a -> Maybe (Stream m a))+ -> Stream m a+ -> Stream m a+altBfsConcatIterate f stream = Stream step (stream, [])++ where++ {-# INLINE_LATE step #-}+ step gst (UnStream step1 st, xs) = do+ r <- step1 (adaptState gst) st+ case r of+ Yield a s -> do+ let xs1 =+ case f a of+ Nothing -> xs+ Just x -> x:xs+ return $ Yield a (Stream step1 s, xs1)+ Skip s -> return $ Skip (Stream step1 s, xs)+ Stop ->+ case xs of+ (y:ys) -> return $ Skip (y, ys)+ [] -> return Stop++RENAME(concatIterateBfsRev,altBfsConcatIterate)++-- | Similar to 'concatIterate' except that it traverses the stream in+-- breadth first style (BFS). First, all the elements in the input stream are+-- emitted, and then their traversals are emitted.+--+-- Example, list a directory tree using BFS:+--+-- >>> f = either (Just . Dir.readEitherPaths id) (const Nothing)+-- >>> input = Stream.fromEffect (Left <$> Path.fromString ".")+-- >>> ls = Stream.bfsConcatIterate f input+--+-- /Pre-release/+{-# INLINE_NORMAL bfsConcatIterate #-}+bfsConcatIterate, concatIterateBfs :: Monad m =>+ (a -> Maybe (Stream m a))+ -> Stream m a+ -> Stream m a+bfsConcatIterate f stream = Stream step (stream, [], [])++ where++ {-# INLINE_LATE step #-}+ step gst (UnStream step1 st, xs, ys) = do+ r <- step1 (adaptState gst) st+ case r of+ Yield a s -> do+ let ys1 =+ case f a of+ Nothing -> ys+ Just y -> y:ys+ return $ Yield a (Stream step1 s, xs, ys1)+ Skip s -> return $ Skip (Stream step1 s, xs, ys)+ Stop ->+ case xs of+ (x:xs1) -> return $ Skip (x, xs1, ys)+ [] ->+ case reverse ys of+ (x:xs1) -> return $ Skip (x, xs1, [])+ [] -> return Stop++RENAME(concatIterateBfs,bfsConcatIterate)++-- | Traverse the stream in depth first style (DFS). Map each element in the+-- input stream to a stream and flatten, recursively map the resulting elements+-- as well to a stream and flatten until no more streams are generated.+--+-- Example, list a directory tree using DFS:+--+-- >>> f = either (Just . Dir.readEitherPaths id) (const Nothing)+-- >>> input = Stream.fromEffect (Left <$> Path.fromString ".")+-- >>> ls = Stream.concatIterate f input+--+-- This is equivalent to using @concatIterateWith StreamK.append@.+--+-- /Pre-release/+{-# INLINE_NORMAL concatIterate #-}+concatIterate, concatIterateDfs :: Monad m =>+ (a -> Maybe (Stream m a))+ -> Stream m a+ -> Stream m a+concatIterate f stream = Stream step (stream, [])++ where++ {-# INLINE_LATE step #-}+ step gst (UnStream step1 st, xs) = do+ r <- step1 (adaptState gst) st+ case r of+ Yield a s -> do+ let st1 =+ case f a of+ Nothing -> (Stream step1 s, xs)+ Just x -> (x, Stream step1 s:xs)+ return $ Yield a st1+ Skip s -> return $ Skip (Stream step1 s, xs)+ Stop ->+ case xs of+ (y:ys) -> return $ Skip (y, ys)+ [] -> return Stop++RENAME(concatIterateDfs,concatIterate)++{-# ANN type IterateUnfoldState Fuse #-}+data IterateUnfoldState o i =+ IterateUnfoldOuter o+ | IterateUnfoldInner o i [i]++-- | Same as 'concatIterate' but more efficient due to stream fusion.+--+-- Example, list a directory tree using DFS:+--+-- >>> f = Unfold.either (Dir.eitherReaderPaths id) Unfold.nil+-- >>> input = Stream.fromEffect (Left <$> Path.fromString ".")+-- >>> ls = Stream.unfoldIterate f input+--+-- /Pre-release/+{-# INLINE_NORMAL unfoldIterate #-}+unfoldIterate, unfoldIterateDfs :: Monad m =>+ Unfold m a a+ -> Stream m a+ -> Stream m a+unfoldIterate (Unfold istep inject) (Stream ostep ost) =+ Stream step (IterateUnfoldOuter ost)++ where++ {-# INLINE_LATE step #-}+ step gst (IterateUnfoldOuter o) = do+ r <- ostep (adaptState gst) o+ case r of+ Yield a s -> do+ i <- inject a+ i `seq` return (Yield a (IterateUnfoldInner s i []))+ Skip s -> return $ Skip (IterateUnfoldOuter s)+ Stop -> return Stop++ step _ (IterateUnfoldInner o i ii) = do+ r <- istep i+ case r of+ Yield x s -> do+ i1 <- inject x+ i1 `seq` return $ Yield x (IterateUnfoldInner o i1 (s:ii))+ Skip s -> return $ Skip (IterateUnfoldInner o s ii)+ Stop ->+ case ii of+ (y:ys) -> return $ Skip (IterateUnfoldInner o y ys)+ [] -> return $ Skip (IterateUnfoldOuter o)++RENAME(unfoldIterateDfs,unfoldIterate)++{-# ANN type IterateUnfoldBFSRevState Fuse #-}+data IterateUnfoldBFSRevState o i =+ IterateUnfoldBFSRevOuter o [i]+ | IterateUnfoldBFSRevInner i [i]++-- | Like 'bfsUnfoldIterate' but processes the children in reverse order,+-- therefore, may be slightly faster.+--+-- /Pre-release/+{-# INLINE_NORMAL altBfsUnfoldIterate #-}+altBfsUnfoldIterate, unfoldIterateBfsRev :: Monad m =>+ Unfold m a a+ -> Stream m a+ -> Stream m a+altBfsUnfoldIterate (Unfold istep inject) (Stream ostep ost) =+ Stream step (IterateUnfoldBFSRevOuter ost [])++ where++ {-# INLINE_LATE step #-}+ step gst (IterateUnfoldBFSRevOuter o ii) = do+ r <- ostep (adaptState gst) o+ case r of+ Yield a s -> do+ i <- inject a+ i `seq` return (Yield a (IterateUnfoldBFSRevOuter s (i:ii)))+ Skip s -> return $ Skip (IterateUnfoldBFSRevOuter s ii)+ Stop ->+ case ii of+ (y:ys) -> return $ Skip (IterateUnfoldBFSRevInner y ys)+ [] -> return Stop++ step _ (IterateUnfoldBFSRevInner i ii) = do+ r <- istep i+ case r of+ Yield x s -> do+ i1 <- inject x+ i1 `seq` return $ Yield x (IterateUnfoldBFSRevInner s (i1:ii))+ Skip s -> return $ Skip (IterateUnfoldBFSRevInner s ii)+ Stop ->+ case ii of+ (y:ys) -> return $ Skip (IterateUnfoldBFSRevInner y ys)+ [] -> return Stop++RENAME(unfoldIterateBfsRev,altBfsUnfoldIterate)++{-# ANN type IterateUnfoldBFSState Fuse #-}+data IterateUnfoldBFSState o i =+ IterateUnfoldBFSOuter o [i]+ | IterateUnfoldBFSInner i [i] [i]++-- | Like 'unfoldIterate' but uses breadth first style traversal.+--+-- /Pre-release/+{-# INLINE_NORMAL bfsUnfoldIterate #-}+bfsUnfoldIterate, unfoldIterateBfs :: Monad m =>+ Unfold m a a+ -> Stream m a+ -> Stream m a+bfsUnfoldIterate (Unfold istep inject) (Stream ostep ost) =+ Stream step (IterateUnfoldBFSOuter ost [])++ where++ {-# INLINE_LATE step #-}+ step gst (IterateUnfoldBFSOuter o rii) = do+ r <- ostep (adaptState gst) o+ case r of+ Yield a s -> do+ i <- inject a+ i `seq` return (Yield a (IterateUnfoldBFSOuter s (i:rii)))+ Skip s -> return $ Skip (IterateUnfoldBFSOuter s rii)+ Stop ->+ case reverse rii of+ (y:ys) -> return $ Skip (IterateUnfoldBFSInner y ys [])+ [] -> return Stop++ step _ (IterateUnfoldBFSInner i ii rii) = do+ r <- istep i+ case r of+ Yield x s -> do+ i1 <- inject x+ i1 `seq` return $ Yield x (IterateUnfoldBFSInner s ii (i1:rii))+ Skip s -> return $ Skip (IterateUnfoldBFSInner s ii rii)+ Stop ->+ case ii of+ (y:ys) -> return $ Skip (IterateUnfoldBFSInner y ys rii)+ [] ->+ case reverse rii of+ (y:ys) -> return $ Skip (IterateUnfoldBFSInner y ys [])+ [] -> return Stop++RENAME(unfoldIterateBfs,bfsUnfoldIterate)++------------------------------------------------------------------------------+-- Folding a tree bottom up+------------------------------------------------------------------------------++-- | Binary BFS style reduce, folds a level entirely using the supplied fold+-- function, collecting the outputs as next level of the tree, then repeats the+-- same process on the next level. The last elements of a previously folded+-- level are folded first.+{-# INLINE_NORMAL bfsReduceIterate #-}+bfsReduceIterate, reduceIterateBfs :: Monad m =>+ (a -> a -> m a) -> Stream m a -> m (Maybe a)+bfsReduceIterate f (Stream step state) = go SPEC state [] Nothing++ where++ go _ st xs Nothing = do+ r <- step defState st+ case r of+ Yield x1 s -> go SPEC s xs (Just x1)+ Skip s -> go SPEC s xs Nothing+ Stop ->+ case xs of+ [] -> return Nothing+ _ -> goBuf SPEC xs []+ go _ st xs (Just x1) = do+ r2 <- step defState st+ case r2 of+ Yield x2 s -> do+ x <- f x1 x2+ go SPEC s (x:xs) Nothing+ Skip s -> go SPEC s xs (Just x1)+ Stop ->+ case xs of+ [] -> return (Just x1)+ _ -> goBuf SPEC (x1:xs) []++ goBuf _ [] ys = goBuf SPEC ys []+ goBuf _ [x1] ys = do+ case ys of+ [] -> return (Just x1)+ (x2:xs) -> do+ y <- f x1 x2+ goBuf SPEC xs [y]+ goBuf _ (x1:x2:xs) ys = do+ y <- f x1 x2+ goBuf SPEC xs (y:ys)++RENAME(reduceIterateBfs,bfsReduceIterate)++-- | N-Ary BFS style iterative fold, if the input stream finished before the+-- fold then it returns Left otherwise Right. If the fold returns Left we+-- terminate.+--+-- /Unimplemented/+bfsFoldIterate ::+ Fold m a (Either a a) -> Stream m a -> m (Maybe a)+bfsFoldIterate = undefined++------------------------------------------------------------------------------+-- Grouping/Splitting+------------------------------------------------------------------------------++-- s = stream state, fs = fold state+{-# ANN type FoldManyPost Fuse #-}+#if __GLASGOW_HASKELL__ >= 810+type FoldManyPost :: Type -> Type -> Type -> Type -> Type+#endif+data FoldManyPost s fs b a+ = FoldManyPostStart s+ | FoldManyPostLoop s fs+ | FoldManyPostYield b (FoldManyPost s fs b a)+ | FoldManyPostDone++-- Note that using a closed fold e.g. @Fold.take 0@, would result in an+-- infinite stream without consuming the input.+--+-- We can call foldManyPost as foldMany0, but we should probably remove it.+-- Like foldMany0, "scan" should ideally be "scan0" always resulting in a+-- non-empty stream, and "postscan" should be called just "scan" because it is+-- much more common. But those names cannot be changed now.++-- | Like 'foldMany' but evaluates the fold even if the fold did not receive+-- any input, therefore, always results in a non-empty output even on an empty+-- stream (default result of the fold).+--+-- Example, empty stream, compare with 'foldMany':+--+-- >>> f = Fold.take 2 Fold.toList+-- >>> fmany = Stream.fold Fold.toList . Stream.foldManyPost f+-- >>> fmany $ Stream.fromList []+-- [[]]+--+-- Example, last empty fold is included, compare with 'foldMany':+--+-- >>> fmany $ Stream.fromList [1..4]+-- [[1,2],[3,4],[]]+--+-- Example, last fold non-empty, same as 'foldMany':+--+-- >>> fmany $ Stream.fromList [1..5]+-- [[1,2],[3,4],[5]]+--+-- /Pre-release/+--+{-# INLINE_NORMAL foldManyPost #-}+foldManyPost :: Monad m => Fold m a b -> Stream m a -> Stream m b+foldManyPost (Fold fstep initial _ final) (Stream step state) =+ Stream step' (FoldManyPostStart state)++ where++ {-# INLINE consume #-}+ consume x s fs = do+ res <- fstep fs x+ return+ $ Skip+ $ case res of+ FL.Done b -> FoldManyPostYield b (FoldManyPostStart s)+ FL.Partial ps -> FoldManyPostLoop s ps++ {-# INLINE_LATE step' #-}+ step' _ (FoldManyPostStart st) = do+ r <- initial+ return+ $ Skip+ $ case r of+ FL.Done b -> FoldManyPostYield b (FoldManyPostStart st)+ FL.Partial fs -> FoldManyPostLoop st fs+ step' gst (FoldManyPostLoop st fs) = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> consume x s fs+ Skip s -> return $ Skip (FoldManyPostLoop s fs)+ Stop -> do+ b <- final fs+ return $ Skip (FoldManyPostYield b FoldManyPostDone)+ step' _ (FoldManyPostYield b next) = return $ Yield b next+ step' _ FoldManyPostDone = return Stop++-- | Apply fold f1 infix separated by fold f2.+--+-- /Unimplemented/+{-# INLINE_NORMAL foldManySepBy #-}+foldManySepBy :: -- Monad m =>+ Fold m a b -> Fold m a b -> Stream m a -> Stream m b+foldManySepBy _f1 _f2 = undefined++{-# ANN type FoldMany Fuse #-}+#if __GLASGOW_HASKELL__ >= 810+type FoldMany :: Type -> Type -> Type -> Type -> Type+#endif+data FoldMany s fs b a+ = FoldManyStart s+ | FoldManyFirst fs s+ | FoldManyLoop s fs+ | FoldManyYield b (FoldMany s fs b a)+ | FoldManyDone++-- XXX Nested foldMany does not fuse.++-- | Apply a terminating 'Fold' repeatedly on a stream and emit the results in+-- the output stream. If the last fold is empty, it's result is not emitted.+-- This means if the input stream is empty the result is also an empty stream.+-- See 'foldManyPost' for an alternate behavior which always results in a+-- non-empty stream even if the input stream is empty.+--+-- Definition:+--+-- >>> foldMany f = Stream.parseMany (Parser.fromFold f)+--+-- Example, empty stream, omits the empty fold value:+--+-- >>> f = Fold.take 2 Fold.toList+-- >>> fmany = Stream.fold Fold.toList . Stream.foldMany f+-- >>> fmany $ Stream.fromList []+-- []+--+-- Example, omits the last empty fold value:+--+-- >>> fmany $ Stream.fromList [1..4]+-- [[1,2],[3,4]]+--+-- Example, last fold non-empty:+--+-- >>> fmany $ Stream.fromList [1..5]+-- [[1,2],[3,4],[5]]+--+-- Note that using a closed fold e.g. @Fold.take 0@, would result in an+-- infinite stream on a non-empty input stream.+--+{-# INLINE_NORMAL foldMany #-}+foldMany :: Monad m => Fold m a b -> Stream m a -> Stream m b+foldMany (Fold fstep initial _ final) (Stream step state) =+ Stream step' (FoldManyStart state)++ where++ {-# INLINE consume #-}+ consume x s fs = do+ res <- fstep fs x+ return+ $ Skip+ $ case res of+ FL.Done b -> FoldManyYield b (FoldManyStart s)+ FL.Partial ps -> FoldManyLoop s ps++ {-# INLINE_LATE step' #-}+ step' _ (FoldManyStart st) = do+ r <- initial+ return+ $ Skip+ $ case r of+ FL.Done b -> FoldManyYield b (FoldManyStart st)+ FL.Partial fs -> FoldManyFirst fs st+ step' gst (FoldManyFirst fs st) = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> consume x s fs+ Skip s -> return $ Skip (FoldManyFirst fs s)+ Stop -> final fs >> return Stop+ step' gst (FoldManyLoop st fs) = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> consume x s fs+ Skip s -> return $ Skip (FoldManyLoop s fs)+ Stop -> do+ b <- final fs+ return $ Skip (FoldManyYield b FoldManyDone)+ step' _ (FoldManyYield b next) = return $ Yield b next+ step' _ FoldManyDone = return Stop++-- | Group the input stream into groups of @n@ elements each and then fold each+-- group using the provided fold function.+--+-- Definition:+--+-- >>> groupsOf n f = Stream.foldMany (Fold.take n f)+--+-- Usage:+--+-- >>> Stream.toList $ Stream.groupsOf 2 Fold.toList (Stream.enumerateFromTo 1 10)+-- [[1,2],[3,4],[5,6],[7,8],[9,10]]+--+-- This can be considered as an n-fold version of 'take' where we apply+-- 'take' repeatedly on the leftover stream until the stream exhausts.+--+{-# INLINE groupsOf #-}+groupsOf :: Monad m => Int -> Fold m a b -> Stream m a -> Stream m b+groupsOf n f = foldMany (FL.take n f)++-- Keep the argument order consistent with refoldIterateM.++-- | Like 'foldMany' but for the 'Refold' type. The supplied action is used as+-- the initial value for each refold.+--+-- /Internal/+{-# INLINE_NORMAL refoldMany #-}+refoldMany :: Monad m => Refold m x a b -> m x -> Stream m a -> Stream m b+refoldMany (Refold fstep inject extract) action (Stream step state) =+ Stream step' (FoldManyStart state)++ where++ {-# INLINE consume #-}+ consume x s fs = do+ res <- fstep fs x+ return+ $ Skip+ $ case res of+ FL.Done b -> FoldManyYield b (FoldManyStart s)+ FL.Partial ps -> FoldManyLoop s ps++ {-# INLINE_LATE step' #-}+ step' _ (FoldManyStart st) = do+ r <- action >>= inject+ return+ $ Skip+ $ case r of+ FL.Done b -> FoldManyYield b (FoldManyStart st)+ FL.Partial fs -> FoldManyFirst fs st+ step' gst (FoldManyFirst fs st) = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> consume x s fs+ Skip s -> return $ Skip (FoldManyFirst fs s)+ Stop -> return Stop+ step' gst (FoldManyLoop st fs) = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> consume x s fs+ Skip s -> return $ Skip (FoldManyLoop s fs)+ Stop -> do+ b <- extract fs+ return $ Skip (FoldManyYield b FoldManyDone)+ step' _ (FoldManyYield b next) = return $ Yield b next+ step' _ FoldManyDone = return Stop++{-# ANN type CIterState Fuse #-}+data CIterState s f fs b+ = CIterInit s f+ | CIterConsume s fs+ | CIterYield b (CIterState s f fs b)+ | CIterStop++-- | Like 'foldIterateM' but using the 'Refold' type instead. This could be+-- much more efficient due to stream fusion.+--+-- /Internal/+{-# INLINE_NORMAL refoldIterateM #-}+refoldIterateM ::+ Monad m => Refold m b a b -> m b -> Stream m a -> Stream m b+refoldIterateM (Refold fstep finject fextract) initial (Stream step state) =+ Stream stepOuter (CIterInit state initial)++ where++ {-# INLINE iterStep #-}+ iterStep st action = do+ res <- action+ return+ $ Skip+ $ case res of+ FL.Partial fs -> CIterConsume st fs+ FL.Done fb -> CIterYield fb $ CIterInit st (return fb)++ {-# INLINE_LATE stepOuter #-}+ stepOuter _ (CIterInit st action) = do+ iterStep st (action >>= finject)+ stepOuter gst (CIterConsume st fs) = do+ r <- step (adaptState gst) st+ case r of+ Yield x s -> iterStep s (fstep fs x)+ Skip s -> return $ Skip $ CIterConsume s fs+ Stop -> do+ b <- fextract fs+ return $ Skip $ CIterYield b CIterStop+ stepOuter _ (CIterYield a next) = return $ Yield a next+ stepOuter _ CIterStop = return Stop++-- | The refold @indexerBy f n@ takes an (index, len) tuple as initial input,+-- and returns @(index + len + n, b)@ as output where @b@ is the output of the+-- fold.+{-# INLINE indexerBy #-}+indexerBy :: Monad m =>+ Fold m a Int -> Int -> Refold m (Int, Int) a (Int, Int)+indexerBy (Fold step1 initial1 extract1 _final) n =+ Refold step inject extract++ where++ inject (i, len) = do+ r <- initial1+ return $ case r of+ FL.Partial s -> FL.Partial $ Tuple' (i + len + n) s+ FL.Done l -> FL.Done (i, l)++ step (Tuple' i s) x = do+ r <- step1 s x+ return $ case r of+ FL.Partial s1 -> FL.Partial $ Tuple' i s1+ FL.Done len -> FL.Done (i, len)++ extract (Tuple' i s) = (i,) <$> extract1 s++-- | Like 'splitEndBy_' but generates a stream of (index, len) tuples marking+-- the places where the predicate matches in the stream.+--+-- >>> Stream.toList $ Stream.indexEndBy_ (== '/') $ Stream.fromList "/home/harendra"+-- [(0,0),(1,4),(6,8)]+--+-- /Pre-release/+{-# INLINE indexEndBy_ #-}+indexEndBy_, indexOnSuffix :: Monad m =>+ (a -> Bool) -> Stream m a -> Stream m (Int, Int)+indexEndBy_ predicate =+ refoldIterateM+ (indexerBy (FL.takeEndBy_ predicate FL.length) 1)+ (return (-1, 0))++RENAME(indexOnSuffix,indexEndBy_)++-- Alternate implementation+{-# INLINE_NORMAL _indexEndBy_ #-}+_indexEndBy_ :: Monad m => (a -> Bool) -> Stream m a -> Stream m (Int, Int)+_indexEndBy_ p (Stream step1 state1) = Stream step (Just (state1, 0, 0))++ where++ {-# INLINE_LATE step #-}+ step gst (Just (st, i, len)) = i `seq` len `seq` do+ r <- step1 (adaptState gst) st+ return+ $ case r of+ Yield x s ->+ if p x+ then Yield (i, len + 1) (Just (s, i + len + 1, 0))+ else Skip (Just (s, i, len + 1))+ Skip s -> Skip (Just (s, i, len))+ Stop -> if len == 0 then Stop else Yield (i, len) Nothing+ step _ Nothing = return Stop++{-# DEPRECATED sliceOnSuffix "Please use indexEndBy_ instead." #-}+sliceOnSuffix :: Monad m => (a -> Bool) -> Stream m a -> Stream m (Int, Int)+sliceOnSuffix = indexEndBy_++-- | Like 'splitEndBy' but generates a stream of (index, len) tuples marking+-- the places where the predicate matches in the stream.+--+-- >>> Stream.toList $ Stream.indexEndBy (== '/') $ Stream.fromList "/home/harendra"+-- [(0,1),(1,5),(6,8)]+--+-- /Pre-release/+{-# INLINE indexEndBy #-}+indexEndBy :: Monad m =>+ (a -> Bool) -> Stream m a -> Stream m (Int, Int)+indexEndBy predicate =+ refoldIterateM+ (indexerBy (FL.takeEndBy predicate FL.length) 0)+ (return (0, 0))++------------------------------------------------------------------------------+-- Stream with a cross product style monad instance+------------------------------------------------------------------------------++-- XXX Nested performs better than the StreamK.Nested when nesting two+-- loops, however, StreamK.Nested seems to be better for more than two nestings,+-- need to do more perf investigation.++-- | A newtype wrapper for the 'Stream' type with a cross product style monad+-- instance.+--+-- A 'Monad' bind behaves like a @for@ loop:+--+-- >>> :{+-- Stream.fold Fold.toList $ Stream.unNested $ do+-- x <- Stream.Nested $ Stream.fromList [1,2]+-- -- Perform the following actions for each x in the stream+-- return x+-- :}+-- [1,2]+--+-- Nested monad binds behave like nested @for@ loops:+--+-- >>> :{+-- Stream.fold Fold.toList $ Stream.unNested $ do+-- x <- Stream.Nested $ Stream.fromList [1,2]+-- y <- Stream.Nested $ Stream.fromList [3,4]+-- -- Perform the following actions for each x, for each y+-- return (x, y)+-- :}+-- [(1,3),(1,4),(2,3),(2,4)]+--+newtype Nested m a = Nested {unNested :: Stream m a}+ deriving (Functor, Foldable)++{-# DEPRECATED CrossStream "Use Nested instead." #-}+type CrossStream = Nested++{-# DEPRECATED mkCross "Use Nested instead." #-}+{-# INLINE mkCross #-}+mkCross :: Stream m a -> Nested m a+mkCross = Nested++{-# INLINE unCross #-}+unCross :: Nested m a -> Stream m a+unCross = unNested++-- Pure (Identity monad) stream instances+deriving instance IsList (Nested Identity a)+deriving instance (a ~ Char) => IsString (Nested Identity a)+deriving instance Eq a => Eq (Nested Identity a)+deriving instance Ord a => Ord (Nested Identity a)++-- Do not use automatic derivation for this to show as "fromList" rather than+-- "fromList Identity".+instance Show a => Show (Nested Identity a) where+ {-# INLINE show #-}+ show (Nested xs) = show xs++instance Read a => Read (Nested Identity a) where+ {-# INLINE readPrec #-}+ readPrec = fmap Nested readPrec++------------------------------------------------------------------------------+-- Applicative+------------------------------------------------------------------------------++-- Note: we need to define all the typeclass operations because we want to+-- INLINE them.+instance Monad m => Applicative (Nested m) where+ {-# INLINE pure #-}+ pure x = Nested (fromPure x)++ {-# INLINE (<*>) #-}+ (Nested s1) <*> (Nested s2) =+ Nested (crossApply s1 s2)++ {-# INLINE liftA2 #-}+ liftA2 f x = (<*>) (fmap f x)++ {-# INLINE (*>) #-}+ (Nested s1) *> (Nested s2) =+ Nested (crossApplySnd s1 s2)++ {-# INLINE (<*) #-}+ (Nested s1) <* (Nested s2) =+ Nested (crossApplyFst s1 s2)++------------------------------------------------------------------------------+-- Monad+------------------------------------------------------------------------------++instance Monad m => Monad (Nested m) where+ return = pure++ -- Benchmarks better with StreamD bind and pure:+ -- toList, filterAllout, *>, *<, >> (~2x)+ --++ -- Benchmarks better with CPS bind and pure:+ -- Prime sieve (25x)+ -- n binds, breakAfterSome, filterAllIn, state transformer (~2x)+ --+ {-# INLINE (>>=) #-}+ (>>=) (Nested m) f = Nested (concatMap (unNested . f) m)++ {-# INLINE (>>) #-}+ (>>) = (*>)++------------------------------------------------------------------------------+-- Transformers+------------------------------------------------------------------------------++instance (MonadIO m) => MonadIO (Nested m) where+ liftIO x = Nested (fromEffect $ liftIO x)++instance MonadTrans Nested where+ {-# INLINE lift #-}+ lift x = Nested (fromEffect x)++instance (MonadThrow m) => MonadThrow (Nested m) where+ throwM = lift . throwM++------------------------------------------------------------------------------+-- Utilities+------------------------------------------------------------------------------++-- | Inlined definition. Without the inline "serially/parser/take" benchmark+-- degrades and parseMany does not fuse. Even using "inline" at the callsite+-- does not help.+{-# INLINE splitAt #-}+splitAt :: String -> Int -> [a] -> ([a],[a])+splitAt desc n ls+ | n < 0 = seekOver n+ | n == 0 = ([], ls)+ | otherwise = splitAt' n ls++ where++ splitAt' :: Int -> [a] -> ([a], [a])+ splitAt' 0 [] = ([], [])+ splitAt' m [] = seekUnder n m+ splitAt' 1 (x:xs) = ([x], xs)+ splitAt' m (x:xs) = (x:xs', xs'')++ where++ (xs', xs'') = splitAt' (m - 1) xs++ seekOver x =+ error $ desc ++ ": bug in parser, seeking ["+ ++ show (negate x)+ ++ "] elements in future"++ seekUnder x y =+ error $ desc ++ ": bug in parser, backtracking ["+ ++ show x+ ++ "] elements. Goes ["+ ++ show y+ ++ "] elements beyond backtrack buffer"
− src/Streamly/Internal/Data/Stream/Zip.hs
@@ -1,91 +0,0 @@-{-# LANGUAGE UndecidableInstances #-}---- |--- Module : Streamly.Internal.Data.Stream.Zip--- Copyright : (c) 2017 Composewell Technologies------ License : BSD3--- Maintainer : streamly@composewell.com--- Stability : experimental--- Portability : GHC------ To run examples in this module:------ >>> import qualified Streamly.Data.Fold as Fold--- >>> import qualified Streamly.Data.Stream as Stream--- >>> import qualified Streamly.Internal.Data.Stream.Zip as Stream----module Streamly.Internal.Data.Stream.Zip- (- ZipStream (..)- , ZipSerialM- , ZipSerial- )-where--import Data.Functor.Identity (Identity(..))-import GHC.Exts (IsList(..), IsString(..))-import Streamly.Internal.Data.Stream.Type (Stream)-import Text.Read- ( Lexeme(Ident), lexP, parens, prec, readPrec, readListPrec- , readListPrecDefault)--import qualified Streamly.Internal.Data.Stream.Bottom as Stream-import qualified Streamly.Internal.Data.Stream.Generate as Stream---- $setup--- >>> import qualified Streamly.Data.Fold as Fold--- >>> import qualified Streamly.Data.Stream as Stream--- >>> import qualified Streamly.Internal.Data.Stream.Zip as Stream----------------------------------------------------------------------------------- Serially Zipping Streams----------------------------------------------------------------------------------- | For 'ZipStream':------ @--- (<>) = 'Streamly.Data.Stream.append'--- (\<*>) = 'Streamly.Data.Stream.zipWith' id--- @------ Applicative evaluates the streams being zipped serially:------ >>> s1 = Stream.ZipStream $ Stream.fromFoldable [1, 2]--- >>> s2 = Stream.ZipStream $ Stream.fromFoldable [3, 4]--- >>> s3 = Stream.ZipStream $ Stream.fromFoldable [5, 6]--- >>> s = (,,) <$> s1 <*> s2 <*> s3--- >>> Stream.fold Fold.toList (Stream.unZipStream s)--- [(1,3,5),(2,4,6)]----newtype ZipStream m a = ZipStream {unZipStream :: Stream m a}- deriving (Functor, Semigroup, Monoid)--deriving instance IsList (ZipStream Identity a)-deriving instance (a ~ Char) => IsString (ZipStream Identity a)-deriving instance Eq a => Eq (ZipStream Identity a)-deriving instance Ord a => Ord (ZipStream Identity a)-deriving instance (Foldable m, Monad m) => Foldable (ZipStream m)-deriving instance Traversable (ZipStream Identity)--instance Show a => Show (ZipStream Identity a) where- showsPrec p dl = showParen (p > 10) $- showString "fromList " . shows (toList dl)--instance Read a => Read (ZipStream Identity a) where- readPrec = parens $ prec 10 $ do- Ident "fromList" <- lexP- fromList <$> readPrec- readListPrec = readListPrecDefault--type ZipSerialM = ZipStream---- | An IO stream whose applicative instance zips streams serially.----type ZipSerial = ZipSerialM IO--instance Monad m => Applicative (ZipStream m) where- pure = ZipStream . Stream.repeat-- {-# INLINE (<*>) #-}- ZipStream m1 <*> ZipStream m2 = ZipStream $ Stream.zipWith id m1 m2
+ src/Streamly/Internal/Data/StreamK.hs view
@@ -0,0 +1,1397 @@+{-# LANGUAGE CPP #-}+{- HLINT ignore "Eta reduce" -}+-- |+-- Module : Streamly.Internal.Data.StreamK+-- Copyright : (c) 2017 Composewell Technologies+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+module Streamly.Internal.Data.StreamK+ (+ -- * Setup+ -- | To execute the code examples provided in this module in ghci, please+ -- run the following commands first.+ --+ -- $setup++ module Streamly.Internal.Data.StreamK.Type+ -- * Transformer+ , module Streamly.Internal.Data.StreamK.Transformer++ -- * From containers+ , fromStream++ -- * Specialized Generation+ , repeatM+ , replicate+ , replicateM+ , fromIndices+ , fromIndicesM+ , iterate+ , iterateM++ -- * Elimination+ -- ** General Folds+ , foldr1+ , fold+ , foldBreak+ , foldEither+ , foldConcat+ , ParserK.toParserK -- XXX move the code to this module+ , parseDBreak+ , parseD+ , parseBreak+ , parseBreakPos+ , parse+ , parsePos++ -- ** Specialized Folds+ , head+ , elem+ , notElem+ , all+ , any+ , last+ , minimum+ , minimumBy+ , maximum+ , maximumBy+ , findIndices+ , lookup+ , findM+ , find+ , (!!)++ -- ** To Containers+ , toList+ , toStream++ -- ** Map and Fold+ , mapM_++ -- * Transformation+ -- ** By folding (scans)+ , scanl'+ , scanlx'++ -- ** Filtering+ , filter+ , take+ , takeWhile+ , drop+ , dropWhile++ -- ** Mapping+ , mapM+ , sequence++ -- ** Inserting+ , intersperseM+ , intersperse+ , insertBy++ -- ** Deleting+ , deleteBy++ -- ** Reordering+ , sortBy+ , sortOn++ -- ** Map and Filter+ , mapMaybe++ -- ** Zipping+ , zipWith+ , zipWithM++ -- ** Merging+ , mergeBy+ , mergeByM++ -- ** Transformation comprehensions+ , the++ -- ** Transforming Inner Monad+ , morphInner++ -- * Exceptions+ , handle++ -- * Resource Management+ , bracketIO++ -- * Deprecated+ , hoist+ , parseBreakChunks+ , parseChunks+ , parseBreakChunksGeneric+ , parseChunksGeneric+ )+where++#include "ArrayMacros.h"+#include "inline.hs"+#include "assert.hs"+#include "deprecation.h"++import Control.Exception (mask_, Exception)+import Control.Monad (void, join)+import Control.Monad.Catch (MonadCatch)+import Control.Monad.IO.Class (MonadIO(..))+import Data.Ord (comparing)+import GHC.Types (SPEC(..))+import Streamly.Internal.Data.Array.Type (Array(..))+import Streamly.Internal.Data.Fold.Type (Fold(..))+import Streamly.Internal.Data.IOFinalizer (newIOFinalizer, runIOFinalizer)+import Streamly.Internal.Data.ParserK.Type (ParserK)+import Streamly.Internal.Data.Producer.Type (Producer(..))+import Streamly.Internal.Data.SVar.Type (adaptState, defState)+import Streamly.Internal.Data.Unbox (Unbox)++import qualified Control.Monad.Catch as MC+import qualified Streamly.Internal.Data.Array as Array+import qualified Streamly.Internal.Data.Array.Generic as GenArr+import qualified Streamly.Internal.Data.Fold.Type as FL+import qualified Streamly.Internal.Data.Parser as Parser+import qualified Streamly.Internal.Data.ParserDrivers as Drivers+import qualified Streamly.Internal.Data.Parser.Type as PR+import qualified Streamly.Internal.Data.ParserK.Type as ParserK+import qualified Streamly.Internal.Data.Stream as Stream+import qualified Prelude++import Prelude+ hiding (Foldable(..), last, map, mapM, mapM_, repeat, sequence,+ take, filter, all, any, takeWhile, drop, dropWhile,+ notElem, head, tail, init, zipWith, lookup,+ (!!), replicate, reverse, concatMap, iterate, splitAt)+import Data.Foldable (length)+import Streamly.Internal.Data.StreamK.Type+import Streamly.Internal.Data.StreamK.Transformer+import Streamly.Internal.Data.Parser (ParseError(..), ParseErrorPos(..))++#include "DocTestDataStreamK.hs"++-- | Convert a fused 'Stream' to 'StreamK'.+--+-- For example:+--+-- >>> s1 = StreamK.fromStream $ Stream.fromList [1,2]+-- >>> s2 = StreamK.fromStream $ Stream.fromList [3,4]+-- >>> Stream.fold Fold.toList $ StreamK.toStream $ s1 `StreamK.append` s2+-- [1,2,3,4]+--+{-# INLINE fromStream #-}+fromStream :: Monad m => Stream.Stream m a -> StreamK m a+fromStream = Stream.toStreamK++-- | Convert a 'StreamK' to a fused 'Stream'.+--+{-# INLINE toStream #-}+toStream :: Applicative m => StreamK m a -> Stream.Stream m a+toStream = Stream.fromStreamK++-------------------------------------------------------------------------------+-- Generation+-------------------------------------------------------------------------------++{-+-- 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 :: (b -> Maybe (m a, b)) -> b -> StreamK m a+unfoldrA = undefined+-}++-------------------------------------------------------------------------------+-- Special generation+-------------------------------------------------------------------------------++-- |+-- >>> repeatM = StreamK.sequence . StreamK.repeat+-- >>> repeatM = fix . StreamK.consM+-- >>> repeatM = cycle1 . StreamK.fromEffect+--+-- Generate a stream by repeatedly executing a monadic action forever.+--+-- >>> :{+-- repeatAction =+-- StreamK.repeatM (threadDelay 1000000 >> print 1)+-- & StreamK.take 10+-- & StreamK.fold Fold.drain+-- :}+--+repeatM :: Monad m => m a -> StreamK m a+repeatM = repeatMWith consM++{-# INLINE replicateM #-}+replicateM :: Monad m => Int -> m a -> StreamK m a+replicateM = replicateMWith consM+{-# INLINE replicate #-}+replicate :: Int -> a -> StreamK m a+replicate n a = go n+ where+ go cnt = if cnt <= 0 then nil else a `cons` go (cnt - 1)++{-# INLINE fromIndicesM #-}+fromIndicesM :: Monad m => (Int -> m a) -> StreamK m a+fromIndicesM = fromIndicesMWith consM+{-# INLINE fromIndices #-}+fromIndices :: (Int -> a) -> StreamK m a+fromIndices gen = go 0+ where+ go n = gen n `cons` go (n + 1)++-- |+-- >>> iterate f x = x `StreamK.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.+--+-- >>> StreamK.toList $ StreamK.take 5 $ StreamK.iterate (+1) 1+-- [1,2,3,4,5]+--+{-# INLINE iterate #-}+iterate :: (a -> a) -> a -> StreamK m a+iterate step = go+ where+ go !s = cons s (go (step s))++-- |+-- >>> iterateM f m = m >>= \a -> return a `StreamK.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.+--+-- >>> :{+-- StreamK.iterateM (\x -> print x >> return (x + 1)) (return 0)+-- & StreamK.take 3+-- & StreamK.toList+-- :}+-- 0+-- 1+-- [0,1,2]+--+{-# INLINE iterateM #-}+iterateM :: Monad m => (a -> m a) -> m a -> StreamK m a+iterateM = iterateMWith consM++-------------------------------------------------------------------------------+-- Elimination by Folding+-------------------------------------------------------------------------------++{-# INLINE foldr1 #-}+foldr1 :: Monad m => (a -> a -> a) -> StreamK 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++-- | Fold a stream using the supplied left 'Fold' and reducing the resulting+-- expression strictly at each step. The behavior is similar to 'foldl''. A+-- 'Fold' can terminate early without consuming the full stream. See the+-- documentation of individual 'Fold's for termination behavior.+--+-- Definitions:+--+-- >>> fold f = fmap fst . StreamK.foldBreak f+-- >>> fold f = StreamK.parseD (Parser.fromFold f)+--+-- Example:+--+-- >>> StreamK.fold Fold.sum $ StreamK.fromStream $ Stream.enumerateFromTo 1 100+-- 5050+--+{-# INLINABLE fold #-}+fold :: Monad m => FL.Fold m a b -> StreamK m a -> m b+fold (FL.Fold step begin _ final) m = do+ res <- begin+ case res of+ FL.Partial fs -> go fs m+ FL.Done fb -> return fb++ where+ go !acc m1 =+ let stop = final acc+ single a = step acc a+ >>= \case+ FL.Partial s -> final s+ FL.Done b1 -> return b1+ yieldk a r = step acc a+ >>= \case+ FL.Partial s -> go s r+ FL.Done b1 -> return b1+ in foldStream defState yieldk single stop m1++-- | Fold resulting in either breaking the stream or continuation of the fold.+-- Instead of supplying the input stream in one go we can run the fold multiple+-- times, each time supplying the next segment of the input stream. If the fold+-- has not yet finished it returns a fold that can be run again otherwise it+-- returns the fold result and the residual stream.+--+-- /Internal/+{-# INLINE foldEither #-}+foldEither :: Monad m =>+ Fold m a b -> StreamK m a -> m (Either (Fold m a b) (b, StreamK m a))+foldEither (FL.Fold step begin done final) m = do+ res <- begin+ case res of+ FL.Partial fs -> go fs m+ FL.Done fb -> return $ Right (fb, m)++ where++ go !acc m1 =+ let stop =+ let f = Fold step (return $ FL.Partial acc) done final+ in return $ Left f+ single a =+ step acc a+ >>= \case+ FL.Partial s ->+ let f = Fold step (return $ FL.Partial s) done final+ in return $ Left f+ FL.Done b1 -> return $ Right (b1, nil)+ yieldk a r =+ step acc a+ >>= \case+ FL.Partial s -> go s r+ FL.Done b1 -> return $ Right (b1, r)+ in foldStream defState yieldk single stop m1++-- | Like 'fold' but also returns the remaining stream. The resulting stream+-- would be 'StreamK.nil' if the stream finished before the fold.+--+{-# INLINE foldBreak #-}+foldBreak :: Monad m => Fold m a b -> StreamK m a -> m (b, StreamK m a)+foldBreak fld strm = do+ r <- foldEither fld strm+ case r of+ Right res -> return res+ Left (Fold _ initial _ final) -> do+ res <- initial+ case res of+ FL.Done _ -> error "foldBreak: unreachable state"+ FL.Partial s -> do+ b <- final s+ return (b, nil)++-- XXX Array folds can be implemented using this.+-- foldContainers? Specialized to foldArrays.++-- | Generate streams from individual elements of a stream and fold the+-- concatenation of those streams using the supplied fold. Return the result of+-- the fold and residual stream.+--+-- For example, this can be used to efficiently fold an Array Word8 stream+-- using Word8 folds.+--+-- /Internal/+{-# INLINE foldConcat #-}+foldConcat :: Monad m =>+ Producer m a b -> Fold m b c -> StreamK m a -> m (c, StreamK m a)+foldConcat+ (Producer pstep pinject pextract)+ (Fold fstep begin _ final)+ stream = do++ res <- begin+ case res of+ FL.Partial fs -> go fs stream+ FL.Done fb -> return (fb, stream)++ where++ go !acc m1 = do+ let stop = do+ r <- final acc+ return (r, nil)+ single a = do+ st <- pinject a+ res <- go1 SPEC acc st+ case res of+ Left fs -> do+ r <- final fs+ return (r, nil)+ Right (b, s) -> do+ x <- pextract s+ return (b, fromPure x)+ yieldk a r = do+ st <- pinject a+ res <- go1 SPEC acc st+ case res of+ Left fs -> go fs r+ Right (b, s) -> do+ x <- pextract s+ return (b, x `cons` r)+ in foldStream defState yieldk single stop m1++ {-# INLINE go1 #-}+ go1 !_ !fs st = do+ r <- pstep st+ case r of+ Stream.Yield x s -> do+ res <- fstep fs x+ case res of+ FL.Done b -> return $ Right (b, s)+ FL.Partial fs1 -> go1 SPEC fs1 s+ Stream.Skip s -> go1 SPEC fs s+ Stream.Stop -> return $ Left fs++------------------------------------------------------------------------------+-- Specialized folds+------------------------------------------------------------------------------++{-# INLINE head #-}+head :: Monad m => StreamK 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 elem #-}+elem :: (Monad m, Eq a) => a -> StreamK m a -> m Bool+elem e = go+ 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 :: (Monad m, Eq a) => a -> StreamK m a -> m Bool+notElem e = go+ 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 :: Monad m => (a -> Bool) -> StreamK m a -> m Bool+all p = go+ 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 :: Monad m => (a -> Bool) -> StreamK m a -> m Bool+any p = go+ 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 :: Monad m => StreamK m a -> m (Maybe a)+last = foldlx' (\_ y -> Just y) Nothing id++{-# INLINE minimum #-}+minimum :: (Monad m, Ord a) => StreamK m a -> m (Maybe a)+minimum = go Nothing+ 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+ :: (Monad m)+ => (a -> a -> Ordering) -> StreamK m a -> m (Maybe a)+minimumBy cmp = go Nothing+ 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 :: (Monad m, Ord a) => StreamK m a -> m (Maybe a)+maximum = go Nothing+ 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 :: Monad m => (a -> a -> Ordering) -> StreamK m a -> m (Maybe a)+maximumBy cmp = go Nothing+ 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 (!!) #-}+(!!) :: Monad m => StreamK 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 :: (Monad m, Eq a) => a -> StreamK m (a, b) -> m (Maybe b)+lookup e = go+ 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 :: Monad m => (a -> m Bool) -> StreamK m a -> m (Maybe a)+findM p = go+ 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 :: Monad m => (a -> Bool) -> StreamK m a -> m (Maybe a)+find p = findM (return . p)++{-# INLINE findIndices #-}+findIndices :: (a -> Bool) -> StreamK m a -> StreamK 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_ :: Monad m => (a -> m b) -> StreamK m a -> m ()+mapM_ f = go+ 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++{-# INLINE mapM #-}+mapM :: Monad m => (a -> m b) -> StreamK m a -> StreamK m b+mapM = mapMWith consM++------------------------------------------------------------------------------+-- Converting folds+------------------------------------------------------------------------------++{-# INLINABLE toList #-}+toList :: Monad m => StreamK m a -> m [a]+toList = foldr (:) []++-- Based on suggestions by David Feuer and Pranay Sashank+{-# INLINE morphInner #-}+morphInner, hoist :: (Monad m, Monad n)+ => (forall x. m x -> n x) -> StreamK m a -> StreamK n a+morphInner 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+RENAME(hoist,morphInner)++-------------------------------------------------------------------------------+-- Transformation by folding (Scans)+-------------------------------------------------------------------------------++{-# INLINE scanlx' #-}+scanlx' :: (x -> a -> x) -> x -> (x -> b) -> StreamK m a -> StreamK 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' :: (b -> a -> b) -> b -> StreamK m a -> StreamK m b+scanl' step begin = scanlx' step begin id++-------------------------------------------------------------------------------+-- Filtering+-------------------------------------------------------------------------------++{-# INLINE filter #-}+filter :: (a -> Bool) -> StreamK m a -> StreamK m a+filter p = go+ 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 :: Int -> StreamK m a -> StreamK m a+take = go+ 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 :: (a -> Bool) -> StreamK m a -> StreamK m a+takeWhile p = go+ 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 :: Int -> StreamK m a -> StreamK m a+drop n m = unShare (go n 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 :: (a -> Bool) -> StreamK m a -> StreamK m a+dropWhile p = go+ 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 :: Monad m => StreamK m (m a) -> StreamK m a+sequence = go+ where+ go m1 = mkStream $ \st yld sng stp ->+ let single ma = ma >>= sng+ yieldk ma r = foldStreamShared st yld sng stp $ ma `consM` go r+ in foldStream (adaptState st) yieldk single stp m1++-------------------------------------------------------------------------------+-- Inserting+-------------------------------------------------------------------------------++{-# INLINE intersperseM #-}+intersperseM :: Monad m => m a -> StreamK m a -> StreamK m a+intersperseM a = prependingStart+ where+ prependingStart m1 = mkStream $ \st yld sng stp ->+ let yieldk i x =+ foldStreamShared st yld sng stp $ return i `consM` 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 `consM` fromPure i+ yieldk i x =+ foldStreamShared+ st yld sng stp $ a `consM` return i `consM` go x+ in foldStream st yieldk single stp m2++{-# INLINE intersperse #-}+intersperse :: Monad m => a -> StreamK m a -> StreamK m a+intersperse a = intersperseM (return a)++{-# INLINE insertBy #-}+insertBy :: (a -> a -> Ordering) -> a -> StreamK m a -> StreamK m a+insertBy cmp x = go+ where+ go m1 = mkStream $ \st yld _ _ ->+ let single a = case cmp x a of+ GT -> yld a (fromPure x)+ _ -> yld x (fromPure 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 :: (a -> a -> Bool) -> a -> StreamK m a -> StreamK m a+deleteBy eq x = go+ 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++-------------------------------------------------------------------------------+-- Map and Filter+-------------------------------------------------------------------------------++{-# INLINE mapMaybe #-}+mapMaybe :: (a -> Maybe b) -> StreamK m a -> StreamK m b+mapMaybe f = go+ where+ go m1 = mkStream $ \st yld sng stp ->+ let single a = maybe stp sng (f a)+ 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++-------------------------------------------------------------------------------+-- Exception Handling+-------------------------------------------------------------------------------++-- | Like Streamly.Data.Stream.'Streamly.Data.Stream.handle' but with one+-- significant difference, this function observes exceptions from the consumer+-- of the stream as well.+--+-- You can also convert 'StreamK' to 'Stream' and use exception handling from+-- 'Stream' module:+--+-- >>> handle f s = StreamK.fromStream $ Stream.handle (\e -> StreamK.toStream (f e)) (StreamK.toStream s)+--+{-# INLINABLE handle #-}+handle :: (MonadCatch m, Exception e)+ => (e -> m (StreamK m a)) -> StreamK m a -> StreamK m a+handle f stream = go stream++ where++ go m1 = mkStream $ \st yld sng stp ->+ let yieldk a r = yld a $ go r+ in do+ res <- MC.try (foldStream (adaptState st) yieldk sng stp m1)+ case res of+ Right r -> return r+ Left e -> do+ r <- f e+ foldStream (adaptState st) yld sng stp r++-------------------------------------------------------------------------------+-- Resource Management+-------------------------------------------------------------------------------++-- If we are folding the stream and we do not drain the entire stream (e.g. if+-- the fold terminates before the stream) then the finalizer will run on GC.+--+-- XXX To implement a prompt cleanup, we will have to yield a cleanup function+-- via the yield continuation. A chain of cleanup functions can be built and+-- the entire chain can be invoked when the stream ends voluntarily or if+-- someone decides to abandon the stream.++-- | Like Streamly.Data.Stream.'Streamly.Data.Stream.bracketIO' but with one+-- significant difference, this function observes exceptions from the consumer+-- of the stream as well. Therefore, it cleans up the resource promptly when+-- the consumer encounters an exception.+--+-- You can also convert 'StreamK' to 'Stream' and use resource handling from+-- 'Stream' module:+--+-- >>> bracketIO bef aft bet = StreamK.fromStream $ Stream.bracketIO bef aft (StreamK.toStream . bet)+--+{-# INLINABLE bracketIO #-}+bracketIO :: (MonadIO m, MonadCatch m)+ => IO b -> (b -> IO c) -> (b -> StreamK m a) -> StreamK m a+bracketIO bef aft bet =+ concatEffect $ do+ (r, ref) <- liftIO $ mask_ $ do+ r <- bef+ ref <- newIOFinalizer (aft r)+ return (r, ref)+ return $ go ref (bet r)++ where++ go ref m1 = mkStream $ \st yld sng stp ->+ let+ -- We can discard exceptions on continuations to make it equivalent+ -- to StreamD, but it seems like a desirable behavior.+ stop = liftIO (runIOFinalizer ref) >> stp+ single a = liftIO (runIOFinalizer ref) >> sng a+ yieldk a r = yld a $ go ref r+ in do+ -- Do not call the finalizer twice if it has already been+ -- called via stop continuation and stop continuation itself+ -- generated an exception. runIOFinalizer takes care of that.+ res <- MC.try (foldStream (adaptState st) yieldk single stop m1)+ case res of+ Right r -> return r+ Left (e :: MC.SomeException) ->+ liftIO (runIOFinalizer ref) >> MC.throwM e++------------------------------------------------------------------------------+-- Serial Zipping+------------------------------------------------------------------------------++-- | Zipping of @n@ streams can be performed by combining the streams pair+-- wise using 'mergeMapWith' with O(n * log n) time complexity. If used+-- with 'concatMapWith' it will have O(n^2) performance.+{-# INLINE zipWith #-}+zipWith :: Monad m => (a -> b -> c) -> StreamK m a -> StreamK m b -> StreamK m c+zipWith f = zipWithM (\a b -> return (f a b))++{-# INLINE zipWithM #-}+zipWithM :: Monad m =>+ (a -> b -> m c) -> StreamK m a -> StreamK m b -> StreamK m c+zipWithM f = go++ where++ go mx my = mkStream $ \st yld sng stp -> do+ let merge a ra =+ let single2 b = f a b >>= sng+ yield2 b rb = f a b >>= \x -> yld x (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 :: Monad m =>+ (a -> a -> m Ordering) -> StreamK m a -> StreamK m a -> StreamK m a+mergeByM cmp = go++ where++ go mx my = mkStream $ \st yld sng stp -> do+ let stop = foldStream st yld sng stp my+ single x = foldStream st yld sng stp (goX0 x my)+ yield x rx = foldStream st yld sng stp (goX x rx my)+ foldStream st yield single stop mx++ goX0 x my = mkStream $ \st yld sng _ -> do+ let stop = sng x+ single y = do+ r <- cmp x y+ case r of+ GT -> yld y (fromPure x)+ _ -> yld x (fromPure y)+ yield y ry = do+ r <- cmp x y+ case r of+ GT -> yld y (goX0 x ry)+ _ -> yld x (y `cons` ry)+ in foldStream st yield single stop my++ goX x mx my = mkStream $ \st yld _ _ -> do+ let stop = yld x mx+ single y = do+ r <- cmp x y+ case r of+ GT -> yld y (x `cons` mx)+ _ -> yld x (goY0 mx y)+ yield y ry = do+ r <- cmp x y+ case r of+ GT -> yld y (goX x mx ry)+ _ -> yld x (goY mx y ry)+ in foldStream st yield single stop my++ goY0 mx y = mkStream $ \st yld sng _ -> do+ let stop = sng y+ single x = do+ r <- cmp x y+ case r of+ GT -> yld y (fromPure x)+ _ -> yld x (fromPure y)+ yield x rx = do+ r <- cmp x y+ case r of+ GT -> yld y (x `cons` rx)+ _ -> yld x (goY0 rx y)+ in foldStream st yield single stop mx++ goY mx y my = mkStream $ \st yld _ _ -> do+ let stop = yld y my+ single x = do+ r <- cmp x y+ case r of+ GT -> yld y (goX0 x my)+ _ -> yld x (y `cons` my)+ yield x rx = do+ r <- cmp x y+ case r of+ GT -> yld y (goX x rx my)+ _ -> yld x (goY rx y my)+ in foldStream st yield single stop mx++-- | Merging of @n@ streams can be performed by combining the streams pair+-- wise using 'mergeMapWith' to give O(n * log n) time complexity. If used+-- with 'concatMapWith' it will have O(n^2) performance.+--+{-# INLINE mergeBy #-}+mergeBy :: (a -> a -> Ordering) -> StreamK m a -> StreamK m a -> StreamK m a+-- XXX GHC: This has slightly worse performance than replacing "r <- cmp x y"+-- with "let r = cmp x y" in the monadic version. The definition below is+-- exactly the same as mergeByM except this change.+-- mergeBy cmp = mergeByM (\a b -> return $ cmp a b)+mergeBy cmp = go++ where++ go mx my = mkStream $ \st yld sng stp -> do+ let stop = foldStream st yld sng stp my+ single x = foldStream st yld sng stp (goX0 x my)+ yield x rx = foldStream st yld sng stp (goX x rx my)+ foldStream st yield single stop mx++ goX0 x my = mkStream $ \st yld sng _ -> do+ let stop = sng x+ single y = do+ case cmp x y of+ GT -> yld y (fromPure x)+ _ -> yld x (fromPure y)+ yield y ry = do+ case cmp x y of+ GT -> yld y (goX0 x ry)+ _ -> yld x (y `cons` ry)+ in foldStream st yield single stop my++ goX x mx my = mkStream $ \st yld _ _ -> do+ let stop = yld x mx+ single y = do+ case cmp x y of+ GT -> yld y (x `cons` mx)+ _ -> yld x (goY0 mx y)+ yield y ry = do+ case cmp x y of+ GT -> yld y (goX x mx ry)+ _ -> yld x (goY mx y ry)+ in foldStream st yield single stop my++ goY0 mx y = mkStream $ \st yld sng _ -> do+ let stop = sng y+ single x = do+ case cmp x y of+ GT -> yld y (fromPure x)+ _ -> yld x (fromPure y)+ yield x rx = do+ case cmp x y of+ GT -> yld y (x `cons` rx)+ _ -> yld x (goY0 rx y)+ in foldStream st yield single stop mx++ goY mx y my = mkStream $ \st yld _ _ -> do+ let stop = yld y my+ single x = do+ case cmp x y of+ GT -> yld y (goX0 x my)+ _ -> yld x (y `cons` my)+ yield x rx = do+ case cmp x y of+ GT -> yld y (goX x rx my)+ _ -> yld x (goY rx y my)+ in foldStream st yield single stop mx++------------------------------------------------------------------------------+-- Transformation comprehensions+------------------------------------------------------------------------------++{-# INLINE the #-}+the :: (Eq a, Monad m) => StreamK 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 :: StreamK m a -> StreamK m a -> StreamK 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++------------------------------------------------------------------------------+-- MonadError+------------------------------------------------------------------------------++{-+-- XXX handle and test cross thread state transfer+withCatchError+ :: MonadError e m+ => StreamK m a -> (e -> StreamK m a) -> StreamK 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+-}++-------------------------------------------------------------------------------+-- Parsing+-------------------------------------------------------------------------------++-- | Run a 'Parser' over a stream and return rest of the Stream.+{-# INLINE_NORMAL parseDBreak #-}+parseDBreak+ :: Monad m+ => PR.Parser a m b+ -> StreamK m a+ -> m (Either ParseErrorPos b, StreamK m a)+parseDBreak (PR.Parser pstep initial extract) stream = do+ res <- initial+ case res of+ PR.IPartial s -> goStream stream [] s 0+ PR.IDone b -> return (Right b, stream)+ PR.IError err -> return (Left (ParseErrorPos 0 err), stream)++ where++ {-# INLINE splitAt #-}+ splitAt = Stream.splitAt "Data.StreamK.parseDBreak"++ -- "buf" contains last few items in the stream that we may have to+ -- backtrack to.+ --+ -- XXX currently we are using a dumb list based approach for backtracking+ -- buffer. This can be replaced by a sliding/ring buffer using Data.Array.+ -- That will allow us more efficient random back and forth movement.+ goStream st buf !pst i =+ let stop = do+ r <- extract pst+ case r of+ PR.FError err -> do+ let src = Prelude.reverse buf+ return (Left (ParseErrorPos i err), fromList src)+ PR.FDone m b -> do+ let n = (- m)+ assertM(n <= length buf)+ let src0 = Prelude.take n buf+ src = Prelude.reverse src0+ return (Right b, fromList src)+ PR.FContinue 0 s -> goStream nil buf s i+ PR.FContinue m s -> do+ let n = (- m)+ assertM(n <= length buf)+ let (src0, buf1) = splitAt n buf+ src = Prelude.reverse src0+ goBuf nil buf1 src s (i + m)+ single x = yieldk x nil+ yieldk x r = do+ res <- pstep pst x+ case res of+ PR.SPartial 1 s -> goStream r [] s (i + 1)+ PR.SPartial m s -> do+ let n = 1 - m+ assertM(n <= length (x:buf))+ let src0 = Prelude.take n (x:buf)+ src = Prelude.reverse src0+ goBuf r [] src s (i + m)+ PR.SContinue 1 s -> goStream r (x:buf) s (i + 1)+ PR.SContinue m s -> do+ let n = 1 - m+ assertM(n <= length (x:buf))+ let (src0, buf1) = splitAt n (x:buf)+ src = Prelude.reverse src0+ goBuf r buf1 src s (i + m)+ PR.SDone 1 b -> return (Right b, r)+ PR.SDone m b -> do+ let n = 1 - m+ assertM(n <= length (x:buf))+ let src0 = Prelude.take n (x:buf)+ src = Prelude.reverse src0+ return (Right b, append (fromList src) r)+ PR.SError err -> do+ let src = Prelude.reverse (x:buf)+ return (Left (ParseErrorPos (i + 1) err), append (fromList src) r)+ in foldStream defState yieldk single stop st++ goBuf st buf [] !pst i = goStream st buf pst i+ goBuf st buf (x:xs) !pst i = do+ pRes <- pstep pst x+ case pRes of+ PR.SPartial 1 s -> goBuf st [] xs s (i + 1)+ PR.SPartial m s -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let src0 = Prelude.take n (x:buf)+ src = Prelude.reverse src0 ++ xs+ goBuf st [] src s (i + m)+ PR.SContinue 1 s -> goBuf st (x:buf) xs s (i + 1)+ PR.SContinue m s -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let (src0, buf1) = splitAt n (x:buf)+ src = Prelude.reverse src0 ++ xs+ goBuf st buf1 src s (i + m)+ PR.SDone m b -> do+ let n = 1 - m+ assert (n <= length (x:buf)) (return ())+ let src0 = Prelude.take n (x:buf)+ src = Prelude.reverse src0 ++ xs+ return (Right b, append (fromList src) st)+ PR.SError err -> do+ let src = Prelude.reverse buf ++ x:xs+ return (Left (ParseErrorPos (i + 1) err), append (fromList src) st)++-- Using ParserD or ParserK on StreamK may not make much difference. We should+-- perhaps use only chunked parsing on StreamK. We can always convert a stream+-- to chunks before parsing. Or just have a ParserK element parser for StreamK+-- and convert ParserD to ParserK for element parsing using StreamK.+{-# INLINE parseD #-}+parseD :: Monad m =>+ Parser.Parser a m b -> StreamK m a -> m (Either ParseErrorPos b)+parseD f = fmap fst . parseDBreak f++-------------------------------------------------------------------------------+-- ParserK Chunked+-------------------------------------------------------------------------------++-- XXX parseDBreakChunks may be faster than converting parserD to toParserK and+-- using parseBreakChunks. We can also use parseBreak as an alternative to the+-- monad instance of ParserD.++-- | Run a 'ParserK' over a chunked 'StreamK' and return the parse result and+-- the remaining Stream.+{-# DEPRECATED parseBreakChunks "Use Streamly.Data.Array.parseBreak instead" #-}+{-# INLINE_NORMAL parseBreakChunks #-}+parseBreakChunks+ :: (Monad m, Unbox a)+ => ParserK (Array a) m b+ -> StreamK m (Array a)+ -> m (Either ParseError b, StreamK m (Array a))+parseBreakChunks = Array.parseBreak++{-# DEPRECATED parseChunks "Use Streamly.Data.Array.parse instead" #-}+{-# INLINE parseChunks #-}+parseChunks :: (Monad m, Unbox a) =>+ ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b)+parseChunks = Array.parse++-------------------------------------------------------------------------------+-- ParserK Singular+-------------------------------------------------------------------------------++-- | Similar to 'parseBreak' but works on singular elements.+--+{-# INLINE parseBreak #-}+parseBreak+ :: forall m a b. Monad m+ => ParserK.ParserK a m b+ -> StreamK m a+ -> m (Either ParseError b, StreamK m a)+parseBreak = Drivers.parseBreakStreamK++-- | Like 'parseBreak' but includes stream position information in the error+-- messages.+--+{-# INLINE parseBreakPos #-}+parseBreakPos+ :: forall m a b. Monad m+ => ParserK.ParserK a m b+ -> StreamK m a+ -> m (Either ParseErrorPos b, StreamK m a)+parseBreakPos = Drivers.parseBreakStreamKPos++-- | Run a 'ParserK' over a 'StreamK'. Please use 'parseChunks' where possible,+-- for better performance.+{-# INLINE parse #-}+parse :: Monad m =>+ ParserK.ParserK a m b -> StreamK m a -> m (Either ParseError b)+parse f = fmap fst . parseBreak f++-- | Like 'parse' but includes stream position information in the error+-- messages.+--+{-# INLINE parsePos #-}+parsePos :: Monad m =>+ ParserK.ParserK a m b -> StreamK m a -> m (Either ParseErrorPos b)+parsePos f = fmap fst . parseBreakPos f++-------------------------------------------------------------------------------+-- ParserK Chunked Generic+-------------------------------------------------------------------------------++-- | Similar to 'parseBreak' but works on generic arrays+--+{-# DEPRECATED parseBreakChunksGeneric "Use Streamly.Data.Array.Generic.parseBreak" #-}+{-# INLINE_NORMAL parseBreakChunksGeneric #-}+parseBreakChunksGeneric+ :: forall m a b. Monad m+ => ParserK.ParserK (GenArr.Array a) m b+ -> StreamK m (GenArr.Array a)+ -> m (Either ParseError b, StreamK m (GenArr.Array a))+parseBreakChunksGeneric = GenArr.parseBreak++{-# DEPRECATED parseChunksGeneric "Use Streamly.Data.Array.Generic.parse" #-}+{-# INLINE parseChunksGeneric #-}+parseChunksGeneric ::+ (Monad m)+ => ParserK.ParserK (GenArr.Array a) m b+ -> StreamK m (GenArr.Array a)+ -> m (Either ParseError b)+parseChunksGeneric = GenArr.parse++-------------------------------------------------------------------------------+-- Sorting+-------------------------------------------------------------------------------++-- | Sort the input stream using a supplied comparison function.+--+-- Sorting can be achieved by simply:+--+-- >>> sortBy cmp = StreamK.mergeMapWith (StreamK.mergeBy cmp) StreamK.fromPure+--+-- However, this combinator uses a parser to first split the input stream into+-- down and up sorted segments and then merges them to optimize sorting when+-- pre-sorted sequences exist in the input stream.+--+-- /O(n) space/+--+{-# INLINE sortBy #-}+sortBy :: Monad m => (a -> a -> Ordering) -> StreamK m a -> StreamK m a+-- sortBy f = Stream.concatPairsWith (Stream.mergeBy f) Stream.fromPure+sortBy cmp =+ let p =+ Parser.groupByRollingEither+ (\x -> (< GT) . cmp x)+ FL.toStreamKRev+ FL.toStreamK+ in mergeMapWith (mergeBy cmp) id+ . Stream.toStreamK+ . Stream.catRights -- its a non-failing backtracking parser+ . Stream.parseMany (fmap (either id id) p)+ . Stream.fromStreamK++{-# INLINE sortOn #-}+sortOn :: (Monad m, Ord b) => (a -> b) -> StreamK m a -> StreamK m a+sortOn f =+ fmap snd+ . sortBy (comparing fst)+ . fmap (\x -> let y = f x in y `seq` (y, x))
+ src/Streamly/Internal/Data/StreamK/Alt.hs view
@@ -0,0 +1,244 @@+-- |+-- 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.StreamK.Alt+ (+ -- * Stream Type++ Stream+ , Step (..)++ -- * Construction+ , nil+ , cons+ , consM+ , unfoldr+ , unfoldrM+ , replicateM++ -- * Folding+ , uncons+ , foldrS++ -- * Specific Folds+ , drain+ )+where++#include "inline.hs"++-- XXX Use Cons and Nil instead of Yield and Stop?+data Step m a = Yield a (Stream m a) | Stop++newtype Stream m a = Stream (m (Step m a))++-------------------------------------------------------------------------------+-- 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 = go+ 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/StreamK/Transformer.hs view
@@ -0,0 +1,91 @@+-- |+-- Module : Streamly.Internal.Data.StreamK.Transformer+-- Copyright : (c) 2017 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+module Streamly.Internal.Data.StreamK.Transformer+ (+ foldlT+ , foldrT++ , liftInner+ , evalStateT++ , localReaderT+ )+where++import Control.Monad.Trans.Class (MonadTrans(lift))+import Control.Monad.Trans.State.Strict (StateT)+import Streamly.Internal.Data.StreamK.Type+ (StreamK(..), nil, cons, uncons, concatEffect, foldStream, mkStream)+import Control.Monad.Trans.Reader (ReaderT, local)++import qualified Control.Monad.Trans.State.Strict as State++-- | Lazy left fold to an arbitrary transformer monad.+{-# INLINE foldlT #-}+foldlT :: (Monad m, Monad (s m), MonadTrans s)+ => (s m b -> a -> s m b) -> s m b -> StreamK m a -> s m b+foldlT step = go+ where+ go acc m1 = do+ res <- lift $ uncons m1+ case res of+ Just (h, t) -> go (step acc h) t+ Nothing -> acc++-- | Right associative fold to an arbitrary transformer monad.+{-# INLINE foldrT #-}+foldrT :: (Monad m, Monad (s m), MonadTrans s)+ => (a -> s m b -> s m b) -> s m b -> StreamK m a -> s m b+foldrT step final = go+ where+ go m1 = do+ res <- lift $ uncons m1+ case res of+ Just (h, t) -> step h (go t)+ Nothing -> final++------------------------------------------------------------------------------+-- Lifting inner monad+------------------------------------------------------------------------------++{-# INLINE evalStateT #-}+evalStateT :: Monad m => m s -> StreamK (StateT s m) a -> StreamK m a+evalStateT = go++ where++ go st m1 = concatEffect $ fmap f (st >>= State.runStateT (uncons m1))++ f (res, s1) =+ case res of+ Just (h, t) -> cons h (go (return s1) t)+ Nothing -> nil++{-# INLINE liftInner #-}+liftInner :: (Monad m, MonadTrans t, Monad (t m)) =>+ StreamK m a -> StreamK (t m) a+liftInner = go++ where++ go m1 = concatEffect $ fmap f $ lift $ uncons m1++ f res =+ case res of+ Just (h, t) -> cons h (go t)+ Nothing -> nil++-- | Modify the environment of the underlying ReaderT monad.+{-# INLINABLE localReaderT #-}+localReaderT :: (r -> r) -> StreamK (ReaderT r m) a -> StreamK (ReaderT r m) a+localReaderT f m =+ mkStream $ \st yld sng stp ->+ let single = local f . sng+ yieldk a r = local f $ yld a (localReaderT f r)+ in foldStream st yieldk single (local f stp) m
+ src/Streamly/Internal/Data/StreamK/Type.hs view
@@ -0,0 +1,3126 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE TypeFamilies #-}+-- Must come after TypeFamilies, otherwise it is re-enabled.+-- MonoLocalBinds enabled by TypeFamilies causes perf regressions in general.+{-# LANGUAGE NoMonoLocalBinds #-}+{-# LANGUAGE UndecidableInstances #-}+-- |+-- Module : Streamly.Internal.Data.StreamK.Type+-- Copyright : (c) 2017 Composewell Technologies+--+-- 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.StreamK.Type+ (+ -- * StreamK type+ Stream+ , StreamK (..)++ -- * Nested type wrapper+ , Nested(..)+ , FairNested(..) -- experimental, do not release, associativity issues++ -- * foldr/build Fusion+ , mkStream+ , foldStream+ , foldStreamShared+ , foldrM+ , foldrS+ , foldrSShared+ , foldrSM+ , build+ , buildS+ , buildM+ , buildSM+ , augmentS+ , augmentSM+ , unShare++ -- * Construction+ -- ** Primitives+ , fromStopK+ , fromYieldK+ , consK+ , cons+ , (.:)+ , consM+ , consMBy+ , nil+ , nilM++ -- ** Unfolding+ , unfoldr+ , unfoldrMWith+ , unfoldrM++ -- ** From Values+ , fromEffect+ , fromPure+ , repeat+ , repeatMWith+ , replicateMWith++ -- ** From Indices+ , fromIndicesMWith++ -- ** Iteration+ , iterateMWith++ -- ** From Containers+ , fromFoldable+ , fromFoldableM+ , Streamly.Internal.Data.StreamK.Type.fromList++ -- ** Cyclic+ , mfix++ -- * Elimination+ -- ** Primitives+ , uncons++ -- ** Strict Left Folds+ , Streamly.Internal.Data.StreamK.Type.foldl'+ , foldlx'+ , foldlMx'+ , foldlM'++ -- ** Lazy Right Folds+ , Streamly.Internal.Data.StreamK.Type.foldr++ -- ** Specific Folds+ , drain+ , null+ , headNonEmpty+ , tail+ , tailNonEmpty+ , init+ , initNonEmpty++ -- * Mapping+ , map+ , mapMWith+ , mapMSerial+ , mapMAccum++ -- * Combining Two Streams+ -- ** Appending+ , conjoin+ , append++ -- ** Interleave+ , interleave+ , interleaveEndBy'+ , interleaveSepBy++ -- ** Cross Product+ , crossApplyWith+ , crossApply+ , crossApplySnd+ , crossApplyFst+ , crossWith+ , cross++ -- * Concat++ -- ** Concat Effects+ , before+ , concatEffect+ , concatMapEffect++ -- ** ConcatMap+ , concatMapWith+ , concatMap+ , bfsConcatMap+ , fairConcatMap+ , concatMapMAccum++ -- ** concatFor (bind)+ , concatFor+ , bfsConcatFor+ , fairConcatFor+ , concatForWith++ -- ** concatForM+ , concatForM+ , bfsConcatForM+ , fairConcatForM+ , concatForWithM++ -- ** Iterated concat+ , concatIterateWith+ , concatIterateLeftsWith+ , concatIterateScanWith++ -- * Merge+ , mergeMapWith+ , mergeIterateWith++ -- * Buffered Operations+ , foldlS+ , reverse++ -- * Deprecated+ , interleaveFst+ , interleaveMin+ , CrossStreamK+ , mkCross+ , unCross+ , bindWith+ )+where++#include "inline.hs"+#include "deprecation.h"++import Control.Applicative (Alternative(..))+import Control.Monad ((>=>), ap, MonadPlus(..))+import Control.Monad.Catch (MonadThrow, throwM)+import Control.Monad.Trans.Class (MonadTrans(lift))+#if !MIN_VERSION_base(4,18,0)+import Control.Applicative (liftA2)+#endif+import Control.Monad.IO.Class (MonadIO(..))+import Data.Foldable (Foldable(foldl'), fold, foldr)+import Data.Function (fix)+import Data.Functor.Identity (Identity(..))+#if __GLASGOW_HASKELL__ >= 810+import Data.Kind (Type)+#endif+import Data.Maybe (fromMaybe)+import Data.Semigroup (Endo(..))+import GHC.Exts (IsList(..), IsString(..), oneShot, inline)+import Streamly.Internal.BaseCompat ((#.))+import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)+import Streamly.Internal.Data.SVar.Type (State, adaptState, defState)+import Text.Read+ ( Lexeme(Ident), lexP, parens, prec, readPrec, readListPrec+ , readListPrecDefault)++import qualified Control.Monad.Fail as Fail+import qualified Prelude++import Prelude hiding+ (map, mapM, concatMap, foldr, repeat, null, reverse, tail, init)++#include "DocTestDataStreamK.hs"++------------------------------------------------------------------------------+-- Basic stream type+------------------------------------------------------------------------------++-- It uses stop, singleton and yield continuations equivalent to the following+-- direct style type:+--+-- @+-- data StreamK m a = Stop | Singleton a | Yield a (StreamK 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 can we replace it with a direct style type? With foldr/build fusion.+-- StreamK (m (Maybe (a, StreamK m a)))+-- XXX remove the State param.++-- | Continuation Passing Style (CPS) version of "Streamly.Data.Stream.Stream".+-- Unlike "Streamly.Data.Stream.Stream", 'StreamK' can be composed recursively+-- without affecting performance.+--+-- Semigroup instance appends two streams:+--+-- >>> (<>) = Stream.append+--+{-# DEPRECATED Stream "Please use StreamK instead." #-}+type Stream = StreamK++newtype StreamK m a =+ MkStream (forall r.+ State StreamK m a -- state+ -> (a -> StreamK m a -> m r) -- yield+ -> (a -> m r) -- singleton+ -> m r -- stop+ -> m r+ )++mkStream+ :: (forall r. State StreamK m a+ -> (a -> StreamK m a -> m r)+ -> (a -> m r)+ -> m r+ -> m r)+ -> StreamK m a+mkStream = MkStream++-- | A terminal function that has no continuation to follow.+#if __GLASGOW_HASKELL__ >= 810+type StopK :: (Type -> Type) -> Type+#endif+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.+#if __GLASGOW_HASKELL__ >= 810+type YieldK :: (Type -> Type) -> Type -> Type+#endif+type YieldK m a = forall r. (a -> m r) -> m r++_wrapM :: Monad m => m a -> YieldK m a+_wrapM m = (m >>=)++-- | Make an empty stream from a stop function.+fromStopK :: StopK m -> StreamK m a+fromStopK k = mkStream $ \_ _ _ stp -> k stp++-- | Make a singleton stream from a callback function. The callback function+-- calls the one-shot yield continuation to yield an element.+fromYieldK :: YieldK m a -> StreamK m a+fromYieldK k = mkStream $ \_ _ sng _ -> k sng++-- | Add a yield function at the head of the stream.+consK :: YieldK m a -> StreamK m a -> StreamK m a+consK k r = mkStream $ \_ yld _ _ -> k (`yld` r)++-- XXX Build a stream from a repeating callback function.++------------------------------------------------------------------------------+-- Construction+------------------------------------------------------------------------------++infixr 5 `cons`++-- faster than consM because there is no bind.++-- | A right associative prepend operation to add a pure value at the head of+-- an existing stream:+--+-- >>> s = 1 `StreamK.cons` 2 `StreamK.cons` 3 `StreamK.cons` StreamK.nil+-- >>> Stream.fold Fold.toList (StreamK.toStream s)+-- [1,2,3]+--+-- Unlike "Streamly.Data.Stream" cons StreamK cons can be used+-- recursively:+--+-- >>> repeat x = let xs = StreamK.cons x xs in xs+-- >>> fromFoldable = Prelude.foldr StreamK.cons StreamK.nil+--+-- cons is same as the following but more efficient:+--+-- >>> cons x xs = return x `StreamK.consM` xs+--+{-# INLINE_NORMAL cons #-}+cons :: a -> StreamK m a -> StreamK m a+cons a r = mkStream $ \_ yield _ _ -> yield a r++infixr 5 .:++-- | Operator equivalent of 'cons'.+--+-- @+-- > toList $ 1 .: 2 .: 3 .: nil+-- [1,2,3]+-- @+--+{-# INLINE (.:) #-}+(.:) :: a -> StreamK m a -> StreamK m a+(.:) = cons++-- | A stream that terminates without producing any output or side effect.+--+-- >>> Stream.fold Fold.toList (StreamK.toStream StreamK.nil)+-- []+--+{-# INLINE_NORMAL nil #-}+nil :: StreamK m a+nil = mkStream $ \_ _ _ stp -> stp++-- | A stream that terminates without producing any output, but produces a side+-- effect.+--+-- >>> Stream.fold Fold.toList (StreamK.toStream (StreamK.nilM (print "nil")))+-- "nil"+-- []+--+-- /Pre-release/+{-# INLINE_NORMAL nilM #-}+nilM :: Applicative m => m b -> StreamK m a+nilM m = mkStream $ \_ _ _ stp -> m *> stp++-- Create a singleton stream from a pure value.+--+-- >>> fromPure a = a `StreamK.cons` StreamK.nil+-- >>> fromPure = pure+-- >>> fromPure = StreamK.fromEffect . pure+--+{-# INLINE_NORMAL fromPure #-}+fromPure :: a -> StreamK m a+fromPure a = mkStream $ \_ _ single _ -> single a++-- Create a singleton stream from a monadic action.+--+-- >>> fromEffect m = m `StreamK.consM` StreamK.nil+--+-- >>> Stream.fold Fold.drain $ StreamK.toStream $ StreamK.fromEffect (putStrLn "hello")+-- hello+--+{-# INLINE_NORMAL fromEffect #-}+fromEffect :: Monad m => m a -> StreamK m a+fromEffect m = mkStream $ \_ _ single _ -> m >>= single++infixr 5 `consM`++-- 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.++-- | A right associative prepend operation to add an effectful value at the+-- head of an existing stream::+--+-- >>> s = putStrLn "hello" `StreamK.consM` putStrLn "world" `StreamK.consM` StreamK.nil+-- >>> Stream.fold Fold.drain (StreamK.toStream s)+-- hello+-- world+--+-- It can be used efficiently with 'Prelude.foldr':+--+-- >>> fromFoldableM = Prelude.foldr StreamK.consM StreamK.nil+--+-- Same as the following but more efficient:+--+-- >>> consM x xs = StreamK.fromEffect x `StreamK.append` xs+--+{-# INLINE consM #-}+{-# SPECIALIZE consM :: IO a -> StreamK IO a -> StreamK IO a #-}+consM :: Monad m => m a -> StreamK m a -> StreamK m a+consM m r = MkStream $ \_ yld _ _ -> m >>= (`yld` r)++-- XXX specialize to IO?+{-# INLINE consMBy #-}+consMBy :: Monad m =>+ (StreamK m a -> StreamK m a -> StreamK m a) -> m a -> StreamK m a -> StreamK m a+consMBy f m r = fromEffect 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+ :: State StreamK m a+ -> (a -> StreamK m a -> m r)+ -> (a -> m r)+ -> m r+ -> StreamK m a+ -> m r+foldStreamShared s yield single stop (MkStream k) = k s yield single stop++-- | 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+ :: State StreamK m a+ -> (a -> StreamK m a -> m r)+ -> (a -> m r)+ -> m r+ -> StreamK m a+ -> m r+foldStream s yield single stop (MkStream k) =+ k (adaptState s) yield single stop++-------------------------------------------------------------------------------+-- 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 ::+ (forall r. State StreamK m b+ -> (b -> StreamK m b -> m r)+ -> (b -> m r)+ -> m r+ -> StreamK m b+ -> m r)+ -> (a -> StreamK m b -> StreamK m b)+ -> StreamK m b+ -> StreamK m a+ -> StreamK 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 ::+ (a -> StreamK m b -> StreamK m b)+ -> StreamK m b+ -> StreamK m a+ -> StreamK 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 (fromPure x) = k x z #-}+-- {-# RULES "foldrSShared/app" [1]+-- forall ys. foldrSShared consM ys = \xs -> xs `conjoin` ys #-}++-- | Right fold to a streaming monad.+--+-- > foldrS StreamK.cons StreamK.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.+--+-- >>> input = StreamK.fromStream $ Stream.fromList [1..5]+-- >>> Stream.fold Fold.toList $ StreamK.toStream $ StreamK.foldrS StreamK.cons StreamK.nil input+-- [1,2,3,4,5]+--+-- Find if any element in the stream is 'True':+--+-- >>> step x xs = if odd x then StreamK.fromPure True else xs+-- >>> input = StreamK.fromStream (Stream.fromList (2:4:5:undefined)) :: StreamK IO Int+-- >>> Stream.fold Fold.toList $ StreamK.toStream $ StreamK.foldrS step (StreamK.fromPure False) input+-- [True]+--+-- Map (+2) on odd elements and filter out the even elements:+--+-- >>> step x xs = if odd x then (x + 2) `StreamK.cons` xs else xs+-- >>> input = StreamK.fromStream (Stream.fromList [1..5]) :: StreamK IO Int+-- >>> Stream.fold Fold.toList $ StreamK.toStream $ StreamK.foldrS step StreamK.nil input+-- [3,5,7]+--+-- /Pre-release/+{-# INLINE_NORMAL foldrS #-}+foldrS ::+ (a -> StreamK m b -> StreamK m b)+ -> StreamK m b+ -> StreamK m a+ -> StreamK 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 (fromPure 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 :: Monad m+ => (forall r. State StreamK m b+ -> (b -> StreamK m b -> m r)+ -> (b -> m r)+ -> m r+ -> StreamK m b+ -> m r)+ -> (m a -> StreamK m b -> StreamK m b)+ -> StreamK m b+ -> StreamK m a+ -> StreamK 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 :: Monad m+ => (m a -> StreamK m b -> StreamK m b)+ -> StreamK m b+ -> StreamK m a+ -> StreamK 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 (fromEffect 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 :: Monad m+ => (m a -> StreamK m b -> StreamK m b)+ -> StreamK m b+ -> StreamK m a+ -> StreamK 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 (fromEffect x) = k x z #-}+-- {-# RULES "foldrSM/app" [1]+-- forall ys. foldrSM consM ys = \xs -> xs `conjoin` ys #-}++-------------------------------------------------------------------------------+-- build+-------------------------------------------------------------------------------++{-# INLINE_NORMAL build #-}+build :: forall m a. (forall b. (a -> b -> b) -> b -> b) -> StreamK 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 ::+ ((a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a)+ -> StreamK m a+buildS g = g cons nil++{-# RULES "foldrS/buildS"+ forall k z+ (g :: (a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a).+ foldrS k z (buildS g) = g k z #-}++{-# RULES "foldrS/cons/buildS"+ forall k z x+ (g :: (a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a).+ foldrS k z (x `cons` buildS g) = k x (g k z) #-}++{-# RULES "foldrSShared/buildS"+ forall k z+ (g :: (a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a).+ foldrSShared k z (buildS g) = g k z #-}++{-# RULES "foldrSShared/cons/buildS"+ forall k z x+ (g :: (a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK 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 :: Monad m+ => ((m a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a)+ -> StreamK m a+buildSM g = g consM nil++{-# RULES "foldrSM/buildSM"+ forall k z+ (g :: (m a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a).+ foldrSM k z (buildSM g) = g k z #-}++{-# RULES "foldrSMShared/buildSM"+ forall k z+ (g :: (m a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK 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 :: Monad m+ => (forall r. (a -> StreamK m a -> m r)+ -> (a -> m r)+ -> m r+ -> m r+ )+ -> StreamK 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 sharedMWith #-}+sharedMWith :: Monad m+ => (m a -> StreamK m a -> StreamK m a)+ -> (forall r. (a -> StreamK m a -> m r)+ -> (a -> m r)+ -> m r+ -> m r+ )+ -> StreamK m a+sharedMWith cns g = mkStream $ \st yld sng stp ->+ g (\a r -> foldStreamShared st yld sng stp (return a `cns` r)) sng stp++-------------------------------------------------------------------------------+-- augment+-------------------------------------------------------------------------------++{-# INLINE_NORMAL augmentS #-}+augmentS ::+ ((a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a)+ -> StreamK m a+ -> StreamK m a+augmentS g xs = g cons xs++{-# RULES "augmentS/nil"+ forall (g :: (a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a).+ augmentS g nil = buildS g+ #-}++{-# RULES "foldrS/augmentS"+ forall k z xs+ (g :: (a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a).+ foldrS k z (augmentS g xs) = g k (foldrS k z xs)+ #-}++{-# RULES "augmentS/buildS"+ forall (g :: (a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a)+ (h :: (a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a).+ augmentS g (buildS h) = buildS (\c n -> g c (h c n))+ #-}++{-# INLINE_NORMAL augmentSM #-}+augmentSM :: Monad m =>+ ((m a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a)+ -> StreamK m a -> StreamK m a+augmentSM g xs = g consM xs++{-# RULES "augmentSM/nil"+ forall+ (g :: (m a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a).+ augmentSM g nil = buildSM g+ #-}++{-# RULES "foldrSM/augmentSM"+ forall k z xs+ (g :: (m a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a).+ foldrSM k z (augmentSM g xs) = g k (foldrSM k z xs)+ #-}++{-# RULES "augmentSM/buildSM"+ forall+ (g :: (m a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK m a)+ (h :: (m a -> StreamK m a -> StreamK m a) -> StreamK m a -> StreamK 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 :: (a -> m b -> m b) -> m b -> StreamK 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 StreamK m a+ -> (a -> StreamK m a -> m b)+ -> (a -> m b)+ -> m b+ -> StreamK m a+ -> m b)+ -> (a -> m b -> m b)+ -> m b+ -> ((a -> StreamK m a -> m b) -> (a -> m b) -> m b -> m b)+ -> m b+foldrMKWith f step acc = go+ 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 -> StreamK 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 -> StreamK m a -> m r)+ -> (a -> m r)+ -> m r+ -> m r+ )).+ foldrM step acc (sharedM g) = foldrMKWith foldStreamShared step acc g+ #-}+-}++------------------------------------------------------------------------------+-- Left fold+------------------------------------------------------------------------------++-- | 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 m a b x. Monad m+ => (x -> a -> x) -> x -> (x -> b) -> StreamK m a -> m b+foldlx' step begin done =+ foldlMx' (\x a -> return (step x a)) (return begin) (return . done)++-- | Strict left associative fold.+{-# INLINE foldl' #-}+foldl' :: Monad m => (b -> a -> b) -> b -> StreamK m a -> m b+foldl' step begin = foldlx' step begin id++-- | Like 'foldx', but with a monadic step function.+{-# INLINE foldlMx' #-}+foldlMx' :: Monad m+ => (x -> a -> m x) -> m x -> (x -> m b) -> StreamK m a -> m b+foldlMx' step begin done stream =+ -- Note: Unrolling improves the last benchmark significantly.+ let stop = begin >>= done+ single a = begin >>= \x -> step x a >>= done+ yieldk a r = begin >>= \x -> step x a >>= go r+ in foldStream defState yieldk single stop stream++ where++ go m1 !acc =+ let stop = done $! acc+ single a = step acc a >>= done+ yieldk a r = step acc a >>= go r+ in foldStream defState yieldk single stop m1++-- | Like 'foldl'' but with a monadic step function.+{-# INLINE foldlM' #-}+foldlM' :: Monad m => (b -> a -> m b) -> m b -> StreamK m a -> m b+foldlM' step begin = foldlMx' step begin return++------------------------------------------------------------------------------+-- 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, maybe 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 => StreamK 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 :: Monad m => StreamK 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++------------------------------------------------------------------------------+-- Semigroup+------------------------------------------------------------------------------++infixr 6 `append`++-- | Unlike the fused "Streamly.Data.Stream" append, StreamK append can be used+-- at scale, recursively, with linear performance:+--+-- >>> cycle xs = let ys = xs `StreamK.append` ys in ys+--+-- 'concatMapWith' 'append' (same as concatMap) flattens a stream of streams in a+-- depth-first manner i.e. it yields each stream fully and then the next and so+-- on. Given a stream of three streams:+--+-- @+-- 1. [1,2,3]+-- 2. [4,5,6]+-- 3. [7,8,9]+-- @+--+-- The resulting stream will be @[1,2,3,4,5,6,7,8,9]@.+--+-- Best used in a right associative manner.+--+{-# INLINE append #-}+append :: StreamK m a -> StreamK m a -> StreamK 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+append m1 m2 =+ 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 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 :: Monad m => StreamK m a -> StreamK m a -> StreamK m a+conjoin xs = augmentSM (\c n -> foldrSM c n xs)++instance Semigroup (StreamK m a) where+ (<>) = append++------------------------------------------------------------------------------+-- Monoid+------------------------------------------------------------------------------++instance Monoid (StreamK m a) where+ mempty = nil+ mappend = (<>)++-------------------------------------------------------------------------------+-- Functor+-------------------------------------------------------------------------------++-- IMPORTANT: This is eta expanded on purpose. This should not be eta+-- reduced. This will cause a lot of regressions, probably because of some+-- rewrite rules. Ideally don't run hlint on this file.+{-# INLINE_LATE mapFB #-}+mapFB :: forall b m a.+ (b -> StreamK m b -> StreamK m b)+ -> (a -> b)+ -> a+ -> StreamK m b+ -> StreamK m b+mapFB c f = \x ys -> c (f x) ys++{-# 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 :: (a -> b) -> StreamK m a -> StreamK 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 = c (x >>= f)++{-# 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+-}++-- 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 :: Monad m => (a -> m b) -> StreamK m a -> StreamK m b+mapMSerial f xs = buildSM (\c n -> foldrSMShared (mapMFB c f) n xs)++{-# INLINE mapMWith #-}+mapMWith ::+ (m b -> StreamK m b -> StreamK m b)+ -> (a -> m b)+ -> StreamK m a+ -> StreamK m b+mapMWith cns f = foldrSShared (\x xs -> f x `cns` xs) nil++{-+-- See note under map definition above.+mapMWith cns f = go+ where+ go m1 = mkStream $ \st yld sng stp ->+ let single a = f a >>= sng+ yieldk a r = foldStreamShared st yld sng stp $ f a `cns` go r+ in foldStream (adaptState st) yieldk single stp m1+-}++-- XXX in fact use the Stream type everywhere and only use polymorphism in the+-- high level modules/prelude.+instance Monad m => Functor (StreamK m) where+ fmap = map++-- $smapM_Notes+--+-- The stateful step function can be simplified to @(s -> a -> m b)@ to provide+-- a read-only environment. However, that would just be 'mapM'.+--+-- The initial action could be @m (s, Maybe b)@, and we can also add a final+-- action @s -> m (Maybe b)@. This can be used to get pre/post scan like+-- functionality and also to flush the state in the end like scanlMAfter'.+-- We can also use it along with a fusible version of bracket to get+-- scanlMAfter' like functionality. See issue #677.+--+-- This can be further generalized to a type similar to Fold/Parser, giving it+-- filtering and parsing capability as well (this is in fact equivalent to+-- parseMany):+--+-- smapM :: (s -> a -> m (Step s b)) -> m s -> t m a -> t m b+--++-- | A stateful map aka scan but with a slight difference.+--+-- This is similar to a scan except that instead of emitting the state it emits+-- a separate result. This is also similar to mapAccumL but does not return the+-- final value of the state.+--+-- Separation of state from the output makes it easier to think in terms of a+-- shared state, and also makes it easier to keep the state fully strict and+-- the output lazy.+--+-- /Unimplemented/+--+{-# INLINE mapMAccum #-}+mapMAccum :: -- Monad m =>+ (s -> a -> m (s, b))+ -> m s+ -> StreamK m a+ -> StreamK m b+mapMAccum _step _initial _stream = undefined+{-+ -- XXX implement this directly instead of using scanlM'+ -- Once we have postscanlM' with monadic initial we can use this code+ -- let r = postscanlM'+ -- (\(s, _) a -> step s a)+ -- (fmap (,undefined) initial)+ -- stream+ let r = postscanlM'+ (\(s, _) a -> step s a)+ (fmap (,undefined) initial)+ stream+ in map snd r+-}++-- | Like 'concatMapWith' but carries a state which can be used to share+-- information across multiple steps of concat.+--+-- @+-- concatSmapMWith combine f initial = concatMapWith combine id . smapM f initial+-- @+--+-- /Unimplemented/+--+{-# INLINE concatMapMAccum #-}+concatMapMAccum :: -- (Monad m) =>+ (StreamK m b -> StreamK m b -> StreamK m b)+ -> (s -> a -> m (s, StreamK m b))+ -> m s+ -> StreamK m a+ -> StreamK m b+concatMapMAccum combine f initial =+ concatMapWith combine id . mapMAccum f initial++------------------------------------------------------------------------------+-- 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?++instance IsList (StreamK Identity a) where+ type (Item (StreamK Identity a)) = a++ {-# INLINE fromList #-}+ fromList = fromFoldable++ {-# INLINE toList #-}+ toList = Data.Foldable.foldr (:) []++-- XXX Fix these+{-+instance Eq a => Eq (StreamK Identity a) where+ {-# INLINE (==) #-}+ (==) xs ys = runIdentity $ eqBy (==) xs ys++instance Ord a => Ord (StreamK Identity a) where+ {-# INLINE compare #-}+ compare xs ys = runIdentity $ 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 (StreamK Identity a) where+ showsPrec p dl = showParen (p > 10) $+ showString "fromList " . shows (toList dl)++instance Read a => Read (StreamK Identity a) where+ readPrec = parens $ prec 10 $ do+ Ident "fromList" <- lexP+ GHC.Exts.fromList <$> readPrec++ readListPrec = readListPrecDefault++instance (a ~ Char) => IsString (StreamK Identity a) where+ {-# INLINE fromString #-}+ fromString = GHC.Exts.fromList++-------------------------------------------------------------------------------+-- Foldable+-------------------------------------------------------------------------------++-- | Lazy right associative fold.+{-# INLINE foldr #-}+foldr :: Monad m => (a -> b -> b) -> b -> StreamK m a -> m b+foldr step acc = foldrM (\x xs -> xs >>= \b -> return (step x b)) (return acc)++-- 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.++instance (Foldable m, Monad m) => Foldable (StreamK m) where++ {-# INLINE foldMap #-}+ foldMap f =+ fold+ . Streamly.Internal.Data.StreamK.Type.foldr (mappend . f) mempty++ {-# INLINE foldr #-}+ foldr f z t = appEndo (foldMap (Endo #. f) t) z++ {-# INLINE foldl' #-}+ foldl' f z0 xs = Data.Foldable.foldr f' id xs z0+ where f' x k = oneShot $ \z -> k $! f z x++ {-# INLINE length #-}+ length = Data.Foldable.foldl' (\n _ -> n + 1) 0++ {-# INLINE elem #-}+ elem = any . (==)++ {-# INLINE maximum #-}+ maximum =+ fromMaybe (errorWithoutStackTrace "maximum: empty stream")+ . toMaybe+ . Data.Foldable.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+ . Data.Foldable.foldl' getMin Nothing'++ where++ getMin Nothing' x = Just' x+ getMin (Just' mn) x = Just' $! min mn x++ {-# INLINE sum #-}+ sum = Data.Foldable.foldl' (+) 0++ {-# INLINE product #-}+ product = Data.Foldable.foldl' (*) 1++-------------------------------------------------------------------------------+-- Traversable+-------------------------------------------------------------------------------++instance Traversable (StreamK Identity) where+ {-# INLINE traverse #-}+ traverse f xs =+ runIdentity+ $ Streamly.Internal.Data.StreamK.Type.foldr+ consA (pure mempty) xs++ where++ consA x ys = liftA2 cons (f x) ys++-------------------------------------------------------------------------------+-- Nesting+-------------------------------------------------------------------------------++-- | Detach a stream from an SVar+{-# INLINE unShare #-}+unShare :: StreamK m a -> StreamK m a+unShare x = mkStream $ \st yld sng stp ->+ foldStream st yld sng stp x++-- XXX the function stream and value stream can run in parallel+{-# INLINE crossApplyWith #-}+crossApplyWith ::+ (StreamK m b -> StreamK m b -> StreamK m b)+ -> StreamK m (a -> b)+ -> StreamK m a+ -> StreamK m b+crossApplyWith par fstream stream = go1 fstream++ where++ go1 m =+ mkStream $ \st yld sng stp ->+ let foldShared = foldStreamShared st yld sng stp+ single f = foldShared $ unShare (go2 f stream)+ yieldk f r = foldShared $ unShare (go2 f stream) `par` go1 r+ in foldStream (adaptState st) yieldk single stp m++ go2 f m =+ mkStream $ \st yld sng stp ->+ let single a = sng (f a)+ yieldk a r = yld (f a) (go2 f r)+ in foldStream (adaptState st) yieldk single stp m++-- | Apply a stream of functions to a stream of values and flatten the results.+--+-- Note that the second stream is evaluated multiple times.+--+-- Definition:+--+-- >>> crossApply = StreamK.crossApplyWith StreamK.append+-- >>> crossApply = Stream.crossWith id+--+{-# INLINE crossApply #-}+crossApply ::+ StreamK m (a -> b)+ -> StreamK m a+ -> StreamK m b+crossApply fstream stream = go1 fstream++ where++ go1 m =+ mkStream $ \st yld sng stp ->+ let foldShared = foldStreamShared st yld sng stp+ single f = foldShared $ go3 f stream+ yieldk f r = foldShared $ go2 f r stream+ in foldStream (adaptState st) yieldk single stp m++ go2 f r1 m =+ mkStream $ \st yld sng stp ->+ let foldShared = foldStreamShared st yld sng stp+ stop = foldShared $ go1 r1+ single a = yld (f a) (go1 r1)+ yieldk a r = yld (f a) (go2 f r1 r)+ in foldStream (adaptState st) yieldk single stop m++ go3 f m =+ mkStream $ \st yld sng stp ->+ let single a = sng (f a)+ yieldk a r = yld (f a) (go3 f r)+ in foldStream (adaptState st) yieldk single stp m++{-# INLINE crossApplySnd #-}+crossApplySnd ::+ StreamK m a+ -> StreamK m b+ -> StreamK m b+crossApplySnd fstream stream = go1 fstream++ where++ go1 m =+ mkStream $ \st yld sng stp ->+ let foldShared = foldStreamShared st yld sng stp+ single _ = foldShared stream+ yieldk _ r = foldShared $ go2 r stream+ in foldStream (adaptState st) yieldk single stp m++ go2 r1 m =+ mkStream $ \st yld sng stp ->+ let foldShared = foldStreamShared st yld sng stp+ stop = foldShared $ go1 r1+ single a = yld a (go1 r1)+ yieldk a r = yld a (go2 r1 r)+ in foldStream st yieldk single stop m++{-# INLINE crossApplyFst #-}+crossApplyFst ::+ StreamK m a+ -> StreamK m b+ -> StreamK m a+crossApplyFst fstream stream = go1 fstream++ where++ go1 m =+ mkStream $ \st yld sng stp ->+ let foldShared = foldStreamShared st yld sng stp+ single f = foldShared $ go3 f stream+ yieldk f r = foldShared $ go2 f r stream+ in foldStream st yieldk single stp m++ go2 f r1 m =+ mkStream $ \st yld sng stp ->+ let foldShared = foldStreamShared st yld sng stp+ stop = foldShared $ go1 r1+ single _ = yld f (go1 r1)+ yieldk _ r = yld f (go2 f r1 r)+ in foldStream (adaptState st) yieldk single stop m++ go3 f m =+ mkStream $ \st yld sng stp ->+ let single _ = sng f+ yieldk _ r = yld f (go3 f r)+ in foldStream (adaptState st) yieldk single stp m++-- |+-- Definition:+--+-- >>> crossWith f m1 m2 = fmap f m1 `StreamK.crossApply` m2+--+-- Note that the second stream is evaluated multiple times.+--+{-# INLINE crossWith #-}+crossWith :: Monad m => (a -> b -> c) -> StreamK m a -> StreamK m b -> StreamK m c+crossWith f m1 m2 = fmap f m1 `crossApply` m2++-- | Given a @StreamK m a@ and @StreamK m b@ generate a stream with all possible+-- combinations of the tuple @(a, b)@.+--+-- Definition:+--+-- >>> cross = StreamK.crossWith (,)+--+-- The second stream is evaluated multiple times. If that is not desired it can+-- be cached in an 'Data.Array.Array' and then generated from the array before+-- calling this function. Caching may also improve performance if the stream is+-- expensive to evaluate.+--+-- See 'Streamly.Internal.Data.Unfold.cross' for a much faster fused+-- alternative.+--+-- Time: O(m x n)+--+-- /Pre-release/+{-# INLINE cross #-}+cross :: Monad m => StreamK m a -> StreamK m b -> StreamK m (a, b)+cross = crossWith (,)++-- XXX This is just concatMapWith 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 concatForWith #-}+bindWith, concatForWith ::+ (StreamK m b -> StreamK m b -> StreamK m b)+ -> StreamK m a+ -> (a -> StreamK m b)+ -> StreamK m b+concatForWith combine m1 f = go m1+{-+ -- There is a small improvement by unrolling the first iteration+ 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) `combine` go r+ in foldStreamShared (adaptState st) yieldk single stp 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) `combine` go r+ in foldStreamShared (adaptState st) yieldk single stp m++RENAME(bindWith,concatForWith)++-- 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, interleaving n streams in a left biased manner:+--+-- >>> lists = mk [[1,5],[2,6],[3,7],[4,8]]+-- >>> un $ StreamK.concatMapWith StreamK.interleave mk lists+-- [1,2,5,3,6,4,7,8]+--+-- For a fair interleaving example see 'bfsConcatMap' and 'mergeMapWith'.+--+{-# INLINE concatMapWith #-}+concatMapWith+ ::+ (StreamK m b -> StreamK m b -> StreamK m b)+ -> (a -> StreamK m b)+ -> StreamK m a+ -> StreamK m b+concatMapWith par f xs = concatForWith par xs f++-- | Like 'concatForWith' but maps an effectful function.+{-# INLINE concatForWithM #-}+concatForWithM :: Monad m =>+ (StreamK m b -> StreamK m b -> StreamK m b)+ -> StreamK m a+ -> (a -> m (StreamK m b))+ -> StreamK m b+concatForWithM combine s f = concatForWith combine s (concatEffect . f)++-- |+-- If total iterations are kept the same, each increase in the nesting level+-- increases the cost by roughly 1.5 times.+--+{-# INLINE concatMap #-}+concatMap :: (a -> StreamK m b) -> StreamK m a -> StreamK m b+concatMap = concatMapWith append++{-+-- 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)+-}++-- | Map a stream generating function on each element of a stream and+-- concatenate the results. This is the same as the bind function of the monad+-- instance. It is just a flipped 'concatMap' but more convenient to use for+-- nested use case, feels like an imperative @for@ loop.+--+-- >>> concatFor = flip StreamK.concatMap+--+-- A concatenating @for@ loop:+--+-- >>> :{+-- un $+-- StreamK.concatFor (mk [1,2,3]) $ \x ->+-- StreamK.fromPure x+-- :}+-- [1,2,3]+--+-- Nested concatenating @for@ loops:+--+-- >>> :{+-- un $+-- StreamK.concatFor (mk [1,2,3]) $ \x ->+-- StreamK.concatFor (mk [4,5,6]) $ \y ->+-- StreamK.fromPure (x, y)+-- :}+-- [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]+--+{-# INLINE concatFor #-}+concatFor :: StreamK m a -> (a -> StreamK m b) -> StreamK m b+concatFor = concatForWith append++-- | Like 'concatFor' but maps an effectful function. It allows conveniently+-- mixing monadic effects with streams.+--+-- >>> import Control.Monad.IO.Class (liftIO)+-- >>> :{+-- un $+-- StreamK.concatForM (mk [1,2,3]) $ \x -> do+-- liftIO $ putStrLn (show x)+-- pure $ StreamK.fromPure x+-- :}+-- 1+-- 2+-- 3+-- [1,2,3]+--+-- Nested concatentating @for@ loops:+--+-- >>> :{+-- un $+-- StreamK.concatForM (mk [1,2,3]) $ \x -> do+-- liftIO $ putStrLn (show x)+-- pure $ StreamK.concatFor (mk [4,5,6]) $ \y ->+-- StreamK.fromPure (x, y)+-- :}+-- 1+-- 2+-- 3+-- [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]+--+{-# INLINE concatForM #-}+concatForM :: Monad m => StreamK m a -> (a -> m (StreamK m b)) -> StreamK m b+concatForM s f =+ -- This should be better than implementing a custom concatForWithM because+ -- here we do not need to inline concatFor, "concatEffect . f" should get+ -- fused right here.+ concatFor s (concatEffect . f)++-- XXX Instead of using "mergeMapWith interleave" we can implement an N-way+-- interleaving CPS combinator which behaves like unfoldEachInterleave. Instead+-- of pairing up the streams we just need to go yielding one element from each+-- stream and storing the remaining streams and then keep doing rounds through+-- those in a round robin fashion. This would be much like wAsync.++-- | Combine streams in pairs using a binary combinator, the resulting streams+-- are then combined again in pairs recursively until we get to a single+-- combined stream. The composition would thus form a binary tree.+--+-- For example, 'mergeMapWith interleave' gives the following result:+--+-- >>> lists = mk [[1,2,3],[4,5,6],[7,8,9],[10,11,12]]+-- >>> un $ StreamK.mergeMapWith StreamK.interleave mk lists+-- [1,7,4,10,2,8,5,11,3,9,6,12]+--+-- The above example is equivalent to the following pairings:+--+-- >>> pair1 = mk [1,2,3] `StreamK.interleave` mk [4,5,6]+-- >>> pair2 = mk [7,8,9] `StreamK.interleave` mk [10,11,12]+-- >>> un $ pair1 `StreamK.interleave` pair2+-- [1,7,4,10,2,8,5,11,3,9,6,12]+--+-- If the number of streams being combined is not a power of 2, the binary tree+-- composed by mergeMapWith is not balanced, therefore, the output may not look+-- fairly interleaved, it will be biased towards the unpaired streams:+--+-- >>> lists = mk [[1,2,3],[4,5,6],[7,8,9]]+-- >>> un $ StreamK.mergeMapWith StreamK.interleave mk lists+-- [1,7,4,8,2,9,5,3,6]+--+-- An efficient merge sort can be implemented by using 'mergeBy' as the+-- combining function:+--+-- >>> combine = StreamK.mergeBy compare+-- >>> un $ StreamK.mergeMapWith combine StreamK.fromPure (mk [5,1,7,9,2])+-- [1,2,5,7,9]+--+-- /Caution: the stream of streams must be finite/+--+-- /Pre-release/+--+{-# INLINE mergeMapWith #-}+mergeMapWith+ ::+ (StreamK m b -> StreamK m b -> StreamK m b)+ -> (a -> StreamK m b)+ -> StreamK m a+ -> StreamK m b+mergeMapWith combine f str = go (leafPairs str)++ where++ go stream =+ mkStream $ \st yld sng stp ->+ let foldShared = foldStreamShared st yld sng stp+ single a = foldShared $ unShare a+ yieldk a r = foldShared $ go1 a r+ in foldStream (adaptState st) yieldk single stp stream++ go1 a1 stream =+ mkStream $ \st yld sng stp ->+ let foldShared = foldStreamShared st yld sng stp+ stop = foldShared $ unShare a1+ single a = foldShared $ unShare a1 `combine` a+ yieldk a r =+ foldShared $ go $ combine a1 a `cons` nonLeafPairs r+ in foldStream (adaptState st) yieldk single stop stream++ -- Exactly the same as "go" except that stop continuation extracts the+ -- stream.+ leafPairs stream =+ mkStream $ \st yld sng stp ->+ let foldShared = foldStreamShared st yld sng stp+ single a = sng (f a)+ yieldk a r = foldShared $ leafPairs1 a r+ in foldStream (adaptState st) yieldk single stp stream++ leafPairs1 a1 stream =+ mkStream $ \st yld sng _ ->+ let stop = sng (f a1)+ single a = sng (f a1 `combine` f a)+ yieldk a r = yld (f a1 `combine` f a) $ leafPairs r+ in foldStream (adaptState st) yieldk single stop stream++ -- Exactly the same as "leafPairs" except that it does not map "f"+ nonLeafPairs stream =+ mkStream $ \st yld sng stp ->+ let foldShared = foldStreamShared st yld sng stp+ single a = sng a+ yieldk a r = foldShared $ nonLeafPairs1 a r+ in foldStream (adaptState st) yieldk single stp stream++ nonLeafPairs1 a1 stream =+ mkStream $ \st yld sng _ ->+ let stop = sng a1+ single a = sng (a1 `combine` a)+ yieldk a r = yld (a1 `combine` a) $ nonLeafPairs r+ in foldStream (adaptState st) yieldk single stop stream++-- | See 'bfsConcatFor' for detailed documentation.+--+-- >>> bfsConcatMap = flip StreamK.bfsConcatFor+--+{-# INLINE bfsConcatMap #-}+bfsConcatMap ::+ (a -> StreamK m b)+ -> StreamK m a+ -> StreamK m b+bfsConcatMap f m1 = go id m1++ where++ go xs m =+ mkStream $ \st yld sng stp ->+ let foldShared = foldStreamShared st yld sng stp+ stop = foldStream st yld sng stp (goLoop id (xs []))+ single a = foldShared $ goLast xs (f a)+ yieldk a r = foldShared $ goNext xs r (f a)+ in foldStream (adaptState st) yieldk single stop m++ -- generate first element from cur stream, and then put it back in the+ -- queue xs.+ goNext xs m cur =+ mkStream $ \st yld sng stp -> do+ let stop = foldStream st yld sng stp (go xs m)+ single a = yld a (go xs m)+ yieldk a r = yld a (go (xs . (r :)) m)+ foldStream st yieldk single stop cur++ goLast xs cur =+ mkStream $ \st yld sng stp -> do+ let stop = foldStream st yld sng stp (goLoop id (xs []))+ single a = yld a (goLoop id (xs []))+ yieldk a r = yld a (goLoop id ((xs . (r :)) []))+ foldStream st yieldk single stop cur++ -- Loop through all streams in the queue ys until they are over+ goLoop ys [] =+ -- We will do this optimization only after two iterations are+ -- over, if doing this earlier is helpful we can do it in+ -- goLast as well, before calling goLoop.+ let xs = ys []+ in case xs of+ [] -> nil+ (z:[]) -> z+ (z1:z2:[]) -> interleave z1 z2+ zs -> goLoop id zs+ goLoop ys (x:xs) =+ mkStream $ \st yld sng stp -> do+ let stop = foldStream st yld sng stp (goLoop ys xs)+ single a = yld a (goLoop ys xs)+ yieldk a r = yld a (goLoop (ys . (r :)) xs)+ foldStream st yieldk single stop x++-- | While 'concatFor' flattens a stream of streams in a depth first manner,+-- 'bfsConcatFor' flattens it in a breadth-first manner. It yields one item+-- from the first stream, then one item from the next stream and so on. In+-- nested loops it has the effect of prioritizing the new outer loop iteration+-- over the inner loops, thus inverting the looping.+-- Given a stream of three streams:+--+-- @+-- 1. [1,2,3]+-- 2. [4,5,6]+-- 3. [7,8,9]+-- @+--+-- The resulting stream is @(1,4,7),(2,5,8),(3,6,9)@. The parenthesis are added+-- to emphasize the iterations.+--+-- For example:+--+-- >>> stream = mk [[1,2,3],[4,5,6],[7,8,9]]+-- >>> :{+-- un $+-- StreamK.bfsConcatFor stream $ \x ->+-- StreamK.fromStream $ Stream.fromList x+-- :}+-- [1,4,7,2,5,8,3,6,9]+--+-- Compare with 'concatForWith' 'interleave' which explores the depth+-- exponentially more compared to the breadth, such that each stream yields+-- twice as many items compared to the next stream.+--+-- See also the equivalent fused version 'Data.Stream.unfoldEachInterleave'.+--+{-# INLINE bfsConcatFor #-}+bfsConcatFor :: StreamK m a -> (a -> StreamK m b) -> StreamK m b+bfsConcatFor = flip bfsConcatMap++-- | Like 'bfsConcatFor' but maps a monadic function.+{-# INLINE bfsConcatForM #-}+bfsConcatForM :: Monad m => StreamK m a -> (a -> m (StreamK m b)) -> StreamK m b+bfsConcatForM s f = bfsConcatMap (concatEffect . f) s++-- | See 'fairConcatFor' for detailed documentation.+--+-- >>> fairConcatMap = flip StreamK.fairConcatFor+--+{-# INLINE fairConcatMap #-}+fairConcatMap ::+ (a -> StreamK m b)+ -> StreamK m a+ -> StreamK m b+fairConcatMap f m1 = go id m1++ where++ go xs m =+ mkStream $ \st yld sng stp ->+ let foldShared = foldStreamShared st yld sng stp+ stop = foldStream st yld sng stp (goLoop id (xs []))+ single a = foldShared $ goLoop id (xs [f a])+ yieldk a r = foldShared $ goNext r id (xs [f a])+ in foldStream (adaptState st) yieldk single stop m++ goNext m ys [] = go ys m+ goNext m ys (x:xs) =+ mkStream $ \st yld sng stp -> do+ let stop = foldStream st yld sng stp (goNext m ys xs)+ single a = yld a (goNext m ys xs)+ yieldk a r = yld a (goNext m (ys . (r :)) xs)+ foldStream st yieldk single stop x++ -- Loop through all streams in the queue ys until they are over+ goLoop ys [] =+ -- We will do this optimization only after two iterations are+ -- over, if doing this earlier is helpful we can do it in+ -- goLast as well, before calling goLoop.+ let xs = ys []+ in case xs of+ [] -> nil+ (z:[]) -> z+ (z1:z2:[]) -> interleave z1 z2+ zs -> goLoop id zs+ goLoop ys (x:xs) =+ mkStream $ \st yld sng stp -> do+ let stop = foldStream st yld sng stp (goLoop ys xs)+ single a = yld a (goLoop ys xs)+ yieldk a r = yld a (goLoop (ys . (r :)) xs)+ foldStream st yieldk single stop x++-- | 'fairConcatFor' is like 'concatFor' but traverses the depth and breadth of+-- nesting equally. Therefore, the outer and the inner loops in a nested loop+-- get equal priority. It can be used to nest infinite streams without starving+-- outer streams due to inner ones.+--+-- Given a stream of three streams:+--+-- @+-- 1. [1,2,3]+-- 2. [4,5,6]+-- 3. [7,8,9]+-- @+--+-- Here, outer loop is the stream of streams and the inner loops are the+-- individual streams. The traversal sweeps the diagonals in the above grid to+-- give equal chance to outer and inner loops. The resulting stream is+-- @(1),(2,4),(3,5,7),(6,8),(9)@, diagonals are parenthesized for emphasis.+--+-- == Looping+--+-- A single stream case is equivalent to 'concatFor':+--+-- >>> un $ StreamK.fairConcatFor (mk [1,2]) $ \x -> StreamK.fromPure x+-- [1,2]+--+-- == Fair Nested Looping+--+-- Multiple streams nest like @for@ loops. The result is a cross product of the+-- streams. However, the ordering of the results of the cross product is such+-- that each stream gets consumed equally. In other words, inner iterations of+-- a nested loop get the same priority as the outer iterations. Inner+-- iterations do not finish completely before the outer iterations start.+--+-- >>> :{+-- un $ do+-- StreamK.fairConcatFor (mk [1,2,3]) $ \x ->+-- StreamK.fairConcatFor (mk [4,5,6]) $ \y ->+-- StreamK.fromPure (x, y)+-- :}+-- [(1,4),(1,5),(2,4),(1,6),(2,5),(3,4),(2,6),(3,5),(3,6)]+--+-- == Nesting Infinite Streams+--+-- Example with infinite streams. Print all pairs in the cross product with sum+-- less than a specified number.+--+-- >>> :{+-- Stream.toList+-- $ Stream.takeWhile (\(x,y) -> x + y < 6)+-- $ StreamK.toStream+-- $ StreamK.fairConcatFor (mk [1..]) $ \x ->+-- StreamK.fairConcatFor (mk [1..]) $ \y ->+-- StreamK.fromPure (x, y)+-- :}+-- [(1,1),(1,2),(2,1),(1,3),(2,2),(3,1),(1,4),(2,3),(3,2),(4,1)]+--+-- == How the nesting works?+--+-- If we look at the cross product of [1,2,3], [4,5,6], the streams being+-- combined using 'fairConcatFor' are the following sequential loop iterations:+--+-- @+-- (1,4) (1,5) (1,6) -- first iteration of the outer loop+-- (2,4) (2,5) (2,6) -- second iteration of the outer loop+-- (3,4) (3,5) (3,6) -- third iteration of the outer loop+-- @+--+-- The result is a triangular or diagonal traversal of these iterations:+--+-- @+-- [(1,4),(1,5),(2,4),(1,6),(2,5),(3,4),(2,6),(3,5),(3,6)]+-- @+--+-- == Non-Termination Cases+--+-- If one of the two interleaved streams does not produce an output at all and+-- continues forever then the other stream will never get scheduled. This is+-- because a stream is unscheduled only after it produces an output. This can+-- lead to non-terminating programs, an example is provided below.+--+-- >>> :{+-- oddsIf x = mk (if x then [1,3..] else [2,4..])+-- filterEven x = if even x then StreamK.fromPure x else StreamK.nil+-- :}+--+-- >>> :{+-- evens =+-- StreamK.fairConcatFor (mk [True,False]) $ \r ->+-- StreamK.concatFor (oddsIf r) filterEven+-- :}+--+-- The @evens@ function does not terminate because, when r is True, the nested+-- 'concatFor' is a non-productive infinite loop, therefore, the outer loop+-- never gets a chance to generate the @False@ value.+--+-- But the following refactoring of the above code works as expected:+--+-- >>> :{+-- mixed =+-- StreamK.fairConcatFor (mk [True,False]) $ \r ->+-- StreamK.concatFor (oddsIf r) StreamK.fromPure+-- :}+--+-- >>> evens = StreamK.fairConcatFor mixed filterEven+-- >>> Stream.toList $ Stream.take 3 $ StreamK.toStream evens+-- [2,4,6]+--+-- This works because in @mixed@ both the streams being interleaved are+-- productive.+--+-- Care should be taken how you write your program, keep in mind the scheduling+-- implications. To avoid such scheduling problems in serial interleaving, you+-- can use concurrent interleaving instead i.e. parFairConcatFor. Due to+-- concurrent threads the other branch will make progress even if one is an+-- infinite loop producing nothing.+--+-- == Logic Programming+--+-- Streamly provides all operations for logic programming. It provides+-- functionality equivalent to 'LogicT' type from the 'logict' package.+-- The @MonadLogic@ operations can be implemented using the available stream+-- operations. For example, 'uncons' is @msplit@, 'interleave' corresponds to+-- the @interleave@ operation of MonadLogic, 'fairConcatFor' is the+-- fair bind (@>>-@) operation.+--+-- == Related Operations+--+-- 'concatForWith' 'interleave' is another way to interleave two serial+-- streams. In this case, the inner loop iterations get exponentially more+-- priority over the outer iterations of the nested loop. This is biased+-- towards the inner loops - this is exactly how the logic-t and list-t+-- implementation of fair bind works.+--+{-# INLINE fairConcatFor #-}+fairConcatFor :: StreamK m a -> (a -> StreamK m b) -> StreamK m b+fairConcatFor = flip fairConcatMap++-- | Like 'fairConcatFor' but maps a monadic function.+{-# INLINE fairConcatForM #-}+fairConcatForM :: Monad m => StreamK m a -> (a -> m (StreamK m b)) -> StreamK m b+fairConcatForM s f = fairConcatMap (concatEffect . f) s++{-+instance Monad m => Applicative (StreamK m) where+ {-# INLINE pure #-}+ pure = fromPure++ {-# INLINE (<*>) #-}+ (<*>) = crossApply++ {-# INLINE liftA2 #-}+ liftA2 f x = (<*>) (fmap f x)++ {-# INLINE (*>) #-}+ (*>) = crossApplySnd++ {-# INLINE (<*) #-}+ (<*) = crossApplyFst++-- 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 (StreamK m) where+ {-# INLINE return #-}+ return = pure++ {-# INLINE (>>=) #-}+ (>>=) = flip concatMap+-}++{-+-- Like concatMap but generates stream using an unfold function. Similar to+-- unfoldMany but for StreamK.+concatUnfoldr :: IsStream t+ => (b -> t m (Maybe (a, b))) -> t m b -> t m a+concatUnfoldr = undefined+-}++------------------------------------------------------------------------------+-- concatIterate - Map and flatten Trees of Streams+------------------------------------------------------------------------------++-- | Yield an input element in the output stream, map a stream generator on it+-- and repeat the process on the resulting stream. Resulting streams are+-- flattened using the 'concatMapWith' combinator. This can be used for a depth+-- first style (DFS) traversal of a tree like structure.+--+-- Example, list a directory tree using DFS:+--+-- >>> f = StreamK.fromStream . either (Dir.readEitherPaths id) (const Stream.nil)+-- >>> input = StreamK.fromEffect (Left <$> Path.fromString ".")+-- >>> ls = StreamK.concatIterateWith StreamK.append f input+--+-- Note that 'iterateM' is a special case of 'concatIterateWith':+--+-- >>> iterateM f = StreamK.concatIterateWith StreamK.append (StreamK.fromEffect . f) . StreamK.fromEffect+--+-- /Pre-release/+--+{-# INLINE concatIterateWith #-}+concatIterateWith ::+ (StreamK m a -> StreamK m a -> StreamK m a)+ -> (a -> StreamK m a)+ -> StreamK m a+ -> StreamK m a+concatIterateWith combine f = iterateStream++ where++ iterateStream = concatMapWith combine generate++ generate x = x `cons` iterateStream (f x)++-- | Like 'concatIterateWith' but uses the pairwise flattening combinator+-- 'mergeMapWith' for flattening the resulting streams. This can be used for a+-- balanced traversal of a tree like structure.+--+-- Example, list a directory tree using balanced traversal:+--+-- >>> f = StreamK.fromStream . either (Dir.readEitherPaths id) (const Stream.nil)+-- >>> input = StreamK.fromEffect (Left <$> Path.fromString ".")+-- >>> ls = StreamK.mergeIterateWith StreamK.interleave f input+--+-- /Pre-release/+--+{-# INLINE mergeIterateWith #-}+mergeIterateWith ::+ (StreamK m a -> StreamK m a -> StreamK m a)+ -> (a -> StreamK m a)+ -> StreamK m a+ -> StreamK m a+mergeIterateWith combine f = iterateStream++ where++ iterateStream = mergeMapWith combine generate++ generate x = x `cons` iterateStream (f x)++------------------------------------------------------------------------------+-- Flattening Graphs+------------------------------------------------------------------------------++-- To traverse graphs we need a state to be carried around in the traversal.+-- For example, we can use a hashmap to store the visited status of nodes.++-- XXX rename to concateIterateAccum? Like mapMAccum++-- | Like 'iterateMap' but carries a state in the stream generation function.+-- This can be used to traverse graph like structures, we can remember the+-- visited nodes in the state to avoid cycles.+--+-- Note that a combination of 'iterateMap' and 'usingState' can also be used to+-- traverse graphs. However, this function provides a more localized state+-- instead of using a global state.+--+-- See also: 'mfix'+--+-- /Pre-release/+--+{-# INLINE concatIterateScanWith #-}+concatIterateScanWith+ :: Monad m+ => (StreamK m a -> StreamK m a -> StreamK m a)+ -> (b -> a -> m (b, StreamK m a))+ -> m b+ -> StreamK m a+ -> StreamK m a+concatIterateScanWith combine f initial stream =+ concatEffect $ do+ b <- initial+ iterateStream (b, stream)++ where++ iterateStream (b, s) = pure $ concatMapWith combine (generate b) s++ generate b a = a `cons` feedback b a++ feedback b a = concatEffect $ f b a >>= iterateStream++------------------------------------------------------------------------------+-- Either streams+------------------------------------------------------------------------------++-- Keep concating either streams as long as rights are generated, stop as soon+-- as a left is generated and concat the left stream.+--+-- See also: 'handle'+--+-- /Unimplemented/+--+{-+concatMapEitherWith+ :: (forall x. t m x -> t m x -> t m x)+ -> (a -> t m (Either (StreamK m b) b))+ -> StreamK m a+ -> StreamK m b+concatMapEitherWith = undefined+-}++-- XXX We should prefer using the Maybe stream returning signatures over this.+-- This API should perhaps be removed in favor of those.++-- | In an 'Either' stream iterate on 'Left's. This is a special case of+-- 'concatIterateWith':+--+-- >>> concatIterateLeftsWith combine f = StreamK.concatIterateWith combine (either f (const StreamK.nil))+--+-- To traverse a directory tree:+--+-- >>> input = StreamK.fromEffect (Left <$> Path.fromString ".")+-- >>> ls = StreamK.concatIterateLeftsWith StreamK.append (StreamK.fromStream . Dir.readEither id) input+--+-- /Pre-release/+--+{-# INLINE concatIterateLeftsWith #-}+concatIterateLeftsWith+ :: (b ~ Either a c)+ => (StreamK m b -> StreamK m b -> StreamK m b)+ -> (a -> StreamK m b)+ -> StreamK m b+ -> StreamK m b+concatIterateLeftsWith combine f =+ concatIterateWith combine (either f (const nil))++------------------------------------------------------------------------------+-- Interleaving+------------------------------------------------------------------------------++infixr 6 `interleave`++-- We can have a variant of interleave where 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.+--+-- TBD; give an example to show second stream is half consumed.+--+-- a1,a2,a3,a4,a5,a6,a7,a8+-- b1,b2,b3,b4,b5,b6,b7,b8+-- c1,c2,c3,c4,c5,c6,c7,c8+-- d1,d2,d3,d4,d5,d6,d7,d8+-- e1,e2,e3,e4,e5,e6,e7,e8+-- f1,f2,f3,f4,f5,f6,f7,f8+-- g1,g2,g3,g4,g5,g6,g7,g8+-- h1,h2,h3,h4,h5,h6,h7,h8+--+-- Produces: (..)+--++-- | Interleave two streams fairly, yielding one item from each in a+-- round-robin fashion:+--+-- >>> un $ StreamK.interleave (mk [1,3,5]) (mk [2,4,6])+-- [1,2,3,4,5,6]+-- >>> un $ StreamK.interleave (mk [1,3]) (mk [2,4,6])+-- [1,2,3,4,6]+-- >>> un $ StreamK.interleave (mk []) (mk [2,4,6])+-- [2,4,6]+--+-- 'interleave' is right associative when used as an infix operator.+--+-- >>> un $ mk [1,2,3] `StreamK.interleave` mk [4,5,6] `StreamK.interleave` mk [7,8,9]+-- [1,4,2,7,3,5,8,6,9]+--+-- Because of right association, the first stream yields as many items as the+-- next two streams combined.+--+-- Be careful when refactoring code involving a chain of three or more+-- 'interleave' operations as it is not associative i.e. right associated code+-- may not produce the same result as left associated. This is a direct+-- consequence of the disbalance of scheduling in the previous example. If left+-- associated the above example would produce:+--+-- >>> un $ (mk [1,2,3] `StreamK.interleave` mk [4,5,6]) `StreamK.interleave` mk [7,8,9]+-- [1,7,4,8,2,9,5,3,6]+--+-- Note: Use concatMap based interleaving instead of the binary operator to+-- interleave more than two streams to avoid associativity issues.+--+-- 'concatMapWith' 'interleave' flattens a stream of streams using 'interleave'+-- in a right associative manner. Given a stream of three streams:+--+-- @+-- 1. [1,2,3]+-- 2. [4,5,6]+-- 3. [7,8,9]+-- @+--+-- The resulting sequence is @[1,4,2,7,3,5,8,6,9]@.+--+-- For this reason, the right associated flattening with 'interleave' can work+-- with infinite number of streams without opening too many streams at the same+-- time. Each stream is consumed twice as much as the next one; if we are+-- combining an infinite number of streams of size @n@ then at most @log n@+-- streams will be opened at any given time, because the first stream will+-- finish by the time the stream after @log n@ th stream is opened.+--+-- Compare with 'bfsConcatMap' and 'mergeMapWith' 'interleave'.+--+-- For interleaving many streams, the best way is to use 'bfsConcatMap'.+--+-- See also the fused version 'Streamly.Data.Stream.interleave'.+{-# INLINE interleave #-}+interleave :: StreamK m a -> StreamK m a -> StreamK m a+interleave 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 (interleave m2 r)+ foldStream st yieldk single stop m1++-- Examples:+--+-- >>> fromList = StreamK.fromStream . Stream.fromList+-- >>> toList = Stream.toList . StreamK.toStream+-- >>> f x y = toList $ StreamK.interleaveSepBy (fromList x) (fromList y)+--+-- -- This is broken.+-- >> f "..." "abc"+-- "a.b.c"++-- >>> f ".." "abc"+-- "a.b.c"+-- >>> f "." "abc"+-- "a.bc"+--+{-# INLINE interleaveSepBy #-}+interleaveSepBy :: StreamK m a -> StreamK m a -> StreamK m a+interleaveSepBy m2 m1 = 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 (interleave s1 r)+ in foldStream st yieldk single stop s2++infixr 6 `interleaveFst`++{-# DEPRECATED interleaveFst "Please use flip interleaveSepBy instead." #-}+{-# INLINE interleaveFst #-}+interleaveFst :: StreamK m a -> StreamK m a -> StreamK m a+interleaveFst = flip interleaveSepBy++-- |+--+-- Examples:+--+-- >>> fromList = StreamK.fromStream . Stream.fromList+-- >>> toList = Stream.toList . StreamK.toStream+-- >>> f x y = toList $ StreamK.interleaveEndBy' (fromList x) (fromList y)+-- >>> f "..." "abc"+-- "a.b.c."+-- >>> f "..." "ab"+-- "a.b."+--+-- Currently broken, generates an additional element at the end::+--+-- >> f ".." "abc"+-- "a.b."+--+{-# INLINE interleaveEndBy' #-}+interleaveEndBy' :: StreamK m a -> StreamK m a -> StreamK m a+interleaveEndBy' m2 m1 = mkStream $ \st yld _ stp -> do+ let stop = stp+ -- "single a" is defined as "yld a (interleaveMin m2 nil)" instead of+ -- "sng a" to keep the behaviour consistent with the yield+ -- continuation.+ single a = yld a (interleaveEndBy' nil m2)+ yieldk a r = yld a (interleaveEndBy' r m2)+ foldStream st yieldk single stop m1++infixr 6 `interleaveMin`++{-# DEPRECATED interleaveMin "Please use flip interleaveEndBy' instead." #-}+{-# INLINE interleaveMin #-}+interleaveMin :: StreamK m a -> StreamK m a -> StreamK m a+interleaveMin = flip interleaveEndBy'++-------------------------------------------------------------------------------+-- Generation+-------------------------------------------------------------------------------++-- |+-- >>> :{+-- unfoldr step s =+-- case step s of+-- Nothing -> StreamK.nil+-- Just (a, b) -> a `StreamK.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 > 2+-- then Nothing+-- else Just (b, b + 1)+-- in StreamK.toList $ StreamK.unfoldr f 0+-- :}+-- [0,1,2]+--+{-# INLINE unfoldr #-}+unfoldr :: (b -> Maybe (a, b)) -> b -> StreamK 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 unfoldrMWith #-}+unfoldrMWith :: Monad m =>+ (m a -> StreamK m a -> StreamK m a)+ -> (b -> m (Maybe (a, b)))+ -> b+ -> StreamK m a+unfoldrMWith cns step = go++ where++ go s = sharedMWith cns $ \yld _ stp -> do+ r <- step s+ case r of+ Just (a, b) -> yld a (go b)+ Nothing -> stp++-- | 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 > 2+-- then return Nothing+-- else return (Just (b, b + 1))+-- in StreamK.toList $ StreamK.unfoldrM f 0+-- :}+-- [0,1,2]+--+{-# INLINE unfoldrM #-}+unfoldrM :: Monad m => (b -> m (Maybe (a, b))) -> b -> StreamK m a+unfoldrM = unfoldrMWith consM++-- | Generate an infinite stream by repeating a pure value.+--+-- >>> repeat x = let xs = StreamK.cons x xs in xs+--+-- /Pre-release/+{-# INLINE repeat #-}+repeat :: a -> StreamK m a+repeat x = let xs = cons x xs in xs++-- | Like 'repeatM' but takes a stream 'cons' operation to combine the actions+-- in a stream specific manner. A serial cons would repeat the values serially+-- while an async cons would repeat concurrently.+--+-- /Pre-release/+repeatMWith :: (m a -> t m a -> t m a) -> m a -> t m a+repeatMWith cns = go++ where++ go m = m `cns` go m++{-# INLINE replicateMWith #-}+replicateMWith :: (m a -> StreamK m a -> StreamK m a) -> Int -> m a -> StreamK m a+replicateMWith cns n m = go n++ where++ go cnt = if cnt <= 0 then nil else m `cns` go (cnt - 1)++{-# INLINE fromIndicesMWith #-}+fromIndicesMWith ::+ (m a -> StreamK m a -> StreamK m a) -> (Int -> m a) -> StreamK m a+fromIndicesMWith cns gen = go 0++ where++ go i = mkStream $ \st stp sng yld -> do+ foldStreamShared st stp sng yld (gen i `cns` go (i + 1))++{-# INLINE iterateMWith #-}+iterateMWith :: Monad m =>+ (m a -> StreamK m a -> StreamK m a) -> (a -> m a) -> m a -> StreamK m a+iterateMWith cns step = go++ where++ go s = mkStream $ \st stp sng yld -> do+ !next <- s+ foldStreamShared st stp sng yld (return next `cns` go (step next))++-- | head for non-empty streams, fails for empty stream case.+--+{-# INLINE headNonEmpty #-}+headNonEmpty :: Monad m => StreamK m a -> m a+headNonEmpty = foldrM (\x _ -> return x) (error "headNonEmpty: empty stream")++-- | init for non-empty streams, fails for empty stream case.+--+-- See also 'init' for a non-partial version of this function..+{-# INLINE initNonEmpty #-}+initNonEmpty :: Stream m a -> Stream m a+initNonEmpty = go0++ where++ go0 m = mkStream $ \st yld sng stp ->+ let stop = error "initNonEmpty: Empty Stream."+ single _ = stp+ yieldk a r = foldStream st yld sng stp (go1 a r)+ in foldStream st yieldk single stop m++ go1 a r = mkStream $ \st yld sng stp ->+ let stop = stp+ single _ = sng a+ yieldk a1 r1 = yld a (go1 a1 r1)+ in foldStream st yieldk single stop r++-- | tail for non-empty streams, fails for empty stream case.+--+-- See also 'tail' for a non-partial version of this function..+--+-- Note: this is same as "drop 1" with error on empty stream.+{-# INLINE tailNonEmpty #-}+tailNonEmpty :: StreamK m a -> StreamK m a+tailNonEmpty m = mkStream $ \st yld sng stp ->+ let stop = error "tailNonEmpty: empty stream"+ single _ = stp+ yieldk _ r = foldStream st yld sng stp r+ in foldStream st yieldk single stop m++-- | We can define cyclic structures using @let@:+--+-- >>> :set -fno-warn-unrecognised-warning-flags+-- >>> :set -fno-warn-x-partial+-- >>> let (a, b) = ([1, b], head a) in (a, b)+-- ([1,1],1)+--+-- The function @fix@ defined as:+--+-- >>> fix f = let x = f x in x+--+-- ensures that the argument of a function and its output refer to the same+-- lazy value @x@ i.e. the same location in memory. Thus @x@ can be defined+-- in terms of itself, creating structures with cyclic references.+--+-- >>> f ~(a, b) = ([1, b], head a)+-- >>> fix f+-- ([1,1],1)+--+-- 'Control.Monad.mfix' is essentially the same as @fix@ but for monadic+-- values.+--+-- Using 'mfix' for streams we can construct a stream in which each element of+-- the stream is defined in a cyclic fashion. The argument of the function+-- being fixed represents the current element of the stream which is being+-- returned by the stream monad. Thus, we can use the argument to construct+-- itself.+--+-- In the following example, the argument @action@ of the function @f@+-- represents the tuple @(x,y)@ returned by it in a given iteration. We define+-- the first element of the tuple in terms of the second.+--+-- >>> import System.IO.Unsafe (unsafeInterleaveIO)+--+-- >>> :{+-- main = Stream.fold (Fold.drainMapM print) $ StreamK.toStream $ StreamK.mfix f+-- where+-- f action = StreamK.unNested $ do+-- let incr n act = fmap ((+n) . snd) $ unsafeInterleaveIO act+-- x <- StreamK.Nested $ StreamK.fromStream $ Stream.sequence $ Stream.fromList [incr 1 action, incr 2 action]+-- y <- StreamK.Nested $ StreamK.fromStream $ Stream.fromList [4,5]+-- return (x, y)+-- :}+--+-- Note: you cannot achieve this by just changing the order of the monad+-- statements because that would change the order in which the stream elements+-- are generated.+--+-- Note that the function @f@ must be lazy in its argument, that's why we use+-- 'unsafeInterleaveIO' on @action@ because IO monad is strict.+--+-- /Pre-release/+{-# INLINE mfix #-}+mfix :: Monad m => (m a -> StreamK m a) -> StreamK 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++ -- fix the head element of the stream+ xs = fix (f . headNonEmpty)++ -- now fix the tail recursively+ ys = mfix (tailNonEmpty . f)++-------------------------------------------------------------------------------+-- Conversions+-------------------------------------------------------------------------------++-- |+-- >>> fromFoldable = Prelude.foldr StreamK.cons StreamK.nil+--+-- Construct a stream from a 'Foldable' containing pure values:+--+{-# INLINE fromFoldable #-}+fromFoldable :: Foldable f => f a -> StreamK m a+fromFoldable = Prelude.foldr cons nil++{-# INLINE fromFoldableM #-}+fromFoldableM :: (Foldable f, Monad m) => f (m a) -> StreamK m a+fromFoldableM = Prelude.foldr consM nil++{-# INLINE fromList #-}+fromList :: [a] -> StreamK m a+fromList = fromFoldable++-------------------------------------------------------------------------------+-- Deconstruction+-------------------------------------------------------------------------------++{-# INLINE uncons #-}+uncons :: Applicative m => StreamK m a -> m (Maybe (a, StreamK m a))+uncons m =+ let stop = pure Nothing+ single a = pure (Just (a, nil))+ yieldk a r = pure (Just (a, r))+ in foldStream defState yieldk single stop m++-- Note that this is not a StreamK -> StreamK because then we cannot handle the+-- empty stream case without making this a partial function.+--+-- See tailNonEmpty as well above.++-- | Same as:+--+-- >>> tail = fmap (fmap snd) . StreamK.uncons+--+{-# INLINE tail #-}+tail :: Applicative m => StreamK m a -> m (Maybe (StreamK m a))+tail =+ let stop = pure Nothing+ single _ = pure $ Just nil+ yieldk _ r = pure $ Just r+ in foldStream defState yieldk single stop++-- Note that this is not a StreamK -> StreamK because then we cannot handle the+-- empty stream case without making this a partial function.+--+-- XXX How do we implement unsnoc? Make StreamK a monad and return the+-- remaining stream as a result value in the monad?++-- | Extract all but the last element of the stream, if any. This will end up+-- evaluating the last element as well to find out that it is last.+--+-- /Pre-release/+{-# INLINE init #-}+init :: Applicative m => StreamK m a -> m (Maybe (StreamK m a))+init = go1+ where+ go1 m1 = do+ (\case+ Nothing -> Nothing+ Just (h, t) -> Just $ go h t) <$> uncons m1+ 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++------------------------------------------------------------------------------+-- Reordering+------------------------------------------------------------------------------++-- | Lazy left fold to a stream.+{-# INLINE foldlS #-}+foldlS ::+ (StreamK m b -> a -> StreamK m b) -> StreamK m b -> StreamK m a -> StreamK m b+foldlS step = go+ 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++{-# INLINE reverse #-}+reverse :: StreamK m a -> StreamK m a+reverse = foldlS (flip cons) nil++------------------------------------------------------------------------------+-- Running effects+------------------------------------------------------------------------------++-- | Run an action before evaluating the stream.+{-# INLINE before #-}+before :: Monad m => m b -> StreamK m a -> StreamK m a+before action stream =+ mkStream $ \st yld sng stp ->+ action >> foldStreamShared st yld sng stp stream++-- XXX Rename to "impure" (opposite of pure) or "purely".+{-# INLINE concatEffect #-}+concatEffect :: Monad m => m (StreamK m a) -> StreamK m a+concatEffect action =+ mkStream $ \st yld sng stp ->+ action >>= foldStreamShared st yld sng stp++{-# INLINE concatMapEffect #-}+concatMapEffect :: Monad m => (b -> StreamK m a) -> m b -> StreamK m a+concatMapEffect f action =+ mkStream $ \st yld sng stp ->+ action >>= foldStreamShared st yld sng stp . f++------------------------------------------------------------------------------+-- Stream with a cross product style monad instance+------------------------------------------------------------------------------++-- XXX add Alternative, MonadPlus - should we use interleave as the Semigroup+-- append operation in FairNested?++-- | 'Nested' is a list-transformer monad, it serves the same purpose as the+-- @ListT@ type from the @list-t@ package. It is similar to the standard+-- Haskell lists' monad instance. 'Nested' monad behaves like nested @for@ loops+-- implementing a computation based on a cross product over multiple streams.+--+-- >>> mk = StreamK.Nested . StreamK.fromStream . Stream.fromList+-- >>> un = Stream.toList . StreamK.toStream . StreamK.unNested+--+-- == Looping+--+-- In the following code the variable @x@ assumes values of the elements of the+-- stream one at a time and runs the code that follows; using that value. It is+-- equivalent to a @for@ loop:+--+-- >>> :{+-- un $ do+-- x <- mk [1,2,3] -- for each element in the stream+-- return x+-- :}+-- [1,2,3]+--+-- == Nested Looping+--+-- Multiple streams can be nested like nested @for@ loops. The result is a+-- cross product of the streams.+--+-- >>> :{+-- un $ do+-- x <- mk [1,2,3] -- outer loop, for each element in the stream+-- y <- mk [4,5,6] -- inner loop, for each element in the stream+-- return (x, y)+-- :}+-- [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]+--+-- Note that an infinite stream in an inner loop will block the outer streams+-- from moving to the next iteration.+--+-- == How it works?+--+-- The bind operation of the monad is flipped 'concatMapWith' 'append'. The+-- concatMap operation maps the lines involving y as a function of x over the+-- stream [1,2,3]. The streams generated so are combined using the 'append'+-- operation. If we desugar the above monad code using bind explicitly, it+-- becomes clear how it works:+--+-- >>> import Streamly.Internal.Data.StreamK (Nested(..))+-- >>> (Nested m) >>= f = Nested $ StreamK.concatMapWith StreamK.append (unNested . f) m+-- >>> un (mk [1,2,3] >>= (\x -> (mk [4,5,6] >>= \y -> return (x,y))))+-- [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]+--+-- You can achieve the looping and nested looping by directly using concatMap+-- but the monad and the \"do notation\" gives you better ergonomics.+--+-- == Interleaving of loop iterations+--+-- If we look at the cross product of [1,2,3], [4,5,6], the streams being+-- combined using 'append' are the @for@ loop iterations as follows:+--+-- @+-- (1,4) (1,5) (1,6) -- first iteration of the outer loop+-- (2,4) (2,5) (2,6) -- second iteration of the outer loop+-- (3,4) (3,5) (3,6) -- third iteration of the outer loop+-- @+--+-- The result is equivalent to sequentially appending all the iterations of the+-- nested @for@ loop:+--+-- @+-- [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]+-- @+--+-- == Logic Programming+--+-- 'Nested' also serves the purpose of 'LogicT' type from the 'logict' package.+-- The @MonadLogic@ operations can be implemented using the available stream+-- operations. For example, 'uncons' is @msplit@, 'interleave' corresponds to+-- the @interleave@ operation of MonadLogic, 'fairConcatFor' is the+-- fair bind (@>>-@) operation. The 'FairNested' type provides a monad with fair+-- bind.+--+-- == Related Functionality+--+-- A custom type can be created using 'bfsConcatMap' as the monad bind+-- operation then the nested loops would get inverted - the innermost loop+-- becomes the outermost and vice versa.+--+-- See 'FairNested' if you want all the streams to get equal chance to execute+-- even if they are infinite.+newtype Nested m a = Nested {unNested :: StreamK m a}+ deriving (Functor, Semigroup, Monoid, Foldable)++{-# DEPRECATED CrossStreamK "Use Nested instead." #-}+type CrossStreamK = Nested++{-# DEPRECATED mkCross "Use Nested instead." #-}+-- | Wrap the 'StreamK' type in a 'Nested' newtype to enable cross+-- product style applicative and monad instances.+--+-- This is a type level operation with no runtime overhead.+{-# INLINE mkCross #-}+mkCross :: StreamK m a -> Nested m a+mkCross = Nested++-- | Unwrap the 'StreamK' type from 'CrossStreamK' newtype.+--+-- This is a type level operation with no runtime overhead.+{-# INLINE unCross #-}+unCross :: CrossStreamK m a -> StreamK m a+unCross = unNested++-- Pure (Identity monad) stream instances+deriving instance Traversable (Nested Identity)+deriving instance IsList (Nested Identity a)+deriving instance (a ~ Char) => IsString (Nested Identity a)+-- deriving instance Eq a => Eq (Nested Identity a)+-- deriving instance Ord a => Ord (Nested Identity a)++-- Do not use automatic derivation for this to show as "fromList" rather than+-- "fromList Identity".+instance Show a => Show (Nested Identity a) where+ {-# INLINE show #-}+ show (Nested xs) = show xs++instance Read a => Read (Nested Identity a) where+ {-# INLINE readPrec #-}+ readPrec = fmap Nested readPrec++------------------------------------------------------------------------------+-- Applicative+------------------------------------------------------------------------------++-- Note: we need to define all the typeclass operations because we want to+-- INLINE them.+instance Monad m => Applicative (Nested m) where+ {-# INLINE pure #-}+ pure x = Nested (fromPure x)++ {-# INLINE (<*>) #-}+ (Nested s1) <*> (Nested s2) =+ Nested (crossApply s1 s2)++ {-# INLINE liftA2 #-}+ liftA2 f x = (<*>) (fmap f x)++ {-# INLINE (*>) #-}+ (Nested s1) *> (Nested s2) =+ Nested (crossApplySnd s1 s2)++ {-# INLINE (<*) #-}+ (Nested s1) <* (Nested s2) =+ Nested (crossApplyFst s1 s2)++------------------------------------------------------------------------------+-- Monad+------------------------------------------------------------------------------++instance Monad m => Monad (Nested m) where+ return = pure++ -- Benchmarks better with CPS bind and pure:+ -- Prime sieve (25x)+ -- n binds, breakAfterSome, filterAllIn, state transformer (~2x)+ --+ {-# INLINE (>>=) #-}+ (>>=) (Nested m) f =+ Nested (bindWith append m (unNested . f))++ {-# INLINE (>>) #-}+ (>>) = (*>)++------------------------------------------------------------------------------+-- Alternative and MonadPlus+------------------------------------------------------------------------------++instance (Monad m) => Fail.MonadFail (Nested m) where+ fail _ = inline mempty++instance (Monad m, Functor m) => Alternative (Nested m) where+ empty = inline mempty+ (<|>) = inline mappend++instance (Monad m) => MonadPlus (Nested m) where+ mzero = inline mempty+ mplus = inline mappend++------------------------------------------------------------------------------+-- Transformers+------------------------------------------------------------------------------++instance (MonadIO m) => MonadIO (Nested m) where+ liftIO x = Nested (fromEffect $ liftIO x)++instance MonadTrans Nested where+ {-# INLINE lift #-}+ lift x = Nested (fromEffect x)++instance (MonadThrow m) => MonadThrow (Nested m) where+ throwM = lift . throwM++------------------------------------------------------------------------------+-- Stream with a fair cross product style monad instance+------------------------------------------------------------------------------++-- XXX We can fix the termination issues by adding a "skip" continuation in the+-- stream. Adding a "block" continuation can allow for blocking IO. Both of+-- these together will provide a co-operative scheduling. However, adding skip+-- will regress performance in heavy filtering cases. If that's important we+-- can create another type StreamK' for skip continuation. That type can use+-- conversion from Stream type for everything except append and concatMap.++-- | 'FairNested' is like the 'Nested' type but explores the depth and breadth of+-- the cross product grid equally, so that each of the stream being crossed is+-- consumed equally. It can be used to nest infinite streams without starving+-- one due to the other.+--+-- >>> mk = StreamK.FairNested . StreamK.fromStream . Stream.fromList+-- >>> un = Stream.toList . StreamK.toStream . StreamK.unFairNested+--+-- == Looping+--+-- A single stream case is equivalent to 'Nested', it is a simple @for@ loop+-- over the stream:+--+-- >>> :{+-- un $ do+-- x <- mk [1,2] -- for each element in the stream+-- return x+-- :}+-- [1,2]+--+-- == Fair Nested Looping+--+-- Multiple streams nest like @for@ loops. The result is a cross product of the+-- streams. However, the ordering of the results of the cross product is such+-- that each stream gets consumed equally. In other words, inner iterations of+-- a nested loop get the same priority as the outer iterations. Inner+-- iterations do not finish completely before the outer iterations start.+--+-- >>> :{+-- un $ do+-- x <- mk [1,2,3] -- outer, for each element in the stream+-- y <- mk [4,5,6] -- inner, for each element in the stream+-- -- Perform the following actions for each x, for each y+-- return (x, y)+-- :}+-- [(1,4),(1,5),(2,4),(1,6),(2,5),(3,4),(2,6),(3,5),(3,6)]+--+-- == Nesting Infinite Streams+--+-- Example with infinite streams. Print all pairs in the cross product with sum+-- less than a specified number.+--+-- >>> :{+-- Stream.toList+-- $ Stream.takeWhile (\(x,y) -> x + y < 6)+-- $ StreamK.toStream $ StreamK.unFairNested+-- $ do+-- x <- mk [1..] -- infinite stream+-- y <- mk [1..] -- infinite stream+-- return (x, y)+-- :}+-- [(1,1),(1,2),(2,1),(1,3),(2,2),(3,1),(1,4),(2,3),(3,2),(4,1)]+--+-- == How it works?+--+-- 'FairNested' uses 'fairConcatFor' as the monad bind operation.+-- If we look at the cross product of [1,2,3], [4,5,6], the streams being+-- combined using 'concatMapDigaonal' are the sequential loop iterations:+--+-- @+-- (1,4) (1,5) (1,6) -- first iteration of the outer loop+-- (2,4) (2,5) (2,6) -- second iteration of the outer loop+-- (3,4) (3,5) (3,6) -- third iteration of the outer loop+-- @+--+-- The result is a triangular or diagonal traversal of these iterations:+--+-- @+-- [(1,4),(1,5),(2,4),(1,6),(2,5),(3,4),(2,6),(3,5),(3,6)]+-- @+--+-- == Associativity Issues+--+-- WARNING! The FairNested monad breaks the associativity law intentionally for+-- usefulness, it is associative only up to permutation equivalence. In this+-- monad the association order of statements might make a difference to the+-- ordering of the results because of changing the way in which streams are+-- scheduled. The same issues arise when you use the 'interleave' operation+-- directly, association order matters - however, here it can be more subtle as+-- the programmer may not see it directly.+--+-- >>> un (mk [1,2] >>= (\x -> mk [x, x + 1] >>= (\y -> mk [y, y + 2])))+-- [1,3,2,2,4,4,3,5]+-- >>> un ((mk [1,2] >>= (\x -> mk [x, x + 1])) >>= (\y -> mk [y, y + 2]))+-- [1,3,2,4,2,4,3,5]+--+-- This type is designed to be used for use cases where ordering of results+-- does not matter, we want to explore different streams to find specific+-- results, but the order in which we find or present the results may not be+-- important. Re-association of statements in this monad may change how different+-- branches are scheduled, which may change the scheduling priority of some+-- streams over others, this may end up starving some branches - in the worst+-- case some branches may be fully starved by some infinite branches producing+-- nothing - resulting in a non-terminating program.+--+-- == Non-Termination Cases+--+-- If an infinite stream that does not produce a value at all is interleaved+-- with another stream then the entire computation gets stuck forever because+-- the interleave operation schedules the second stream only after the first+-- stream yields a value. This can lead to non-terminating programs, an example+-- is provided below.+--+-- >>> :{+-- toS = StreamK.toStream . StreamK.unFairNested+-- odds x = mk (if x then [1,3..] else [2,4..])+-- filterEven x = if even x then pure x else StreamK.FairNested StreamK.nil+-- :}+--+-- When writing code with do notation, keep in mind that when we bind a+-- variable to a monadic value, all the following code that depends on this+-- variable is associated together and connected to it via a monad bind.+-- Consider the following code:+--+-- >>> :{+-- evens = toS $ do+-- r <- mk [True,False]+-- -- The next two statements depending on the variable r are associated+-- -- together and bound to the previous line using a monad bind.+-- x <- odds r+-- filterEven x+-- :}+--+-- This code does not terminate because, when r is True, @odds@ and+-- @filterEven@ together constitute an infinite inner loop, coninuously working+-- but not yielding any value at all, this stream is interleaved with the outer+-- loop, therefore, the outer loop does not get a chance to move to the next+-- iteration.+--+-- But the following code works as expected:+--+-- >>> :{+-- evens = toS $ do+-- x <- mk [True,False] >>= odds+-- filterEven x+-- :}+--+-- >>> Stream.toList $ Stream.take 3 $ evens+-- [2,4,6]+--+-- This works because both the lists being interleaved continue to produce+-- values in the outer loop and the inner loop keeps filtering them.+--+-- Care should be taken how you write your program, keep in mind the scheduling+-- implications. To avoid such scheduling problems in the serial FairNested type+-- use the concurrent version i.e. FairParallel described in+-- 'Streamly.Data.Stream.MkType' module. Due to concurrent evaluation each+-- branch will make progress even if one is an infinite loop producing nothing.+--+-- == Related Operations+--+-- We can create a custom type with 'concatMapWith' 'interleave' as the monad+-- bind operation then the inner loop iterations get exponentially more+-- priority over the outer iterations of the nested loop. This is not fully+-- fair, it is biased - this is exactly how the logic-t and list-t+-- implementation of fair bind works.++newtype FairNested m a = FairNested {unFairNested :: StreamK m a}+ deriving (Functor, Foldable)++-- Pure (Identity monad) stream instances+deriving instance Traversable (FairNested Identity)+deriving instance IsList (FairNested Identity a)+deriving instance (a ~ Char) => IsString (FairNested Identity a)+-- deriving instance Eq a => Eq (FairNested Identity a)+-- deriving instance Ord a => Ord (FairNested Identity a)++-- Do not use automatic derivation for this to show as "fromList" rather than+-- "fromList Identity".+instance Show a => Show (FairNested Identity a) where+ {-# INLINE show #-}+ show (FairNested xs) = show xs++instance Read a => Read (FairNested Identity a) where+ {-# INLINE readPrec #-}+ readPrec = fmap FairNested readPrec++------------------------------------------------------------------------------+-- Applicative+------------------------------------------------------------------------------++-- Note: we need to define all the typeclass operations because we want to+-- INLINE them.+instance Monad m => Applicative (FairNested m) where+ {-# INLINE pure #-}+ pure x = FairNested (fromPure x)++ -- XXX implement more efficient version of these+ (<*>) = ap+ {-+ {-# INLINE (<*>) #-}+ (FairNested s1) <*> (FairNested s2) =+ FairNested (crossApply s1 s2)++ {-# INLINE liftA2 #-}+ liftA2 f x = (<*>) (fmap f x)++ {-# INLINE (*>) #-}+ (FairNested s1) *> (FairNested s2) =+ FairNested (crossApplySnd s1 s2)++ {-# INLINE (<*) #-}+ (FairNested s1) <* (FairNested s2) =+ FairNested (crossApplyFst s1 s2)+ -}++------------------------------------------------------------------------------+-- Monad+------------------------------------------------------------------------------++instance Monad m => Monad (FairNested m) where+ return = pure++ {-# INLINE (>>=) #-}+ (>>=) (FairNested m) f = FairNested (fairConcatMap (unFairNested . f) m)++ -- {-# INLINE (>>) #-}+ -- (>>) = (*>)++------------------------------------------------------------------------------+-- Transformers+------------------------------------------------------------------------------++instance (MonadIO m) => MonadIO (FairNested m) where+ liftIO x = FairNested (fromEffect $ liftIO x)++instance MonadTrans FairNested where+ {-# INLINE lift #-}+ lift x = FairNested (fromEffect x)++instance (MonadThrow m) => MonadThrow (FairNested m) where+ throwM = lift . throwM
src/Streamly/Internal/Data/Time/Clock.hs view
@@ -9,8 +9,7 @@ module Streamly.Internal.Data.Time.Clock ( -- * System clock- Clock(..)- , getTime+ module Streamly.Internal.Data.Time.Clock.Type -- * Async clock , asyncClock@@ -30,12 +29,13 @@ import Control.Concurrent (threadDelay, ThreadId) import Control.Concurrent.MVar (MVar, newEmptyMVar, takeMVar, tryPutMVar) import Control.Monad (forever, when, void)-import Streamly.Internal.Data.Time.Clock.Type (Clock(..), getTime) import Streamly.Internal.Data.Time.Units (MicroSecond64(..), fromAbsTime, addToAbsTime, toRelTime) import Streamly.Internal.Control.ForkIO (forkIOManaged) -import qualified Streamly.Internal.Data.IORef.Unboxed as Unboxed+import qualified Streamly.Internal.Data.IORef as Unboxed++import Streamly.Internal.Data.Time.Clock.Type ------------------------------------------------------------------------------ -- Async clock
src/Streamly/Internal/Data/Time/Clock/Type.hsc view
@@ -228,7 +228,7 @@ #elif HS_CLOCK_OSX -- XXX perform error checks inside c implementation-foreign import ccall+foreign import ccall unsafe clock_gettime_darwin :: #{type clock_id_t} -> Ptr TimeSpec -> IO () {-# INLINABLE getTime #-}@@ -238,10 +238,10 @@ #elif HS_CLOCK_WINDOWS -- XXX perform error checks inside c implementation-foreign import ccall clock_gettime_win32_monotonic :: Ptr TimeSpec -> IO ()-foreign import ccall clock_gettime_win32_realtime :: Ptr TimeSpec -> IO ()-foreign import ccall clock_gettime_win32_processtime :: Ptr TimeSpec -> IO ()-foreign import ccall clock_gettime_win32_threadtime :: Ptr TimeSpec -> IO ()+foreign import ccall unsafe clock_gettime_win32_monotonic :: Ptr TimeSpec -> IO ()+foreign import ccall unsafe clock_gettime_win32_realtime :: Ptr TimeSpec -> IO ()+foreign import ccall unsafe clock_gettime_win32_processtime :: Ptr TimeSpec -> IO ()+foreign import ccall unsafe clock_gettime_win32_threadtime :: Ptr TimeSpec -> IO () {-# INLINABLE getTime #-} getTime :: Clock -> IO AbsTime
src/Streamly/Internal/Data/Time/Units.hs view
@@ -1,6 +1,3 @@-{-# LANGUAGE TypeInType #-}-{-# LANGUAGE UnboxedTuples #-}- -- | -- Module : Streamly.Internal.Data.Time.Units -- Copyright : (c) 2019 Composewell Technologies@@ -52,7 +49,7 @@ import Data.Int import Foreign.Storable (Storable)-import Streamly.Internal.Data.Unboxed (Unbox)+import Streamly.Internal.Data.Unbox (Unbox) import Streamly.Internal.Data.Time.TimeSpec -------------------------------------------------------------------------------
+ src/Streamly/Internal/Data/Unbox.hs view
@@ -0,0 +1,923 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+-- Must come after TypeFamilies, otherwise it is re-enabled.+-- MonoLocalBinds enabled by TypeFamilies causes perf regressions in general.+{-# LANGUAGE NoMonoLocalBinds #-}+{-# LANGUAGE UnboxedTuples #-}+{-# LANGUAGE UndecidableInstances #-}+{- HLINT ignore -}++-- |+-- Module : Streamly.Internal.Data.Unbox+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+module Streamly.Internal.Data.Unbox+ (+ -- ** Unbox type class+ Unbox(..)++ -- ** Peek and poke utilities+ , BoundedPtr (..)+ -- Peek+ , Peeker (..)+ , read+ , readUnsafe+ , skipByte+ , runPeeker+ -- Poke+ , pokeBoundedPtrUnsafe+ , pokeBoundedPtr++ -- ** Generic Deriving+ , PeekRep(..)+ , PokeRep(..)+ , SizeOfRep(..)+ , genericSizeOf+ , genericPeekByteIndex+ , genericPokeByteIndex+ ) where++#include "MachDeps.h"+#include "HsBaseConfig.h"+#include "ArrayMacros.h"++import Control.Monad (void, when)+import Data.Complex (Complex((:+)))+import Data.Functor ((<&>))+import Data.Functor.Const (Const(..))+import Data.Functor.Identity (Identity(..))+import Data.Kind (Type)+import Data.Proxy (Proxy (..))+import Foreign.C.Types (CChar(..), CWchar(..))+import Foreign.Ptr (IntPtr(..), WordPtr(..))+import GHC.Base (IO(..))+import GHC.Fingerprint.Type (Fingerprint(..))+import GHC.Int (Int16(..), Int32(..), Int64(..), Int8(..))+import GHC.Real (Ratio(..))+import GHC.Stable (StablePtr(..))+import GHC.Word (Word16(..), Word32(..), Word64(..), Word8(..))+#if MIN_VERSION_base(4,21,0)+import GHC.IO.SubSystem (IoSubSystem (..))+#elif MIN_VERSION_base(4,15,0)+import GHC.RTS.Flags (IoSubSystem(..))+#endif+import Streamly.Internal.Data.Builder (Builder (..))++import GHC.Generics+import GHC.Exts+import GHC.TypeLits+import Prelude hiding (read)++import Streamly.Internal.Data.MutByteArray.Type (MutByteArray(..))+#ifdef DEBUG+import qualified Streamly.Internal.Data.MutByteArray.Type as MutByteArray+#endif++--------------------------------------------------------------------------------+-- The Unbox type class+--------------------------------------------------------------------------------++-- XXX generate error if the size is < 1++-- = Design notes =+--+-- == Fixed length data types ==+--+-- The main goal of the Unbox type class is to be used in arrays. Invariants+-- for the sizeOf value required for use in arrays:+--+-- * size is independent of the value, it is determined by the type only. So+-- that we can store values of the same type in fixed length array cells.+-- * recursive data types cannot be fixed length, therefore, cannot be+-- serialized using this type class.+-- * size cannot be zero. So that the length of an array storing the element+-- and the number of elements can be related.+--+-- Note, for general serializable types the size cannot be fixed e.g. we may+-- want to serialize a list. This type class can be considered a special case+-- of a more general serialization type class.+--+-- == Stream vs Array ==+--+-- In theory we could convert a type to and from a byte stream and use that+-- to implement boxing, unboxing. But composing a stream from parts of the+-- structure is much more inefficient than writing them to a memory location.+-- However, it should be possible to efficiently parse a Haskell type from an+-- array using chunk folds.+--+-- Also, this type class allows each primitive type to have its own specific+-- efficient implementation to read and write the type to the mutable byte+-- array using special GHC byte array operations. For example, see the Unbox+-- instances of Char, Int, Word types.+--+-- == MutableByteArray vs ForeignPtr ==+--+-- The Unbox typeclass uses MutableByteArray but could use ForeignPtr or+-- any other representation of memory. We could make this a multiparameter type+-- class if necessary.+--+-- If the type class would have to support reading and writing to a Ptr as well,+-- basically what Storable does. We will also need to touch the anchoring ptr at+-- the right points which is prone to errors. However, it should be simple to+-- implement unmanaged/read-only memory arrays by allowing a Ptr type in+-- ArrayContents, though it would require all instances to support reading from+-- a Ptr.+--+-- == Byte Offset vs Element Index ==+--+-- There is a reason for using byte offset instead of element index in read and+-- write operations in the type class. If we use element index, slicing of the+-- array becomes rigid. We can only slice the array at addresses that are+-- aligned with the type, therefore, we cannot slice at misaligned location and+-- then cast the slice into another type which does not necessarily align with+-- the original type.+--+-- == Alignment ==+--+-- As a side note, there seem to be no performance advantage of alignment+-- anymore, see+-- https://lemire.me/blog/2012/05/31/data-alignment-for-speed-myth-or-reality/+--+-- = Unboxed Records =+--+-- Unboxed types can be treated as unboxed records. We can provide a more+-- convenient API to access different parts from the Unboxed representation+-- without having to unbox the entire type. The type can have nested parts+-- therefore, we will need a general way (some sort of lenses) to address the+-- parts.+--+-- = Lazy Boxing =+--+-- When converting an unboxed representation to a boxed representation we can+-- use lazy construction. Each constructor of the constructed computation may+-- just hold a lazy computation to actually construct it on demand. This could+-- be useful for larger structures where we may need only small parts of it.+--+-- Same thing can be done for serialize type class as well but it will require+-- size fields at each nesting level, aggregating the size upwards.++-- | The 'Unbox' type class provides operations for serialization (unboxing)+-- and deserialization (boxing) of fixed-length, non-recursive Haskell data+-- types to and from their byte stream representation.+--+-- Unbox uses fixed size encoding, therefore, size is independent of the value,+-- it must be determined solely by the type. This restriction makes types with+-- 'Unbox' instances suitable for storing in arrays. Note that sum types may+-- have multiple constructors of different sizes, the size of a sum type is+-- computed as the maximum required by any constructor.+--+-- The 'peekAt' operation reads as many bytes from the mutable byte+-- array as the @size@ of the data type and builds a Haskell data type from+-- these bytes. 'pokeAt' operation converts a Haskell data type to its+-- binary representation which consists of @size@ bytes and then stores+-- these bytes into the mutable byte array. These operations do not check the+-- bounds of the array, the user of the type class is expected to check the+-- bounds before peeking or poking.+--+-- IMPORTANT: The serialized data's byte ordering remains the same as the host+-- machine's byte order. Therefore, it can not be deserialized from host+-- machines with a different byte ordering.+--+-- Instances can be derived via Generics, Template Haskell, or written+-- manually. Note that the data type must be non-recursive. WARNING! Generic+-- and Template Haskell deriving, both hang for recursive data types. Deriving+-- via Generics is more convenient but Template Haskell should be preferred+-- over Generics for the following reasons:+--+-- 1. Instances derived via Template Haskell provide better and more reliable+-- performance.+-- 2. Generic deriving allows only 256 fields or constructor tags whereas+-- template Haskell has no limit.+--+-- Here is an example, for deriving an instance of this type class using+-- generics:+--+-- >>> import GHC.Generics (Generic)+-- >>> :{+-- data Object = Object+-- { _int0 :: Int+-- , _int1 :: Int+-- } deriving Generic+-- :}+--+-- >>> import Streamly.Data.MutByteArray (Unbox(..))+-- >>> instance Unbox Object+--+-- To derive the instance via Template Haskell:+--+-- @+-- import Streamly.Data.MutByteArray (deriveUnbox)+-- \$(deriveUnbox [d|instance Unbox Object|])+-- @+--+-- See 'Streamly.Data.MutByteArray.deriveUnbox' for more information on deriving+-- using Template Haskell.+--+-- If you want to write the instance manually:+--+-- >>> :{+-- instance Unbox Object where+-- sizeOf _ = 16+-- peekAt i arr = do+-- -- Check the array bounds+-- x0 <- peekAt i arr+-- x1 <- peekAt (i + 8) arr+-- return $ Object x0 x1+-- pokeAt i arr (Object x0 x1) = do+-- -- Check the array bounds+-- pokeAt i arr x0+-- pokeAt (i + 8) arr x1+-- :}+--+class Unbox a where+ -- | Get the size. Size cannot be zero, should be at least 1 byte.+ sizeOf :: Proxy a -> Int++ {-# INLINE sizeOf #-}+ default sizeOf :: (SizeOfRep (Rep a)) => Proxy a -> Int+ sizeOf = genericSizeOf++ -- | @peekAt byte-offset array@ reads an element of type @a@ from the+ -- given byte offset in the array.+ --+ -- IMPORTANT: The implementation of this interface may not check the bounds+ -- of the array, the caller must not assume that.+ peekAt :: Int -> MutByteArray -> IO a++ {-# INLINE peekAt #-}+ default peekAt :: (Generic a, PeekRep (Rep a)) =>+ Int -> MutByteArray -> IO a+ peekAt i arr = genericPeekByteIndex arr i++ peekByteIndex :: Int -> MutByteArray -> IO a+ peekByteIndex = peekAt++ -- | @pokeAt byte-offset array@ writes an element of type @a@ to the+ -- given byte offset in the array.+ --+ -- IMPORTANT: The implementation of this interface may not check the bounds+ -- of the array, the caller must not assume that.+ pokeAt :: Int -> MutByteArray -> a -> IO ()++ pokeByteIndex :: Int -> MutByteArray -> a -> IO ()+ pokeByteIndex = pokeAt++ {-# INLINE pokeAt #-}+ default pokeAt :: (Generic a, PokeRep (Rep a)) =>+ Int -> MutByteArray -> a -> IO ()+ pokeAt i arr = genericPokeByteIndex arr i++{-# DEPRECATED peekByteIndex "Use peekAt." #-}+{-# DEPRECATED pokeByteIndex "Use pokeAt." #-}++-- _size is the length from array start to the last accessed byte.+{-# INLINE checkBounds #-}+checkBounds :: String -> Int -> MutByteArray -> IO ()+checkBounds _label _size _arr = do+#ifdef DEBUG+ sz <- MutByteArray.length _arr+ if (_size > sz)+ then error+ $ _label+ ++ ": accessing array at offset = "+ ++ show (_size - 1)+ ++ " max valid offset = " ++ show (sz - 1)+ else return ()+#else+ return ()+#endif++#define DERIVE_UNBOXED(_type, _constructor, _readArray, _writeArray, _sizeOf) \+instance Unbox _type where { \+; {-# INLINE peekAt #-} \+; peekAt off@(I# n) arr@(MutByteArray mbarr) = \+ checkBounds "peek" (off + sizeOf (Proxy :: Proxy _type)) arr \+ >> (IO $ \s -> \+ case _readArray mbarr n s of \+ { (# s1, i #) -> (# s1, _constructor i #) }) \+; {-# INLINE pokeAt #-} \+; pokeAt off@(I# n) arr@(MutByteArray mbarr) (_constructor val) = \+ checkBounds "poke" (off + sizeOf (Proxy :: Proxy _type)) arr \+ >> (IO $ \s -> (# _writeArray mbarr n val s, () #)) \+; {-# INLINE sizeOf #-} \+; sizeOf _ = _sizeOf \+}++#define DERIVE_WRAPPED_UNBOX(_constraint, _type, _constructor, _innerType) \+instance _constraint Unbox _type where \+; {-# INLINE peekAt #-} \+; peekAt i arr = \+ checkBounds "peek" (i + sizeOf (Proxy :: Proxy _type)) arr \+ >> _constructor <$> peekAt i arr \+; {-# INLINE pokeAt #-} \+; pokeAt i arr (_constructor a) = \+ checkBounds "poke" (i + sizeOf (Proxy :: Proxy _type)) arr \+ >> pokeAt i arr a \+; {-# INLINE sizeOf #-} \+; sizeOf _ = SIZE_OF(_innerType)++#define DERIVE_BINARY_UNBOX(_constraint, _type, _constructor, _innerType) \+instance _constraint Unbox _type where { \+; {-# INLINE peekAt #-} \+; peekAt i arr = \+ checkBounds "peek" (i + sizeOf (Proxy :: Proxy _type)) arr >> \+ peekAt i arr >>= \+ (\p1 -> peekAt (i + SIZE_OF(_innerType)) arr <&> _constructor p1) \+; {-# INLINE pokeAt #-} \+; pokeAt i arr (_constructor p1 p2) = \+ checkBounds "poke" (i + sizeOf (Proxy :: Proxy _type)) arr >> \+ pokeAt i arr p1 >> \+ pokeAt (i + SIZE_OF(_innerType)) arr p2 \+; {-# INLINE sizeOf #-} \+; sizeOf _ = 2 * SIZE_OF(_innerType) \+}++-------------------------------------------------------------------------------+-- Unbox instances for primitive types+-------------------------------------------------------------------------------++DERIVE_UNBOXED( Char+ , C#+ , readWord8ArrayAsWideChar#+ , writeWord8ArrayAsWideChar#+ , SIZEOF_HSCHAR)++DERIVE_UNBOXED( Int8+ , I8#+ , readInt8Array#+ , writeInt8Array#+ , 1)++DERIVE_UNBOXED( Int16+ , I16#+ , readWord8ArrayAsInt16#+ , writeWord8ArrayAsInt16#+ , 2)++DERIVE_UNBOXED( Int32+ , I32#+ , readWord8ArrayAsInt32#+ , writeWord8ArrayAsInt32#+ , 4)++DERIVE_UNBOXED( Int+ , I#+ , readWord8ArrayAsInt#+ , writeWord8ArrayAsInt#+ , SIZEOF_HSINT)++DERIVE_UNBOXED( Int64+ , I64#+ , readWord8ArrayAsInt64#+ , writeWord8ArrayAsInt64#+ , 8)++DERIVE_UNBOXED( Word+ , W#+ , readWord8ArrayAsWord#+ , writeWord8ArrayAsWord#+ , SIZEOF_HSWORD)++DERIVE_UNBOXED( Word8+ , W8#+ , readWord8Array#+ , writeWord8Array#+ , 1)++DERIVE_UNBOXED( Word16+ , W16#+ , readWord8ArrayAsWord16#+ , writeWord8ArrayAsWord16#+ , 2)++DERIVE_UNBOXED( Word32+ , W32#+ , readWord8ArrayAsWord32#+ , writeWord8ArrayAsWord32#+ , 4)++DERIVE_UNBOXED( Word64+ , W64#+ , readWord8ArrayAsWord64#+ , writeWord8ArrayAsWord64#+ , 8)++DERIVE_UNBOXED( Double+ , D#+ , readWord8ArrayAsDouble#+ , writeWord8ArrayAsDouble#+ , SIZEOF_HSDOUBLE)++DERIVE_UNBOXED( Float+ , F#+ , readWord8ArrayAsFloat#+ , writeWord8ArrayAsFloat#+ , SIZEOF_HSFLOAT)++-------------------------------------------------------------------------------+-- Unbox instances for derived types+-------------------------------------------------------------------------------++DERIVE_UNBOXED( (StablePtr a)+ , StablePtr+ , readWord8ArrayAsStablePtr#+ , writeWord8ArrayAsStablePtr#+ , SIZEOF_HSSTABLEPTR)++DERIVE_UNBOXED( (Ptr a)+ , Ptr+ , readWord8ArrayAsAddr#+ , writeWord8ArrayAsAddr#+ , SIZEOF_HSPTR)++DERIVE_UNBOXED( (FunPtr a)+ , FunPtr+ , readWord8ArrayAsAddr#+ , writeWord8ArrayAsAddr#+ , SIZEOF_HSFUNPTR)++DERIVE_WRAPPED_UNBOX(,IntPtr,IntPtr,Int)+DERIVE_WRAPPED_UNBOX(,WordPtr,WordPtr,Word)+DERIVE_WRAPPED_UNBOX(Unbox a =>,(Identity a),Identity,a)+#if MIN_VERSION_base(4,14,0)+DERIVE_WRAPPED_UNBOX(Unbox a =>,(Down a),Down,a)+#endif+DERIVE_WRAPPED_UNBOX(Unbox a =>,(Const a b),Const,a)++-- XXX Add more CTypes+DERIVE_WRAPPED_UNBOX(,CChar,CChar,HTYPE_CHAR)+DERIVE_WRAPPED_UNBOX(,CWchar,CWchar,HTYPE_WCHAR_T)++DERIVE_BINARY_UNBOX(forall a. Unbox a =>,(Complex a),(:+),a)+DERIVE_BINARY_UNBOX(forall a. Unbox a =>,(Ratio a),(:%),a)+DERIVE_BINARY_UNBOX(,Fingerprint,Fingerprint,Word64)++instance Unbox () where++ {-# INLINE peekAt #-}+ peekAt i arr =+ checkBounds "peek ()" (i + sizeOf (Proxy :: Proxy ())) arr >> return ()++ {-# INLINE pokeAt #-}+ pokeAt i arr _ =+ checkBounds "poke ()" (i + sizeOf (Proxy :: Proxy ())) arr >> return ()++ {-# INLINE sizeOf #-}+ sizeOf _ = 1++#if MIN_VERSION_base(4,15,0)++instance Unbox IoSubSystem where++ {-# INLINE peekAt #-}+ peekAt i arr =+ checkBounds+ "peek IoSubSystem" (i + sizeOf (Proxy :: Proxy IoSubSystem)) arr+ >> toEnum <$> peekAt i arr++ {-# INLINE pokeAt #-}+ pokeAt i arr a =+ checkBounds+ "poke IoSubSystem" (i + sizeOf (Proxy :: Proxy IoSubSystem)) arr+ >> pokeAt i arr (fromEnum a)++ {-# INLINE sizeOf #-}+ sizeOf _ = sizeOf (Proxy :: Proxy Int)+#endif++instance Unbox Bool where++ {-# INLINE peekAt #-}+ peekAt i arr = do+ checkBounds "peek Bool" (i + sizeOf (Proxy :: Proxy Bool)) arr+ res <- peekAt i arr+ return $ res /= (0 :: Int8)++ {-# INLINE pokeAt #-}+ pokeAt i arr a =+ checkBounds "poke Bool" (i + sizeOf (Proxy :: Proxy Bool)) arr+ >> if a+ then pokeAt i arr (1 :: Int8)+ else pokeAt i arr (0 :: Int8)++ {-# INLINE sizeOf #-}+ sizeOf _ = 1++--------------------------------------------------------------------------------+-- Generic deriving+--------------------------------------------------------------------------------++-- Utilities to build or parse a type safely and easily.++-- XXX Use Array instead.++-- | A location inside a mutable byte array with the bound of the array. Is it+-- cheaper to just get the bound using the size of the array whenever needed?+data BoundedPtr =+ BoundedPtr+ MutByteArray -- byte array+ Int -- current pos+ Int -- position after end++--------------------------------------------------------------------------------+-- Peeker monad+--------------------------------------------------------------------------------++-- | Chains peek functions that pass the current position to the next function+newtype Peeker a = Peeker (Builder BoundedPtr IO a)+ deriving (Functor, Applicative, Monad)++{-# INLINE readUnsafe #-}+readUnsafe :: Unbox a => Peeker a+readUnsafe = Peeker (Builder step)++ where++ {-# INLINE step #-}+ step :: forall a. Unbox a => BoundedPtr -> IO (a, BoundedPtr)+ step (BoundedPtr arr pos end) = do+ let next = pos + sizeOf (Proxy :: Proxy a)+#ifdef DEBUG+ when (next > end)+ $ error $ "readUnsafe: reading beyond limit. next = "+ ++ show next+ ++ " end = " ++ show end+#endif+ r <- peekAt pos arr+ return (r, BoundedPtr arr next end)++{-# INLINE read #-}+read :: Unbox a => Peeker a+read = Peeker (Builder step)++ where++ {-# INLINE step #-}+ step :: forall a. Unbox a => BoundedPtr -> IO (a, BoundedPtr)+ step (BoundedPtr arr pos end) = do+ let next = pos + sizeOf (Proxy :: Proxy a)+ when (next > end)+ $ error $ "read: reading beyond limit. next = "+ ++ show next+ ++ " end = " ++ show end+ r <- peekAt pos arr+ return (r, BoundedPtr arr next end)++{-# INLINE skipByte #-}+skipByte :: Peeker ()+skipByte = Peeker (Builder step)++ where++ {-# INLINE step #-}+ step :: BoundedPtr -> IO ((), BoundedPtr)+ step (BoundedPtr arr pos end) = do+ let next = pos + 1+#ifdef DEBUG+ when (next > end)+ $ error $ "skipByte: reading beyond limit. next = "+ ++ show next+ ++ " end = " ++ show end+#endif+ return ((), BoundedPtr arr next end)++{-# INLINE runPeeker #-}+runPeeker :: Peeker a -> BoundedPtr -> IO a+runPeeker (Peeker (Builder f)) ptr = fmap fst (f ptr)++--------------------------------------------------------------------------------+-- Poke utilities+--------------------------------------------------------------------------------++-- XXX Use MutArray instead of BoundedPtr.++-- XXX Using a Poker monad may be useful when we have to compute the size to be+-- poked as we go and then poke the size at a previous location. For variable+-- sized object serialization we may also want to reallocate the array and+-- return the newly allocated array in the output.++-- Does not check writing beyond bound.+{-# INLINE pokeBoundedPtrUnsafe #-}+pokeBoundedPtrUnsafe :: forall a. Unbox a => a -> BoundedPtr -> IO BoundedPtr+pokeBoundedPtrUnsafe a (BoundedPtr arr pos end) = do+ let next = pos + sizeOf (Proxy :: Proxy a)+#ifdef DEBUG+ when (next > end)+ $ error $ "pokeBoundedPtrUnsafe: reading beyond limit. next = "+ ++ show next+ ++ " end = " ++ show end+#endif+ pokeAt pos arr a+ return (BoundedPtr arr next end)++{-# INLINE pokeBoundedPtr #-}+pokeBoundedPtr :: forall a. Unbox a => a -> BoundedPtr -> IO BoundedPtr+pokeBoundedPtr a (BoundedPtr arr pos end) = do+ let next = pos + sizeOf (Proxy :: Proxy a)+ when (next > end) $ error "pokeBoundedPtr writing beyond limit"+ pokeAt pos arr a+ return (BoundedPtr arr next end)++--------------------------------------------------------------------------------+-- Check the number of constructors in a sum type+--------------------------------------------------------------------------------++-- Count the constructors of a sum type.+type family SumArity (a :: Type -> Type) :: Nat where+ SumArity (C1 _ _) = 1+ -- Requires UndecidableInstances+ SumArity (f :+: g) = SumArity f + SumArity g++type family TypeErrorMessage (a :: Symbol) :: Constraint where+ TypeErrorMessage a = TypeError ('Text a)++type family ArityCheck (b :: Bool) :: Constraint where+ ArityCheck 'True = ()+ ArityCheck 'False = TypeErrorMessage+ "Generic Unbox deriving does not support > 256 constructors."++-- Type constraint to restrict the sum type arity so that the constructor tag+-- can fit in a single byte.+-- Note that Arity starts from 1 and constructor tags start from 0. So if max+-- arity is 256 then max constructor tag would be 255.+-- XXX Use variable length encoding to support more than 256 constructors.+type MaxArity256 n = ArityCheck (n <=? 256)++--------------------------------------------------------------------------------+-- Generic Deriving of Unbox instance+--------------------------------------------------------------------------------++-- Unbox uses fixed size encoding, therefore, when a (sum) type has multiple+-- constructors, the size of the type is computed as the maximum required by+-- any constructor. Therefore, size is independent of the value, it can be+-- determined solely by the type.++-- | Implementation of sizeOf that works on the generic representation of an+-- ADT.+class SizeOfRep (f :: Type -> Type) where+ sizeOfRep :: f x -> Int++-- Meta information wrapper, go inside+instance SizeOfRep f => SizeOfRep (M1 i c f) where+ {-# INLINE sizeOfRep #-}+ sizeOfRep _ = sizeOfRep (undefined :: f x)++-- Primitive type "a".+instance Unbox a => SizeOfRep (K1 i a) where+ {-# INLINE sizeOfRep #-}+ sizeOfRep _ = sizeOf (Proxy :: Proxy a)++-- Void: data type without constructors. Values of this type cannot exist,+-- therefore the size is undefined. We should never be serializing structures+-- with elements of this type.+instance SizeOfRep V1 where+ {-# INLINE sizeOfRep #-}+ sizeOfRep = error "sizeOfRep: a value of a Void type must not exist"++-- Note that when a sum type has many unit constructors only a single byte is+-- required to encode the type as only the constructor tag is stored.+instance SizeOfRep U1 where+ {-# INLINE sizeOfRep #-}+ sizeOfRep _ = 0++-- Product type+instance (SizeOfRep f, SizeOfRep g) => SizeOfRep (f :*: g) where+ {-# INLINE sizeOfRep #-}+ sizeOfRep _ = sizeOfRep (undefined :: f x) + sizeOfRep (undefined :: g x)++-------------------------------------------------------------------------------++class SizeOfRepSum (f :: Type -> Type) where+ sizeOfRepSum :: f x -> Int++-- Constructor+instance SizeOfRep a => SizeOfRepSum (C1 c a) where+ {-# INLINE sizeOfRepSum #-}+ sizeOfRepSum = sizeOfRep++instance (SizeOfRepSum f, SizeOfRepSum g) => SizeOfRepSum (f :+: g) where+ {-# INLINE sizeOfRepSum #-}+ sizeOfRepSum _ =+ max (sizeOfRepSum (undefined :: f x)) (sizeOfRepSum (undefined :: g x))++-------------------------------------------------------------------------------++instance (MaxArity256 (SumArity (f :+: g)), SizeOfRepSum f, SizeOfRepSum g) =>+ SizeOfRep (f :+: g) where++ -- The size of a sum type is the max of any of the constructor size.+ -- sizeOfRepSum type class operation is used here instead of sizeOfRep so+ -- that we account the constructor index byte only for the first time and+ -- avoid including it for the subsequent sum constructors.+ {-# INLINE sizeOfRep #-}+ sizeOfRep _ =+ -- One byte for the constructor id and then the constructor value.+ sizeOf (Proxy :: Proxy Word8) ++ max (sizeOfRepSum (undefined :: f x))+ (sizeOfRepSum (undefined :: g x))++-- Unit: constructors without arguments.+-- Theoretically the size can be 0, but we use 1 to simplify the implementation+-- of an array of unit type elements. With a non-zero size we can count the number+-- of elements in the array based on the size of the array. Otherwise we will+-- have to store a virtual length in the array, but keep the physical size of+-- the array as 0. Or we will have to make a special handling for zero sized+-- elements to make the size as 1. Or we can disallow arrays with elements+-- having size 0.+--+-- Some examples:+--+-- data B = B -- one byte+-- data A = A B -- one byte+-- data X = X1 | X2 -- one byte (constructor tag only)+--+{-# INLINE genericSizeOf #-}+genericSizeOf :: forall a. (SizeOfRep (Rep a)) => Proxy a -> Int+genericSizeOf _ =+ let s = sizeOfRep (undefined :: Rep a x)+ in if s == 0 then 1 else s++--------------------------------------------------------------------------------+-- Generic poke+--------------------------------------------------------------------------------++class PokeRep (f :: Type -> Type) where+ pokeRep :: f a -> BoundedPtr -> IO BoundedPtr++instance PokeRep f => PokeRep (M1 i c f) where+ {-# INLINE pokeRep #-}+ pokeRep f = pokeRep (unM1 f)++instance Unbox a => PokeRep (K1 i a) where+ {-# INLINE pokeRep #-}+ pokeRep a = pokeBoundedPtrUnsafe (unK1 a)++instance PokeRep V1 where+ {-# INLINE pokeRep #-}+ pokeRep = error "pokeRep: a value of a Void type should not exist"++instance PokeRep U1 where+ {-# INLINE pokeRep #-}+ pokeRep _ x = pure x++instance (PokeRep f, PokeRep g) => PokeRep (f :*: g) where+ {-# INLINE pokeRep #-}+ pokeRep (f :*: g) ptr = pokeRep f ptr >>= pokeRep g++-------------------------------------------------------------------------------++class KnownNat n => PokeRepSum (n :: Nat) (f :: Type -> Type) where+ -- "n" is the constructor tag to be poked.+ pokeRepSum :: Proxy n -> f a -> BoundedPtr -> IO BoundedPtr++instance (KnownNat n, PokeRep a) => PokeRepSum n (C1 c a) where+ {-# INLINE pokeRepSum #-}+ pokeRepSum _ x ptr = do+ let tag = fromInteger (natVal (Proxy :: Proxy n)) :: Word8+ pokeBoundedPtrUnsafe tag ptr >>= pokeRep x++instance (PokeRepSum n f, PokeRepSum (n + SumArity f) g)+ => PokeRepSum n (f :+: g) where+ {-# INLINE pokeRepSum #-}+ pokeRepSum _ (L1 x) ptr =+ pokeRepSum (Proxy :: Proxy n) x ptr+ pokeRepSum _ (R1 x) ptr =+ pokeRepSum (Proxy :: Proxy (n + SumArity f)) x ptr++-------------------------------------------------------------------------------++instance (MaxArity256 (SumArity (f :+: g)), PokeRepSum 0 (f :+: g)) =>+ PokeRep (f :+: g) where++ {-# INLINE pokeRep #-}+ pokeRep = pokeRepSum (Proxy :: Proxy 0)++{-# INLINE genericPokeObject #-}+genericPokeObject :: (Generic a, PokeRep (Rep a)) =>+ a -> BoundedPtr -> IO BoundedPtr+genericPokeObject a = pokeRep (from a)++genericPokeObj :: (Generic a, PokeRep (Rep a)) => a -> BoundedPtr -> IO ()+genericPokeObj a ptr = void $ genericPokeObject a ptr++{-# INLINE genericPokeByteIndex #-}+genericPokeByteIndex :: (Generic a, PokeRep (Rep a)) =>+ MutByteArray -> Int -> a -> IO ()+genericPokeByteIndex arr index x = do+ -- XXX Should we use unsafe poke?+#ifdef DEBUG+ end <- MutByteArray.length arr+ genericPokeObj x (BoundedPtr arr index end)+#else+ genericPokeObj x (BoundedPtr arr index undefined)+#endif++--------------------------------------------------------------------------------+-- Generic peek+--------------------------------------------------------------------------------++class PeekRep (f :: Type -> Type) where+ -- Like pokeRep, we can use the following signature instead of using Peeker+ -- peekRep :: BoundedPtr -> IO (BoundedPtr, f a)+ peekRep :: Peeker (f x)++instance PeekRep f => PeekRep (M1 i c f) where+ {-# INLINE peekRep #-}+ peekRep = fmap M1 peekRep++instance Unbox a => PeekRep (K1 i a) where+ {-# INLINE peekRep #-}+ peekRep = fmap K1 readUnsafe++instance PeekRep V1 where+ {-# INLINE peekRep #-}+ peekRep = error "peekRep: a value of a Void type should not exist"++instance PeekRep U1 where+ {-# INLINE peekRep #-}+ peekRep = pure U1++instance (PeekRep f, PeekRep g) => PeekRep (f :*: g) where+ {-# INLINE peekRep #-}+ peekRep = (:*:) <$> peekRep <*> peekRep++-------------------------------------------------------------------------------++class KnownNat n => PeekRepSum (n :: Nat) (f :: Type -> Type) where+ -- "n" is the constructor tag to be matched.+ peekRepSum :: Proxy n -> Word8 -> Peeker (f a)++instance (KnownNat n, PeekRep a) => PeekRepSum n (C1 c a) where+ {-# INLINE peekRepSum #-}+ peekRepSum _ _ = peekRep+ {-+ -- These error checks are expensive, to avoid these+ -- we validate the max value of the tag in peekRep.+ -- XXX Add tests to cover all cases+ peekRepSum _ tag+ | tag == curTag = peekRep+ | tag > curTag =+ error $ "Unbox instance peek: Constructor tag index "+ ++ show tag ++ " out of range, max tag index is "+ ++ show curTag+ | otherwise = error "peekRepSum: bug"++ where++ curTag = fromInteger (natVal (Proxy :: Proxy n))+ -}++instance (PeekRepSum n f, PeekRepSum (n + SumArity f) g)+ => PeekRepSum n (f :+: g) where+ {-# INLINE peekRepSum #-}+ peekRepSum curProxy tag+ | tag < firstRightTag =+ L1 <$> peekRepSum curProxy tag+ | otherwise =+ R1 <$> peekRepSum (Proxy :: Proxy (n + SumArity f)) tag++ where++ firstRightTag = fromInteger (natVal (Proxy :: Proxy (n + SumArity f)))++-------------------------------------------------------------------------------++instance ( MaxArity256 (SumArity (f :+: g))+ , KnownNat (SumArity (f :+: g))+ , PeekRepSum 0 (f :+: g))+ => PeekRep (f :+: g) where+ {-# INLINE peekRep #-}+ peekRep = do+ tag :: Word8 <- readUnsafe+ -- XXX test with 256 and more constructors+ let arity :: Int =+ fromInteger (natVal (Proxy :: Proxy (SumArity (f :+: g))))+ when (fromIntegral tag >= arity)+ $ error $ "peek: Tag " ++ show tag+ ++ " is greater than the max tag " ++ show (arity - 1)+ ++ " for the data type"+ peekRepSum (Proxy :: Proxy 0) tag -- DataKinds++{-# INLINE genericPeeker #-}+genericPeeker :: (Generic a, PeekRep (Rep a)) => Peeker a+genericPeeker = to <$> peekRep++{-# INLINE genericPeekBoundedPtr #-}+genericPeekBoundedPtr :: (Generic a, PeekRep (Rep a)) => BoundedPtr -> IO a+genericPeekBoundedPtr = runPeeker genericPeeker++{-# INLINE genericPeekByteIndex #-}+genericPeekByteIndex :: (Generic a, PeekRep (Rep a)) =>+ MutByteArray -> Int -> IO a+genericPeekByteIndex arr index = do+ -- XXX Should we use unsafe peek?+#ifdef DEBUG+ end <- MutByteArray.length arr+ genericPeekBoundedPtr (BoundedPtr arr index end)+#else+ genericPeekBoundedPtr (BoundedPtr arr index undefined)+#endif
+ src/Streamly/Internal/Data/Unbox/TH.hs view
@@ -0,0 +1,499 @@+{-# LANGUAGE TemplateHaskell #-}++-- |+-- Module : Streamly.Internal.Data.Unbox.TH+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3-3-Clause+-- Maintainer : streamly@composewell.com+-- Stability : experimental+-- Portability : GHC+--+module Streamly.Internal.Data.Unbox.TH+ ( deriveUnbox++ -- th-helpers+ , DataCon(..)+ , DataType(..)+ , reifyDataType+ ) where++--------------------------------------------------------------------------------+-- Imports+--------------------------------------------------------------------------------++import Data.Bifunctor (second)+import Data.List (elemIndex)+import Data.Proxy (Proxy(..))+import Data.Word (Word16, Word32, Word64, Word8)++import Language.Haskell.TH+import Language.Haskell.TH.Syntax+import Streamly.Internal.Data.Unbox++--------------------------------------------------------------------------------+-- th-utilities+--------------------------------------------------------------------------------++-- Note: We don't support template-haskell < 2.14 (GHC < 8.6)++-- The following are copied to remove the dependency on th-utilities.+-- The code has been copied from th-abstraction and th-utilities.++-- Some CPP macros in the following code are not required but are kept+-- anyway. They can be removed if deemed as a maintainance burden.++#if MIN_VERSION_template_haskell(2,17,0)+type TyVarBndr_ flag = TyVarBndr flag+#else+type TyVarBndr_ flag = TyVarBndr+#endif++-- | Case analysis for a 'TyVarBndr'. If the value is a @'PlainTV' n _@, apply+-- the first function to @n@; if it is @'KindedTV' n _ k@, apply the second+-- function to @n@ and @k@.+elimTV :: (Name -> r) -> (Name -> Kind -> r) -> TyVarBndr_ flag -> r+#if MIN_VERSION_template_haskell(2,17,0)+elimTV ptv _ktv (PlainTV n _) = ptv n+elimTV _ptv ktv (KindedTV n _ k) = ktv n k+#else+elimTV ptv _ktv (PlainTV n) = ptv n+elimTV _ptv ktv (KindedTV n k) = ktv n k+#endif++-- | Extract the type variable name from a 'TyVarBndr', ignoring the+-- kind signature if one exists.+tvName :: TyVarBndr_ flag -> Name+tvName = elimTV id const++-- | Get the 'Name' of a 'TyVarBndr'+tyVarBndrName :: TyVarBndr_ flag -> Name+tyVarBndrName = tvName++-- | Simplified info about a 'DataD'. Omits deriving, strictness,+-- kind info, and whether it's @data@ or @newtype@.+data DataType = DataType+ { dtName :: Name+ , dtTvs :: [Name]+ , dtCxt :: Cxt+ , dtCons :: [DataCon]+ } deriving (Eq, Show, Ord) --, Data, Typeable, Generic)++-- | Simplified info about a 'Con'. Omits deriving, strictness, and kind+-- info. This is much nicer than consuming 'Con' directly, because it+-- unifies all the constructors into one.+data DataCon = DataCon+ { dcName :: Name+ , dcTvs :: [Name]+ , dcCxt :: Cxt+ , dcFields :: [(Maybe Name, Type)]+ } deriving (Eq, Show, Ord) --, Data, Typeable, Generic)+++-- | Convert a 'Con' to a list of 'DataCon'. The result is a list+-- because 'GadtC' and 'RecGadtC' can define multiple constructors.+conToDataCons :: Con -> [DataCon]+conToDataCons = \case+ NormalC name slots ->+ [DataCon name [] [] (map (\(_, ty) -> (Nothing, ty)) slots)]+ RecC name fields ->+ [DataCon name [] [] (map (\(n, _, ty) -> (Just n, ty)) fields)]+ InfixC (_, ty1) name (_, ty2) ->+ [DataCon name [] [] [(Nothing, ty1), (Nothing, ty2)]]+ ForallC tvs preds con ->+ map (\(DataCon name tvs0 preds0 fields) ->+ DataCon name (tvs0 ++ map tyVarBndrName tvs) (preds0 ++ preds) fields) (conToDataCons con)+#if MIN_VERSION_template_haskell(2,11,0)+ GadtC ns slots _ ->+ map (\dn -> DataCon dn [] [] (map (\(_, ty) -> (Nothing, ty)) slots)) ns+ RecGadtC ns fields _ ->+ map (\dn -> DataCon dn [] [] (map (\(fn, _, ty) -> (Just fn, ty)) fields)) ns+#endif++-- | Reify the given data or newtype declaration, and yields its+-- 'DataType' representation.+reifyDataType :: Name -> Q DataType+reifyDataType name = do+ info <- reify name+ case infoToDataType info of+ Nothing -> fail $ "Expected to reify a datatype. Instead got:\n" ++ pprint info+ Just x -> return x++infoToDataType :: Info -> Maybe DataType+infoToDataType info = case info of+#if MIN_VERSION_template_haskell(2,11,0)+ TyConI (DataD preds name tvs _kind cons _deriving) ->+#else+ TyConI (DataD preds name tvs cons _deriving) ->+#endif+ Just $ DataType name (map tyVarBndrName tvs) preds (concatMap conToDataCons cons)+#if MIN_VERSION_template_haskell(2,11,0)+ TyConI (NewtypeD preds name tvs _kind con _deriving) ->+#else+ TyConI (NewtypeD preds name tvs con _deriving) ->+#endif+ Just $ DataType name (map tyVarBndrName tvs) preds (conToDataCons con)+ _ -> Nothing++--------------------------------------------------------------------------------+-- Helpers+--------------------------------------------------------------------------------++type Field = (Maybe Name, Type)++_arr :: Name+_arr = mkName "arr"++_tag :: Name+_tag = mkName "tag"++_initialOffset :: Name+_initialOffset = mkName "initialOffset"++_val :: Name+_val = mkName "val"++mkOffsetName :: Int -> Name+mkOffsetName i = mkName ("offset" ++ show i)++mkFieldName :: Int -> Name+mkFieldName i = mkName ("field" ++ show i)++--------------------------------------------------------------------------------+-- Domain specific helpers+--------------------------------------------------------------------------------++exprGetSize :: Type -> Q Exp+exprGetSize ty = appE (varE 'sizeOf) [|Proxy :: Proxy $(pure ty)|]++getTagSize :: Int -> Int+getTagSize numConstructors+ | numConstructors == 1 = 0+ | fromIntegral (maxBound :: Word8) >= numConstructors = 1+ | fromIntegral (maxBound :: Word16) >= numConstructors = 2+ | fromIntegral (maxBound :: Word32) >= numConstructors = 4+ | fromIntegral (maxBound :: Word64) >= numConstructors = 8+ | otherwise = error "Too many constructors"++getTagType :: Int -> Name+getTagType numConstructors+ | numConstructors == 1 = error "No tag for 1 constructor"+ | fromIntegral (maxBound :: Word8) >= numConstructors = ''Word8+ | fromIntegral (maxBound :: Word16) >= numConstructors = ''Word16+ | fromIntegral (maxBound :: Word32) >= numConstructors = ''Word32+ | fromIntegral (maxBound :: Word64) >= numConstructors = ''Word64+ | otherwise = error "Too many constructors"++mkOffsetDecls :: Int -> [Field] -> [Q Dec]+mkOffsetDecls tagSize fields =+ init+ ((:) (valD+ (varP (mkOffsetName 0))+ (normalB+ [|$(litE (IntegerL (fromIntegral tagSize))) ++ $(varE _initialOffset)|])+ [])+ (fmap mkOffsetExpr (zip [1 ..] fields)))++ where++ mkOffsetExpr (i, (_, ty)) =+ valD+ (varP (mkOffsetName i))+ (normalB [|$(varE (mkOffsetName (i - 1))) + $(exprGetSize ty)|])+ []++--------------------------------------------------------------------------------+-- Size+--------------------------------------------------------------------------------++isUnitType :: [DataCon] -> Bool+isUnitType [DataCon _ _ _ []] = True+isUnitType _ = False++mkSizeOfExpr :: Type -> [DataCon] -> Q Exp+mkSizeOfExpr headTy constructors =+ case constructors of+ [] ->+ [|error+ ("Attempting to get size with no constructors (" +++ $(lift (pprint headTy)) ++ ")")|]+ -- One constructor with no fields is a unit type. Size of a unit type is+ -- 1.+ [con@(DataCon _ _ _ fields)] ->+ case fields of+ [] -> litE (IntegerL 1)+ _ -> [|$(sizeOfConstructor con)|]+ _ -> [|$(litE (IntegerL (fromIntegral tagSize))) + $(sizeOfHeadDt)|]++ where++ tagSize = getTagSize (length constructors)+ sizeOfField (_, ty) = exprGetSize ty+ sizeOfConstructor (DataCon _ _ _ fields) =+ appE (varE 'sum) (listE (map sizeOfField fields))+ -- The size of any Unbox type is atleast 1+ sizeOfHeadDt =+ appE (varE 'maximum) (listE (map sizeOfConstructor constructors))++--------------------------------------------------------------------------------+-- Peek+--------------------------------------------------------------------------------++mkPeekExprOne :: Int -> DataCon -> Q Exp+mkPeekExprOne tagSize (DataCon cname _ _ fields) =+ case fields of+ -- XXX Should we peek and check if the byte value is 0?+ [] -> [|pure $(conE cname)|]+ _ ->+ letE+ (mkOffsetDecls tagSize fields)+ (foldl+ (\acc i -> [|$(acc) <*> $(peekField i)|])+ [|$(conE cname) <$> $(peekField 0)|]+ [1 .. (length fields - 1)])++ where++ peekField i = [|peekAt $(varE (mkOffsetName i)) $(varE _arr)|]++mkPeekExpr :: Type -> [DataCon] -> Q Exp+mkPeekExpr headTy cons =+ case cons of+ [] ->+ [|error+ ("Attempting to peek type with no constructors (" +++ $(lift (pprint headTy)) ++ ")")|]+ [con] -> mkPeekExprOne 0 con+ _ ->+ doE+ [ bindS+ (varP _tag)+ [|peekAt $(varE _initialOffset) $(varE _arr)|]+ , noBindS+ (caseE+ (sigE (varE _tag) (conT tagType))+ (fmap peekMatch (zip [0 ..] cons) ++ [peekErr]))+ ]++ where++ lenCons = length cons+ tagType = getTagType lenCons+ tagSize = getTagSize lenCons+ peekMatch (i, con) =+ match (litP (IntegerL i)) (normalB (mkPeekExprOne tagSize con)) []+ peekErr =+ match+ wildP+ (normalB+ [|error+ ("Found invalid tag while peeking (" +++ $(lift (pprint headTy)) ++ ")")|])+ []++--------------------------------------------------------------------------------+-- Poke+--------------------------------------------------------------------------------++mkPokeExprTag :: Name -> Int -> Q Exp+mkPokeExprTag tagType tagVal = pokeTag++ where++ pokeTag =+ [|pokeAt+ $(varE _initialOffset)+ $(varE _arr)+ $(sigE (litE (IntegerL (fromIntegral tagVal))) (conT tagType))|]++mkPokeExprFields :: Int -> [Field] -> Q Exp+mkPokeExprFields tagSize fields = do+ case fields of+ [] -> [|pure ()|]+ _ ->+ letE+ (mkOffsetDecls tagSize fields)+ (doE $ map (noBindS . pokeField) [0 .. (numFields - 1)])++ where++ numFields = length fields+ pokeField i =+ [|pokeAt+ $(varE (mkOffsetName i))+ $(varE _arr)+ $(varE (mkFieldName i))|]++mkPokeMatch :: Name -> Int -> Q Exp -> Q Match+mkPokeMatch cname numFields exp0 =+ match+ (conP cname (map (varP . mkFieldName) [0 .. (numFields - 1)]))+ (normalB exp0)+ []++mkPokeExpr :: Type -> [DataCon] -> Q Exp+mkPokeExpr headTy cons =+ case cons of+ [] ->+ [|error+ ("Attempting to poke type with no constructors (" +++ $(lift (pprint headTy)) ++ ")")|]+ -- XXX We don't gaurentee encoded equivalilty for Unbox. Does it still+ -- make sense to encode a default value for unit constructor?+ [DataCon _ _ _ []] -> [|pure ()|] -- mkPokeExprTag ''Word8 0+ [DataCon cname _ _ fields] ->+ caseE+ (varE _val)+ [mkPokeMatch cname (length fields) (mkPokeExprFields 0 fields)]+ _ ->+ caseE+ (varE _val)+ (fmap (\(tagVal, DataCon cname _ _ fields) ->+ mkPokeMatch+ cname+ (length fields)+ (doE [ noBindS $ mkPokeExprTag tagType tagVal+ , noBindS $ mkPokeExprFields tagSize fields+ ]))+ (zip [0 ..] cons))++ where++ lenCons = length cons+ tagType = getTagType lenCons+ tagSize = getTagSize lenCons++--------------------------------------------------------------------------------+-- Main+--------------------------------------------------------------------------------++-- | A general function to derive Unbox instances where you can control which+-- Constructors of the datatype to consider and what the Context for the 'Unbox'+-- instance would be.+--+-- Consider the datatype:+-- @+-- data CustomDataType a b+-- = CDTConstructor1+-- | CDTConstructor2 Bool+-- | CDTConstructor3 Bool b+-- deriving (Show, Eq)+-- @+--+-- Usage:+-- @+-- $(deriveUnboxInternal+-- [AppT (ConT ''Unbox) (VarT (mkName "b"))]+-- (AppT+-- (AppT (ConT ''CustomDataType) (VarT (mkName "a")))+-- (VarT (mkName "b")))+-- [ DataCon 'CDTConstructor1 [] [] []+-- , DataCon 'CDTConstructor2 [] [] [(Nothing, (ConT ''Bool))]+-- , DataCon+-- 'CDTConstructor3+-- []+-- []+-- [(Nothing, (ConT ''Bool)), (Nothing, (VarT (mkName "b")))]+-- ])+-- @+deriveUnboxInternal :: Type -> [DataCon] -> ([Dec] -> Q [Dec]) -> Q [Dec]+deriveUnboxInternal headTy cons mkDec = do+ sizeOfMethod <- mkSizeOfExpr headTy cons+ peekMethod <- mkPeekExpr headTy cons+ pokeMethod <- mkPokeExpr headTy cons+ let methods =+ -- INLINE on sizeOf actually worsens some benchmarks, and improves+ -- none+ [ -- PragmaD (InlineP 'sizeOf Inline FunLike AllPhases)+ FunD 'sizeOf [Clause [WildP] (NormalB sizeOfMethod) []]+ , PragmaD (InlineP 'peekAt Inline FunLike AllPhases)+ , FunD+ 'peekAt+ [ Clause+ (if isUnitType cons+ then [WildP, WildP]+ else [VarP _initialOffset, VarP _arr])+ (NormalB peekMethod)+ []+ ]+ , PragmaD (InlineP 'pokeAt Inline FunLike AllPhases)+ , FunD+ 'pokeAt+ [ Clause+ (if isUnitType cons+ then [WildP, WildP, WildP]+ else [VarP _initialOffset, VarP _arr, VarP _val])+ (NormalB pokeMethod)+ []+ ]+ ]+ mkDec methods++-- | Given an 'Unbox' instance declaration splice without the methods (e.g.+-- @[d|instance Unbox a => Unbox (Maybe a)|]@), generate an instance+-- declaration including all the type class method implementations.+--+-- Usage:+--+-- @+-- \$(deriveUnbox [d|instance Unbox a => Unbox (Maybe a)|])+-- @+deriveUnbox :: Q [Dec] -> Q [Dec]+deriveUnbox mDecs = do+ dec <- mDecs+ case dec of+ [InstanceD mo preds headTyWC []] -> do+ let headTy = unwrap dec headTyWC+ (mainTyName, subs) = getMainTypeName dec headTy+ dt <- reifyDataType mainTyName+ let tyVars = dtTvs dt+ mapper = mapperWith (VarT <$> tyVars) subs+ cons = map (modifyConVariables mapper) (dtCons dt)+ deriveUnboxInternal headTy cons (mkInst mo preds headTyWC)+ _ -> errorMessage dec++ where++ mapperWith l1 l2 a =+ case elemIndex a l1 of+ Nothing -> a+ -- XXX Capture this case and give a relavant error.+ Just i -> l2 !! i++ mapType f (AppT t1 t2) = AppT (mapType f t1) (mapType f t2)+ mapType f (InfixT t1 n t2) = InfixT (mapType f t1) n (mapType f t2)+ mapType f (UInfixT t1 n t2) = UInfixT (mapType f t1) n (mapType f t2)+ mapType f (ParensT t) = ParensT (mapType f t)+ mapType f v = f v++ modifyConVariables f con =+ con { dcFields = map (second (mapType f)) (dcFields con) }++ mkInst mo preds headTyWC methods =+ pure [InstanceD mo preds headTyWC methods]++ errorMessage dec =+ error $ unlines+ [ "Error: deriveUnbox:"+ , ""+ , ">> " ++ pprint dec+ , ""+ , "The supplied declaration not a valid instance declaration."+ , "Provide a valid Haskell instance declaration without a body."+ , ""+ , "Examples:"+ , "instance Unbox (Proxy a)"+ , "instance Unbox a => Unbox (Identity a)"+ , "instance Unbox (TableT Identity)"+ ]++ unwrap _ (AppT (ConT _) r) = r+ unwrap dec _ = errorMessage dec++ getMainTypeName dec = go []++ where++ go xs (ConT nm) = (nm, xs)+ go xs (AppT l r) = go (r:xs) l+ go _ _ = errorMessage dec
− src/Streamly/Internal/Data/Unboxed.hs
@@ -1,855 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DefaultSignatures #-}-{-# LANGUAGE UnboxedTuples #-}-{-# LANGUAGE UndecidableInstances #-}---- | TODO: Implement TH based instance derivation for better performance.--module Streamly.Internal.Data.Unboxed- ( Unbox(..)- , peekWith- , pokeWith- , MutableByteArray(..)- , touch- , getMutableByteArray#- , pin- , unpin- , newUnpinnedBytes- , newPinnedBytes- , newAlignedPinnedBytes- , nil-- -- * Type Parser and Builder- , BoundedPtr (..)-- , Peeker (..)- , read- , readUnsafe- , skipByte- , runPeeker-- , pokeBoundedPtrUnsafe- , pokeBoundedPtr-- -- * Generic Unbox instances- , genericSizeOf- , genericPeekByteIndex- , genericPokeByteIndex-- -- Classess used for generic deriving.- , PeekRep(..)- , PokeRep(..)- , SizeOfRep(..)- ) where--#include "MachDeps.h"-#include "ArrayMacros.h"--import Control.Monad (void, when)-import Data.Complex (Complex((:+)))-import Data.Functor ((<&>))-import Data.Functor.Const (Const(..))-import Data.Functor.Identity (Identity(..))-import Data.Kind (Type)-import Data.Proxy (Proxy (..))-import Foreign.Ptr (IntPtr(..), WordPtr(..))-import GHC.Base (IO(..))-import GHC.Fingerprint.Type (Fingerprint(..))-import GHC.Int (Int16(..), Int32(..), Int64(..), Int8(..))-import GHC.Real (Ratio(..))-import GHC.Stable (StablePtr(..))-import GHC.Word (Word16(..), Word32(..), Word64(..), Word8(..))-#if MIN_VERSION_base(4,15,0)-import GHC.RTS.Flags (IoSubSystem(..))-#endif-import Streamly.Internal.Data.Builder (Builder (..))-import System.IO.Unsafe (unsafePerformIO)--import GHC.Generics-import GHC.Exts-import GHC.TypeLits-import Prelude hiding (read)------------------------------------------------------------------------------------- The ArrayContents type------------------------------------------------------------------------------------- XXX can use UnliftedNewtypes-data MutableByteArray = MutableByteArray (MutableByteArray# RealWorld)--{-# INLINE getMutableByteArray# #-}-getMutableByteArray# :: MutableByteArray -> MutableByteArray# RealWorld-getMutableByteArray# (MutableByteArray mbarr) = mbarr--{-# INLINE touch #-}-touch :: MutableByteArray -> IO ()-touch (MutableByteArray contents) =- IO $ \s -> case touch# contents s of s' -> (# s', () #)---- | Return the size of the array in bytes.-{-# INLINE sizeOfMutableByteArray #-}-sizeOfMutableByteArray :: MutableByteArray -> IO Int-sizeOfMutableByteArray (MutableByteArray arr) =- IO $ \s ->- case getSizeofMutableByteArray# arr s of- (# s1, i #) -> (# s1, I# i #)------------------------------------------------------------------------------------- Creation-----------------------------------------------------------------------------------{-# NOINLINE nil #-}-nil :: MutableByteArray-nil = unsafePerformIO $ newUnpinnedBytes 0--{-# INLINE newUnpinnedBytes #-}-newUnpinnedBytes :: Int -> IO MutableByteArray-newUnpinnedBytes nbytes | nbytes < 0 =- errorWithoutStackTrace "newUnpinnedBytes: size must be >= 0"-newUnpinnedBytes (I# nbytes) = IO $ \s ->- case newByteArray# nbytes s of- (# s', mbarr# #) ->- let c = MutableByteArray mbarr#- in (# s', c #)--{-# INLINE newPinnedBytes #-}-newPinnedBytes :: Int -> IO MutableByteArray-newPinnedBytes nbytes | nbytes < 0 =- errorWithoutStackTrace "newPinnedBytes: size must be >= 0"-newPinnedBytes (I# nbytes) = IO $ \s ->- case newPinnedByteArray# nbytes s of- (# s', mbarr# #) ->- let c = MutableByteArray mbarr#- in (# s', c #)--{-# INLINE newAlignedPinnedBytes #-}-newAlignedPinnedBytes :: Int -> Int -> IO MutableByteArray-newAlignedPinnedBytes nbytes _align | nbytes < 0 =- errorWithoutStackTrace "newAlignedPinnedBytes: size must be >= 0"-newAlignedPinnedBytes (I# nbytes) (I# align) = IO $ \s ->- case newAlignedPinnedByteArray# nbytes align s of- (# s', mbarr# #) ->- let c = MutableByteArray mbarr#- in (# s', c #)------------------------------------------------------------------------------------ Pinning & Unpinning----------------------------------------------------------------------------------{-# INLINE isPinned #-}-isPinned :: MutableByteArray -> Bool-isPinned (MutableByteArray arr#) =- let pinnedInt = I# (isMutableByteArrayPinned# arr#)- in pinnedInt == 1---{-# INLINE cloneMutableArrayWith# #-}-cloneMutableArrayWith#- :: (Int# -> State# RealWorld -> (# State# RealWorld- , MutableByteArray# RealWorld #))- -> MutableByteArray# RealWorld- -> State# RealWorld- -> (# State# RealWorld, MutableByteArray# RealWorld #)-cloneMutableArrayWith# alloc# arr# s# =- case getSizeofMutableByteArray# arr# s# of- (# s1#, i# #) ->- case alloc# i# s1# of- (# s2#, arr1# #) ->- case copyMutableByteArray# arr# 0# arr1# 0# i# s2# of- s3# -> (# s3#, arr1# #)--{-# INLINE pin #-}-pin :: MutableByteArray -> IO MutableByteArray-pin arr@(MutableByteArray marr#) =- if isPinned arr- then return arr- else IO- $ \s# ->- case cloneMutableArrayWith# newPinnedByteArray# marr# s# of- (# s1#, marr1# #) -> (# s1#, MutableByteArray marr1# #)--{-# INLINE unpin #-}-unpin :: MutableByteArray -> IO MutableByteArray-unpin arr@(MutableByteArray marr#) =- if not (isPinned arr)- then return arr- else IO- $ \s# ->- case cloneMutableArrayWith# newByteArray# marr# s# of- (# s1#, marr1# #) -> (# s1#, MutableByteArray marr1# #)------------------------------------------------------------------------------------- The Unbox type class------------------------------------------------------------------------------------- XXX generate error if the size is < 1---- In theory we could convert a type to and from a byte stream and use that--- to implement boxing, unboxing. But that would be inefficient. This type--- class allows each primitive type to have its own specific efficient--- implementation to read and write the type to memory.------ This is a typeclass that uses MutableByteArray but could use ForeignPtr or--- any other representation of memory. We could make this a multiparameter type--- class if necessary.------ If the type class would have to support reading and writing to a Ptr as well,--- basically what Storable does. We will also need to touch the anchoring ptr at--- the right points which is prone to errors. However, it should be simple to--- implement unmanaged/read-only memory arrays by allowing a Ptr type in--- ArrayContents, though it would require all instances to support reading from--- a Ptr.------ There is a reason for using byte offset instead of element index in read and--- write operations in the type class. If we use element index slicing of the--- array becomes rigid. We can only slice the array at addresses that are--- aligned with the type, therefore, we cannot slice at misaligned location and--- then cast the slice into another type which does not ncessarily align with--- the original type.------ As a side note, there seem to be no performance advantage of alignment--- anymore, see--- https://lemire.me/blog/2012/05/31/data-alignment-for-speed-myth-or-reality/------- The main goal of the Unbox type class is to be used in arrays. Invariants--- for the sizeOf value required for use in arrays:------ * size is independent of the value, it is determined by the type only. So--- that we can store values of the same type in fixed length array cells.--- * size cannot be zero. So that the length of an array storing the element--- and the number of elements can be related.------ Note, for general serializable types the size cannot be fixed e.g. we may--- want to serialize a list. This type class can be considered a special case--- of a more general serialization type class.---- | A type implementing the 'Unbox' interface supplies operations for reading--- and writing the type from and to a mutable byte array (an unboxed--- representation of the type) in memory. The read operation 'peekByteIndex'--- deserializes the boxed type from the mutable byte array. The write operation--- 'pokeByteIndex' serializes the boxed type to the mutable byte array.------ Instances can be derived via 'Generic'. Note that the data type must be--- non-recursive. Here is an example, for deriving an instance of this type--- class.------ >>> import GHC.Generics (Generic)--- >>> :{--- data Object = Object--- { _int0 :: Int--- , _int1 :: Int--- } deriving Generic--- :}------ WARNING! Generic deriving hangs for recursive data types.------ >>> import Streamly.Data.Array (Unbox(..))--- >>> instance Unbox Object------ If you want to write the instance manually:------ >>> :{--- instance Unbox Object where--- sizeOf _ = 16--- peekByteIndex i arr = do--- x0 <- peekByteIndex i arr--- x1 <- peekByteIndex (i + 8) arr--- return $ Object x0 x1--- pokeByteIndex i arr (Object x0 x1) = do--- pokeByteIndex i arr x0--- pokeByteIndex (i + 8) arr x1--- :}----class Unbox a where- -- | Get the size. Size cannot be zero.- sizeOf :: Proxy a -> Int-- default sizeOf :: (SizeOfRep (Rep a)) => Proxy a -> Int- sizeOf = genericSizeOf-- -- | Read an element of type "a" from a MutableByteArray given the byte- -- index.- --- -- IMPORTANT: The implementation of this interface may not check the bounds- -- of the array, the caller must not assume that.- peekByteIndex :: Int -> MutableByteArray -> IO a-- default peekByteIndex :: (Generic a, PeekRep (Rep a)) =>- Int -> MutableByteArray -> IO a- peekByteIndex i arr = genericPeekByteIndex arr i-- -- | Write an element of type "a" to a MutableByteArray given the byte- -- index.- --- -- IMPORTANT: The implementation of this interface may not check the bounds- -- of the array, the caller must not assume that.- pokeByteIndex :: Int -> MutableByteArray -> a -> IO ()-- default pokeByteIndex :: (Generic a, PokeRep (Rep a)) =>- Int -> MutableByteArray -> a -> IO ()- pokeByteIndex i arr = genericPokeByteIndex arr i--#define DERIVE_UNBOXED(_type, _constructor, _readArray, _writeArray, _sizeOf) \-instance Unbox _type where { \-; {-# INLINE peekByteIndex #-} \-; peekByteIndex (I# n) (MutableByteArray mbarr) = IO $ \s -> \- case _readArray mbarr n s of \- { (# s1, i #) -> (# s1, _constructor i #) } \-; {-# INLINE pokeByteIndex #-} \-; pokeByteIndex (I# n) (MutableByteArray mbarr) (_constructor val) = \- IO $ \s -> (# _writeArray mbarr n val s, () #) \-; {-# INLINE sizeOf #-} \-; sizeOf _ = _sizeOf \-}--#define DERIVE_WRAPPED_UNBOX(_constraint, _type, _constructor, _innerType) \-instance _constraint Unbox _type where \-; {-# INLINE peekByteIndex #-} \-; peekByteIndex i arr = _constructor <$> peekByteIndex i arr \-; {-# INLINE pokeByteIndex #-} \-; pokeByteIndex i arr (_constructor a) = pokeByteIndex i arr a \-; {-# INLINE sizeOf #-} \-; sizeOf _ = SIZE_OF(_innerType)--#define DERIVE_BINARY_UNBOX(_constraint, _type, _constructor, _innerType) \-instance _constraint Unbox _type where { \-; {-# INLINE peekByteIndex #-} \-; peekByteIndex i arr = \- peekByteIndex i arr >>= \- (\p1 -> peekByteIndex (i + SIZE_OF(_innerType)) arr \- <&> _constructor p1) \-; {-# INLINE pokeByteIndex #-} \-; pokeByteIndex i arr (_constructor p1 p2) = \- pokeByteIndex i arr p1 >> \- pokeByteIndex (i + SIZE_OF(_innerType)) arr p2 \-; {-# INLINE sizeOf #-} \-; sizeOf _ = 2 * SIZE_OF(_innerType) \-}------------------------------------------------------------------------------------ Unbox instances for primitive types----------------------------------------------------------------------------------DERIVE_UNBOXED( Char- , C#- , readWord8ArrayAsWideChar#- , writeWord8ArrayAsWideChar#- , SIZEOF_HSCHAR)--DERIVE_UNBOXED( Int8- , I8#- , readInt8Array#- , writeInt8Array#- , 1)--DERIVE_UNBOXED( Int16- , I16#- , readWord8ArrayAsInt16#- , writeWord8ArrayAsInt16#- , 2)--DERIVE_UNBOXED( Int32- , I32#- , readWord8ArrayAsInt32#- , writeWord8ArrayAsInt32#- , 4)--DERIVE_UNBOXED( Int- , I#- , readWord8ArrayAsInt#- , writeWord8ArrayAsInt#- , SIZEOF_HSINT)--DERIVE_UNBOXED( Int64- , I64#- , readWord8ArrayAsInt64#- , writeWord8ArrayAsInt64#- , 8)--DERIVE_UNBOXED( Word- , W#- , readWord8ArrayAsWord#- , writeWord8ArrayAsWord#- , SIZEOF_HSWORD)--DERIVE_UNBOXED( Word8- , W8#- , readWord8Array#- , writeWord8Array#- , 1)--DERIVE_UNBOXED( Word16- , W16#- , readWord8ArrayAsWord16#- , writeWord8ArrayAsWord16#- , 2)--DERIVE_UNBOXED( Word32- , W32#- , readWord8ArrayAsWord32#- , writeWord8ArrayAsWord32#- , 4)--DERIVE_UNBOXED( Word64- , W64#- , readWord8ArrayAsWord64#- , writeWord8ArrayAsWord64#- , 8)--DERIVE_UNBOXED( Double- , D#- , readWord8ArrayAsDouble#- , writeWord8ArrayAsDouble#- , SIZEOF_HSDOUBLE)--DERIVE_UNBOXED( Float- , F#- , readWord8ArrayAsFloat#- , writeWord8ArrayAsFloat#- , SIZEOF_HSFLOAT)------------------------------------------------------------------------------------ Unbox instances for derived types----------------------------------------------------------------------------------DERIVE_UNBOXED( (StablePtr a)- , StablePtr- , readWord8ArrayAsStablePtr#- , writeWord8ArrayAsStablePtr#- , SIZEOF_HSSTABLEPTR)--DERIVE_UNBOXED( (Ptr a)- , Ptr- , readWord8ArrayAsAddr#- , writeWord8ArrayAsAddr#- , SIZEOF_HSPTR)--DERIVE_UNBOXED( (FunPtr a)- , FunPtr- , readWord8ArrayAsAddr#- , writeWord8ArrayAsAddr#- , SIZEOF_HSFUNPTR)--DERIVE_WRAPPED_UNBOX(,IntPtr,IntPtr,Int)-DERIVE_WRAPPED_UNBOX(,WordPtr,WordPtr,Word)-DERIVE_WRAPPED_UNBOX(Unbox a =>,(Identity a),Identity,a)-#if MIN_VERSION_base(4,14,0)-DERIVE_WRAPPED_UNBOX(Unbox a =>,(Down a),Down,a)-#endif-DERIVE_WRAPPED_UNBOX(Unbox a =>,(Const a b),Const,a)-DERIVE_BINARY_UNBOX(forall a. Unbox a =>,(Complex a),(:+),a)-DERIVE_BINARY_UNBOX(forall a. Unbox a =>,(Ratio a),(:%),a)-DERIVE_BINARY_UNBOX(,Fingerprint,Fingerprint,Word64)--instance Unbox () where-- {-# INLINE peekByteIndex #-}- peekByteIndex _ _ = return ()-- {-# INLINE pokeByteIndex #-}- pokeByteIndex _ _ _ = return ()-- {-# INLINE sizeOf #-}- sizeOf _ = 1--#if MIN_VERSION_base(4,15,0)-instance Unbox IoSubSystem where-- {-# INLINE peekByteIndex #-}- peekByteIndex i arr = toEnum <$> peekByteIndex i arr-- {-# INLINE pokeByteIndex #-}- pokeByteIndex i arr a = pokeByteIndex i arr (fromEnum a)-- {-# INLINE sizeOf #-}- sizeOf _ = sizeOf (Proxy :: Proxy Int)-#endif--instance Unbox Bool where-- {-# INLINE peekByteIndex #-}- peekByteIndex i arr = do- res <- peekByteIndex i arr- return $ res /= (0 :: Int8)-- {-# INLINE pokeByteIndex #-}- pokeByteIndex i arr a =- if a- then pokeByteIndex i arr (1 :: Int8)- else pokeByteIndex i arr (0 :: Int8)-- {-# INLINE sizeOf #-}- sizeOf _ = 1------------------------------------------------------------------------------------- Functions-----------------------------------------------------------------------------------{-# INLINE peekWith #-}-peekWith :: Unbox a => MutableByteArray -> Int -> IO a-peekWith arr i = peekByteIndex i arr--{-# INLINE pokeWith #-}-pokeWith :: Unbox a => MutableByteArray -> Int -> a -> IO ()-pokeWith arr i = pokeByteIndex i arr------------------------------------------------------------------------------------- Generic deriving------------------------------------------------------------------------------------- Utilities to build or parse a type safely and easily.---- | A location inside a mutable byte array with the bound of the array. Is it--- cheaper to just get the bound using the size of the array whenever needed?-data BoundedPtr =- BoundedPtr- MutableByteArray -- byte array- Int -- current pos- Int -- position after end------------------------------------------------------------------------------------- Peeker monad------------------------------------------------------------------------------------- | Chains peek functions that pass the current position to the next function-newtype Peeker a = Peeker (Builder BoundedPtr IO a)- deriving (Functor, Applicative, Monad)--{-# INLINE readUnsafe #-}-readUnsafe :: Unbox a => Peeker a-readUnsafe = Peeker (Builder step)-- where-- {-# INLINE step #-}- step :: forall a. Unbox a => BoundedPtr -> IO (BoundedPtr, a)- step (BoundedPtr arr pos end) = do- let next = pos + sizeOf (Proxy :: Proxy a)- r <- peekByteIndex pos arr- return (BoundedPtr arr next end, r)--{-# INLINE read #-}-read :: Unbox a => Peeker a-read = Peeker (Builder step)-- where-- {-# INLINE step #-}- step :: forall a. Unbox a => BoundedPtr -> IO (BoundedPtr, a)- step (BoundedPtr arr pos end) = do- let next = pos + sizeOf (Proxy :: Proxy a)- when (next > end) $ error "peekObject reading beyond limit"- r <- peekByteIndex pos arr- return (BoundedPtr arr next end, r)--{-# INLINE skipByte #-}-skipByte :: Peeker ()-skipByte = Peeker (Builder step)-- where-- {-# INLINE step #-}- step :: BoundedPtr -> IO (BoundedPtr, ())- step (BoundedPtr arr pos end) = do- let next = pos + 1- when (next > end)- $ error $ "skipByte: reading beyond limit. next = "- ++ show next- ++ " end = " ++ show end- return (BoundedPtr arr next end, ())--{-# INLINE runPeeker #-}-runPeeker :: Peeker a -> BoundedPtr -> IO a-runPeeker (Peeker (Builder f)) ptr = fmap snd (f ptr)------------------------------------------------------------------------------------- Poke utilities------------------------------------------------------------------------------------- XXX Using a Poker monad may be useful when we have to compute the size to be--- poked as we go and then poke the size at a previous location. For variable--- sized object serialization we may also want to reallocate the array and--- return the newly allocated array in the output.---- Does not check writing beyond bound.-{-# INLINE pokeBoundedPtrUnsafe #-}-pokeBoundedPtrUnsafe :: forall a. Unbox a => a -> BoundedPtr -> IO BoundedPtr-pokeBoundedPtrUnsafe a (BoundedPtr arr pos end) = do- let next = pos + sizeOf (Proxy :: Proxy a)- pokeByteIndex pos arr a- return (BoundedPtr arr next end)--{-# INLINE pokeBoundedPtr #-}-pokeBoundedPtr :: forall a. Unbox a => a -> BoundedPtr -> IO BoundedPtr-pokeBoundedPtr a (BoundedPtr arr pos end) = do- let next = pos + sizeOf (Proxy :: Proxy a)- when (next > end) $ error "pokeBoundedPtr writing beyond limit"- pokeByteIndex pos arr a- return (BoundedPtr arr next end)------------------------------------------------------------------------------------- Check the number of constructors in a sum type------------------------------------------------------------------------------------- Count the constructors of a sum type.-type family SumArity (a :: Type -> Type) :: Nat where- SumArity (C1 _ _) = 1- -- Requires UndecidableInstances- SumArity (f :+: g) = SumArity f + SumArity g--type family TypeErrorMessage (a :: Symbol) :: Constraint where- TypeErrorMessage a = TypeError ('Text a)--type family ArityCheck (b :: Bool) :: Constraint where- ArityCheck 'True = ()- ArityCheck 'False = TypeErrorMessage- "Generic Unbox deriving does not support > 256 constructors."---- Type constraint to restrict the sum type arity so that the constructor tag--- can fit in a single byte.-type MaxArity256 n = ArityCheck (n <=? 255)------------------------------------------------------------------------------------- Generic Deriving of Unbox instance------------------------------------------------------------------------------------- Unbox uses fixed size encoding, therefore, when a (sum) type has multiple--- constructors, the size of the type is computed as the maximum required by--- any constructor. Therefore, size is independent of the value, it can be--- determined solely by the type.---- | Implementation of sizeOf that works on the generic representation of an--- ADT.-class SizeOfRep (f :: Type -> Type) where- sizeOfRep :: f x -> Int---- Meta information wrapper, go inside-instance SizeOfRep f => SizeOfRep (M1 i c f) where- {-# INLINE sizeOfRep #-}- sizeOfRep _ = sizeOfRep (undefined :: f x)---- Primitive type "a".-instance Unbox a => SizeOfRep (K1 i a) where- {-# INLINE sizeOfRep #-}- sizeOfRep _ = sizeOf (Proxy :: Proxy a)---- Void: data type without constructors. Values of this type cannot exist,--- therefore the size is undefined. We should never be serializing structures--- with elements of this type.-instance SizeOfRep V1 where- {-# INLINE sizeOfRep #-}- sizeOfRep = error "sizeOfRep: a value of a Void type must not exist"---- Note that when a sum type has many unit constructors only a single byte is--- required to encode the type as only the constructor tag is stored.-instance SizeOfRep U1 where- {-# INLINE sizeOfRep #-}- sizeOfRep _ = 0---- Product type-instance (SizeOfRep f, SizeOfRep g) => SizeOfRep (f :*: g) where- {-# INLINE sizeOfRep #-}- sizeOfRep _ = sizeOfRep (undefined :: f x) + sizeOfRep (undefined :: g x)-----------------------------------------------------------------------------------class SizeOfRepSum (f :: Type -> Type) where- sizeOfRepSum :: f x -> Int---- Constructor-instance SizeOfRep a => SizeOfRepSum (C1 c a) where- {-# INLINE sizeOfRepSum #-}- sizeOfRepSum = sizeOfRep--instance (SizeOfRepSum f, SizeOfRepSum g) => SizeOfRepSum (f :+: g) where- {-# INLINE sizeOfRepSum #-}- sizeOfRepSum _ =- max (sizeOfRepSum (undefined :: f x)) (sizeOfRepSum (undefined :: g x))-----------------------------------------------------------------------------------instance (MaxArity256 (SumArity (f :+: g)), SizeOfRepSum f, SizeOfRepSum g) =>- SizeOfRep (f :+: g) where-- -- The size of a sum type is the max of any of the constructor size.- -- sizeOfRepSum type class operation is used here instead of sizeOfRep so- -- that we add the constructor index byte only for the first time and avoid- -- including it for the subsequent sum constructors.- {-# INLINE sizeOfRep #-}- sizeOfRep _ =- -- One byte for the constructor id and then the constructor value.- sizeOf (Proxy :: Proxy Word8) +- max (sizeOfRepSum (undefined :: f x))- (sizeOfRepSum (undefined :: g x))---- Unit: constructors without arguments.--- Theoretically the size can be 0, but we use 1 to simplify the implementation--- of an array of unit type elements. With a non-zero size we can count the number--- of elements in the array based on the size of the array. Otherwise we will--- have to store a virtual length in the array, but keep the physical size of--- the array as 0. Or we will have to make a special handling for zero sized--- elements to make the size as 1. Or we can disallow arrays with elements--- having size 0.----{-# INLINE genericSizeOf #-}-genericSizeOf :: forall a. (SizeOfRep (Rep a)) => Proxy a -> Int-genericSizeOf _ =- let s = sizeOfRep (undefined :: Rep a x)- in if s == 0 then 1 else s------------------------------------------------------------------------------------- Generic poke-----------------------------------------------------------------------------------class PokeRep (f :: Type -> Type) where- pokeRep :: f a -> BoundedPtr -> IO BoundedPtr--instance PokeRep f => PokeRep (M1 i c f) where- {-# INLINE pokeRep #-}- pokeRep f = pokeRep (unM1 f)--instance Unbox a => PokeRep (K1 i a) where- {-# INLINE pokeRep #-}- pokeRep a = pokeBoundedPtr (unK1 a)--instance PokeRep V1 where- {-# INLINE pokeRep #-}- pokeRep = error "pokeRep: a value of a Void type should not exist"--instance PokeRep U1 where- {-# INLINE pokeRep #-}- pokeRep _ x = pure x--instance (PokeRep f, PokeRep g) => PokeRep (f :*: g) where- {-# INLINE pokeRep #-}- pokeRep (f :*: g) ptr = pokeRep f ptr >>= pokeRep g-----------------------------------------------------------------------------------class KnownNat n => PokeRepSum (n :: Nat) (f :: Type -> Type) where- -- "n" is the constructor tag to be poked.- pokeRepSum :: Proxy n -> f a -> BoundedPtr -> IO BoundedPtr--instance (KnownNat n, PokeRep a) => PokeRepSum n (C1 c a) where- {-# INLINE pokeRepSum #-}- pokeRepSum _ x ptr = do- pokeBoundedPtr (fromInteger (natVal (Proxy :: Proxy n)) :: Word8) ptr- >>= pokeRep x--instance (KnownNat n, PokeRepSum n f, PokeRepSum (n + SumArity f) g)- => PokeRepSum n (f :+: g) where- {-# INLINE pokeRepSum #-}- pokeRepSum _ (L1 x) ptr =- pokeRepSum (Proxy :: Proxy n) x ptr- pokeRepSum _ (R1 x) ptr =- pokeRepSum (Proxy :: Proxy (n + SumArity f)) x ptr-----------------------------------------------------------------------------------instance (MaxArity256 (SumArity (f :+: g)), PokeRepSum 0 (f :+: g)) =>- PokeRep (f :+: g) where-- {-# INLINE pokeRep #-}- pokeRep = pokeRepSum (Proxy :: Proxy 0)--{-# INLINE genericPokeObject #-}-genericPokeObject :: (Generic a, PokeRep (Rep a)) =>- a -> BoundedPtr -> IO BoundedPtr-genericPokeObject a = pokeRep (from a)--genericPokeObj :: (Generic a, PokeRep (Rep a)) => a -> BoundedPtr -> IO ()-genericPokeObj a ptr = void $ genericPokeObject a ptr--{-# INLINE genericPokeByteIndex #-}-genericPokeByteIndex :: (Generic a, PokeRep (Rep a)) =>- MutableByteArray -> Int -> a -> IO ()-genericPokeByteIndex arr index x = do- -- XXX Should we use unsafe poke?- end <- sizeOfMutableByteArray arr- genericPokeObj x (BoundedPtr arr index end)------------------------------------------------------------------------------------- Generic peek-----------------------------------------------------------------------------------class PeekRep (f :: Type -> Type) where- peekRep :: Peeker (f x)--instance PeekRep f => PeekRep (M1 i c f) where- {-# INLINE peekRep #-}- peekRep = fmap M1 peekRep--instance Unbox a => PeekRep (K1 i a) where- {-# INLINE peekRep #-}- peekRep = fmap K1 read--instance PeekRep V1 where- {-# INLINE peekRep #-}- peekRep = error "peekRep: a value of a Void type should not exist"--instance PeekRep U1 where- {-# INLINE peekRep #-}- peekRep = pure U1--instance (PeekRep f, PeekRep g) => PeekRep (f :*: g) where- {-# INLINE peekRep #-}- peekRep = (:*:) <$> peekRep <*> peekRep-----------------------------------------------------------------------------------class KnownNat n => PeekRepSum (n :: Nat) (f :: Type -> Type) where- -- "n" is the constructor tag to be matched.- peekRepSum :: Proxy n -> Word8 -> Peeker (f a)--instance (KnownNat n, PeekRep a) => PeekRepSum n (C1 c a) where- {-# INLINE peekRepSum #-}- peekRepSum _ tag- | tag == curTag = peekRep- | tag > curTag =- error $ "Unbox instance peek: Constructor tag index "- ++ show tag ++ " out of range, max tag index is "- ++ show curTag- | otherwise = error "peekRepSum: bug"-- where-- curTag = fromInteger (natVal (Proxy :: Proxy n))--instance (KnownNat n, PeekRepSum n f, PeekRepSum (n + SumArity f) g)- => PeekRepSum n (f :+: g) where- {-# INLINE peekRepSum #-}- peekRepSum curProxy tag- | tag < firstRightTag =- L1 <$> peekRepSum curProxy tag- | otherwise =- R1 <$> peekRepSum (Proxy :: Proxy (n + SumArity f)) tag-- where-- firstRightTag = fromInteger (natVal (Proxy :: Proxy (n + SumArity f)))-----------------------------------------------------------------------------------instance (MaxArity256 (SumArity (f :+: g)), PeekRepSum 0 (f :+: g))- => PeekRep (f :+: g) where- {-# INLINE peekRep #-}- peekRep = do- tag <- read- peekRepSum (Proxy :: Proxy 0) tag--{-# INLINE genericPeeker #-}-genericPeeker :: (Generic a, PeekRep (Rep a)) => Peeker a-genericPeeker = to <$> peekRep--{-# INLINE genericPeekBoundedPtr #-}-genericPeekBoundedPtr :: (Generic a, PeekRep (Rep a)) => BoundedPtr -> IO a-genericPeekBoundedPtr = runPeeker genericPeeker--{-# INLINE genericPeekByteIndex #-}-genericPeekByteIndex :: (Generic a, PeekRep (Rep a)) =>- MutableByteArray -> Int -> IO a-genericPeekByteIndex arr index = do- -- XXX Should we use unsafe peek?- end <- sizeOfMutableByteArray arr- genericPeekBoundedPtr (BoundedPtr arr index end)
src/Streamly/Internal/Data/Unfold.hs view
@@ -16,69 +16,28 @@ -- $setup -- * Unfold Type- Step(..)- , Unfold+ module Streamly.Internal.Data.Unfold.Type -- * Unfolds -- One to one correspondence with -- "Streamly.Internal.Data.Stream.Generate" -- ** Basic Constructors- , mkUnfoldM- , mkUnfoldrM- , unfoldrM- , unfoldr- , functionM- , function- , identity , nilM , nil , consM - -- ** From Values- , fromEffect- , fromPure- -- ** Generators -- | Generate a monadic stream from a seed. , repeatM+ , repeat , replicateM , fromIndicesM , iterateM -- ** Enumerations- , Enumerable (..)-- -- ** Enumerate Num- , enumerateFromNum- , enumerateFromThenNum- , enumerateFromStepNum-- -- ** Enumerating 'Bounded 'Integral' Types- , enumerateFromIntegralBounded- , enumerateFromThenIntegralBounded- , enumerateFromToIntegralBounded- , enumerateFromThenToIntegralBounded-- -- ** Enumerating 'Unounded Integral' Types- , enumerateFromIntegral- , enumerateFromThenIntegral- , enumerateFromToIntegral- , enumerateFromThenToIntegral-- -- ** Enumerating 'Small Integral' Types- , enumerateFromSmallBounded- , enumerateFromThenSmallBounded- , enumerateFromToSmall- , enumerateFromThenToSmall-- -- ** Enumerating 'Fractional' Types- , enumerateFromFractional- , enumerateFromThenFractional- , enumerateFromToFractional- , enumerateFromThenToFractional+ , module Streamly.Internal.Data.Unfold.Enumeration -- ** From Containers- , fromList , fromListM -- ** From Memory@@ -91,11 +50,6 @@ -- * Combinators -- ** Mapping on Input- , lmap- , lmapM- , both- , first- , second , discardFirst , discardSecond , swap@@ -105,20 +59,11 @@ -- * Folding , fold - -- XXX Add "WithInput" versions of all these, map2, postscan2, takeWhile2,- -- filter2 etc. Alternatively, we can use the default operations with- -- input, but that might make the common case more inconvenient.- -- ** Mapping on Output- , map- , map2- , mapM- , mapM2- , postscanlM' , postscan- , scan- , scanMany+ , scanl+ , scanlMany , foldMany -- pipe @@ -126,8 +71,6 @@ , either -- ** Filtering- , takeWhileM- , takeWhile , take , filter , filterM@@ -135,23 +78,11 @@ , dropWhile , dropWhileM - -- ** Zipping- , zipWithM- , zipWith- -- ** Cross product- , crossWithM- , crossWith- , cross- , joinInnerGeneric- , crossApply+ , innerJoin - -- ** Nesting- , ConcatState (..)- , many- , many2- , concatMapM- , bind+ -- ** Zip+ , zipRepeat -- ** Resource Management -- | 'bracket' is the most general resource management operation, all other@@ -176,6 +107,10 @@ -- stream of arrays before flattening it to a stream of chars. , onException , handle++ -- ** Deprecated+ , scan+ , scanMany ) where @@ -187,29 +122,34 @@ import Data.Functor (($>)) import GHC.Types (SPEC(..)) import Streamly.Internal.Data.Fold.Type (Fold(..))+import Streamly.Internal.Data.Scanl.Type (Scanl(..)) import Streamly.Internal.Data.IOFinalizer (newIOFinalizer, runIOFinalizer, clearingIOFinalizer)-import Streamly.Internal.Data.Stream.StreamD.Type (Stream(..), Step(..))+import Streamly.Internal.Data.Stream.Type (Stream(..)) import Streamly.Internal.Data.SVar.Type (defState) import qualified Control.Monad.Catch as MC import qualified Data.Tuple as Tuple import qualified Streamly.Internal.Data.Fold.Type as FL-import qualified Streamly.Internal.Data.Stream.StreamD.Type as D-import qualified Streamly.Internal.Data.Stream.StreamK.Type as K+import qualified Streamly.Internal.Data.Stream.Type as D+import qualified Streamly.Internal.Data.StreamK.Type as K import qualified Prelude import Streamly.Internal.Data.Unfold.Enumeration import Streamly.Internal.Data.Unfold.Type import Prelude hiding (map, mapM, takeWhile, take, filter, const, zipWith- , drop, dropWhile, either)+ , drop, dropWhile, either, scanl, repeat) import Control.Monad.IO.Class (MonadIO (liftIO)) import Foreign (Storable, peek, sizeOf) import Foreign.Ptr #include "DocTestDataUnfold.hs" +-------------------------------------------------------------------------------+-- Input operations+-------------------------------------------------------------------------------+ -- | Convert an 'Unfold' into an unfold accepting a tuple as an argument, -- using the argument of the original fold as the second element of tuple and -- discarding the first element of the tuple.@@ -270,7 +210,7 @@ -- {-# INLINE_NORMAL fold #-} fold :: Monad m => Fold m b c -> Unfold m a b -> a -> m c-fold (Fold fstep initial extract) (Unfold ustep inject) a = do+fold (Fold fstep initial _ final) (Unfold ustep inject) a = do res <- initial case res of FL.Partial x -> inject a >>= go SPEC x@@ -288,7 +228,7 @@ FL.Partial fs1 -> go SPEC fs1 s FL.Done c -> return c Skip s -> go SPEC fs s- Stop -> extract fs+ Stop -> final fs -- {-# ANN type FoldMany Fuse #-} data FoldMany s fs b a@@ -303,7 +243,7 @@ -- /Pre-release/ {-# INLINE_NORMAL foldMany #-} foldMany :: Monad m => Fold m b c -> Unfold m a b -> Unfold m a c-foldMany (Fold fstep initial extract) (Unfold ustep inject1) =+foldMany (Fold fstep initial _ final) (Unfold ustep inject1) = Unfold step inject where@@ -334,14 +274,14 @@ case r of Yield x s -> consume x s fs Skip s -> return $ Skip (FoldManyFirst fs s)- Stop -> return Stop+ Stop -> final fs >> return Stop step (FoldManyLoop st fs) = do r <- ustep st case r of Yield x s -> consume x s fs Skip s -> return $ Skip (FoldManyLoop s fs) Stop -> do- b <- extract fs+ b <- final fs return $ Skip (FoldManyYield b FoldManyDone) step (FoldManyYield b next) = return $ Yield b next step FoldManyDone = return Stop@@ -382,7 +322,7 @@ -- /Pre-release/ {-# INLINE_NORMAL postscan #-} postscan :: Monad m => Fold m b c -> Unfold m a b -> Unfold m a c-postscan (Fold stepF initial extract) (Unfold stepU injectU) =+postscan (Fold stepF initial extract final) (Unfold stepU injectU) = Unfold step inject where@@ -405,15 +345,15 @@ v <- extract fs1 return $ Yield v (Just (fs1, s)) Skip s -> return $ Skip (Just (fs, s))- Stop -> return Stop+ Stop -> final fs >> return Stop step Nothing = return Stop data ScanState s f = ScanInit s | ScanDo s !f | ScanDone {-# INLINE_NORMAL scanWith #-}-scanWith :: Monad m => Bool -> Fold m b c -> Unfold m a b -> Unfold m a c-scanWith restart (Fold fstep initial extract) (Unfold stepU injectU) =+scanWith :: Monad m => Bool -> Scanl m b c -> Unfold m a b -> Unfold m a c+scanWith restart (Scanl fstep initial extract final) (Unfold stepU injectU) = Unfold step inject where@@ -438,34 +378,46 @@ case res of Yield x s -> runStep s (fstep fs x) Skip s -> return $ Skip $ ScanDo s fs- Stop -> return Stop+ Stop -> final fs >> return Stop step ScanDone = return Stop -- | Scan the output of an 'Unfold' to change it in a stateful manner. -- Once fold is done it will restart from its initial state. ----- >>> u = Unfold.scanMany (Fold.take 2 Fold.sum) Unfold.fromList+-- >>> u = Unfold.scanlMany (Scanl.take 2 Scanl.sum) Unfold.fromList -- >>> Unfold.fold Fold.toList u [1,2,3,4,5] -- [0,1,3,0,3,7,0,5] -- -- /Pre-release/+{-# INLINE_NORMAL scanlMany #-}+scanlMany :: Monad m => Scanl m b c -> Unfold m a b -> Unfold m a c+scanlMany = scanWith True++-- When we remove extract from Fold this function should be removed.+{-# DEPRECATED scanMany "Please use scanlMany instead" #-} {-# INLINE_NORMAL scanMany #-} scanMany :: Monad m => Fold m b c -> Unfold m a b -> Unfold m a c-scanMany = scanWith True+scanMany (Fold s i e f) = scanWith True (Scanl s i e f) -- scan2 :: Monad m => Refold m a b c -> Unfold m a b -> Unfold m a c -- | Scan the output of an 'Unfold' to change it in a stateful manner. -- Once fold is done it will stop. ----- >>> u = Unfold.scan (Fold.take 2 Fold.sum) Unfold.fromList+-- >>> u = Unfold.scanl (Scanl.take 2 Scanl.sum) Unfold.fromList -- >>> Unfold.fold Fold.toList u [1,2,3,4,5] -- [0,1,3] -- -- /Pre-release/+{-# INLINE_NORMAL scanl #-}+scanl :: Monad m => Scanl m b c -> Unfold m a b -> Unfold m a c+scanl = scanWith False++-- When we remove extract from Fold this function should be removed.+{-# DEPRECATED scan "Please use scanl instead" #-} {-# INLINE_NORMAL scan #-} scan :: Monad m => Fold m b c -> Unfold m a b -> Unfold m a c-scan = scanWith False+scan (Fold s i e f) = scanWith False (Scanl s i e f) -- | Scan the output of an 'Unfold' to change it in a stateful manner. --@@ -584,7 +536,7 @@ where - inject seed = pure seed+ inject = pure {-# INLINE_LATE step #-} step (i, action) =@@ -603,6 +555,32 @@ {-# INLINE_LATE step #-} step action = (`Yield` action) <$> action +{-# INLINE repeat #-}+repeat :: Applicative m => Unfold m a a+repeat = lmap pure repeatM++-- | Takes a tuple whose first element is repeated and the second element is+-- passed through the supplied unfold.+--+-- >>> zipRepeat = fmap (\(x,y) -> (fst x, y)) . Unfold.carry . Unfold.lmap snd+-- >>> zipRepeat = Unfold.zipArrowWith (,) Unfold.repeat+--+{-# INLINE_NORMAL zipRepeat #-}+zipRepeat :: Functor m => Unfold m a b -> Unfold m (c,a) (c,b)+-- zipRepeat = zipArrowWith (,) repeat+zipRepeat (Unfold ustep uinject) = Unfold step (\(c,a) -> (c,) <$> uinject a)++ where++ func a r =+ case r of+ Yield x s -> Yield (a, x) (a, s)+ Skip s -> Skip (a, s)+ Stop -> Stop++ {-# INLINE_LATE step #-}+ step (a, st) = fmap (func a) (ustep st)+ -- | Generates an infinite stream starting with the given seed and applying the -- given function repeatedly. --@@ -742,10 +720,12 @@ dropWhile :: Monad m => (b -> Bool) -> Unfold m a b -> Unfold m a b dropWhile f = dropWhileM (return . f) -{-# INLINE_NORMAL joinInnerGeneric #-}-joinInnerGeneric :: Monad m =>+-- | Cross intersection of two unfolds. See+-- 'Streamly.Internal.Data.Stream.innerJoin' for more details.+{-# INLINE_NORMAL innerJoin #-}+innerJoin :: Monad m => (b -> c -> Bool) -> Unfold m a b -> Unfold m a c -> Unfold m a (b, c)-joinInnerGeneric eq s1 s2 = filter (\(a, b) -> a `eq` b) $ cross s1 s2+innerJoin eq s1 s2 = filter (\(a, b) -> a `eq` b) $ cross s1 s2 ------------------------------------------------------------------------------ -- Exceptions
src/Streamly/Internal/Data/Unfold/Enumeration.hs view
@@ -65,7 +65,6 @@ import Data.Word import Numeric.Natural import Data.Functor.Identity (Identity(..))-import Streamly.Internal.Data.Stream.StreamD.Step (Step(..)) import Streamly.Internal.Data.Unfold.Type import Prelude hiding (map, mapM, takeWhile, take, filter, const, zipWith@@ -88,7 +87,7 @@ -- the value overflows it keeps enumerating in a cycle: -- -- @--- >>> Stream.fold Fold.toList $ Stream.take 10 $ Stream.unfold Unfold.enumerateFromStepNum (255::Word8,1)+-- >>> Stream.toList $ Stream.take 10 $ Stream.unfold Unfold.enumerateFromStepNum (255::Word8,1) -- [255,0,1,2,3,4,5,6,7,8] -- -- @@@ -125,7 +124,7 @@ -- -- Example: -- @--- >>> Stream.fold Fold.toList $ Stream.take 10 $ Stream.unfold enumerateFromThenNum (255::Word8,0)+-- >>> Stream.toList $ Stream.take 10 $ Stream.unfold enumerateFromThenNum (255::Word8,0) -- [255,0,1,2,3,4,5,6,7,8] -- -- @@@ -152,7 +151,7 @@ -- -- @ -- >>> enumerateFromNum = lmap (\from -> (from, 1)) Unfold.enumerateFromStepNum--- >>> Stream.fold Fold.toList $ Stream.take 6 $ Stream.unfold enumerateFromNum (0.9)+-- >>> Stream.toList $ Stream.take 6 $ Stream.unfold enumerateFromNum (0.9) -- [0.9,1.9,2.9,3.9,4.9,5.9] -- -- @@@ -162,7 +161,7 @@ -- -- @ -- >>> enumerateFromNum = lmap (\from -> (from, from + 1)) Unfold.enumerateFromThenNum--- >>> Stream.fold Fold.toList $ Stream.take 6 $ Stream.unfold enumerateFromNum (0.9)+-- >>> Stream.toList $ Stream.take 6 $ Stream.unfold enumerateFromNum (0.9) -- [0.9,1.9,2.9,3.8999999999999995,4.8999999999999995,5.8999999999999995] -- -- @@@ -284,7 +283,7 @@ -- specified upper limit rounded to the nearest integral value: -- -- @--- >>> Stream.fold Fold.toList $ Stream.unfold Unfold.enumerateFromToFractional (0.1, 6.3)+-- >>> Stream.toList $ Stream.unfold Unfold.enumerateFromToFractional (0.1, 6.3) -- [0.1,1.1,2.1,3.1,4.1,5.1,6.1] -- -- @@@ -389,24 +388,15 @@ -- @from@, enumerating up to 'maxBound' when the type is 'Bounded' or -- generating an infinite stream when the type is not 'Bounded'. --- -- >>> import qualified Streamly.Data.Stream as Stream- -- >>> import qualified Streamly.Internal.Data.Unfold as Unfold- --- -- @- -- >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.unfold Unfold.enumerateFrom (0 :: Int)+ -- >>> Stream.toList $ Stream.take 4 $ Stream.unfold Unfold.enumerateFrom (0 :: Int) -- [0,1,2,3] --- -- @- -- -- For 'Fractional' types, enumeration is numerically stable. However, no -- overflow or underflow checks are performed. --- -- @- -- >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.unfold Unfold.enumerateFrom 1.1+ -- >>> Stream.toList $ Stream.take 4 $ Stream.unfold Unfold.enumerateFrom 1.1 -- [1.1,2.1,3.1,4.1] --- -- @- -- -- /Pre-release/ -- enumerateFrom :: Monad m => Unfold m a a@@ -415,27 +405,18 @@ -- @from@, enumerating the type up to the value @to@. If @to@ is smaller than -- @from@ then an empty stream is returned. --- -- >>> import qualified Streamly.Data.Stream as Stream- -- >>> import qualified Streamly.Internal.Data.Unfold as Unfold- --- -- @- -- >>> Stream.fold Fold.toList $ Stream.unfold Unfold.enumerateFromTo (0, 4)+ -- >>> Stream.toList $ Stream.unfold Unfold.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. --- -- @- -- >>> Stream.fold Fold.toList $ Stream.unfold Unfold.enumerateFromTo (1.1, 4)+ -- >>> Stream.toList $ Stream.unfold Unfold.enumerateFromTo (1.1, 4) -- [1.1,2.1,3.1,4.1] --- -- >>> Stream.fold Fold.toList $ Stream.unfold Unfold.enumerateFromTo (1.1, 4.6)+ -- >>> Stream.toList $ Stream.unfold Unfold.enumerateFromTo (1.1, 4.6) -- [1.1,2.1,3.1,4.1,5.1] --- -- @- -- -- /Pre-release/ enumerateFromTo :: Monad m => Unfold m (a, a) a @@ -445,18 +426,12 @@ -- after @from@. For 'Bounded' types the stream ends when 'maxBound' is -- reached, for unbounded types it keeps enumerating infinitely. --- -- >>> import qualified Streamly.Data.Stream as Stream- -- >>> import qualified Streamly.Internal.Data.Unfold as Unfold- --- -- @- -- >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.unfold Unfold.enumerateFromThen (0, 2)+ -- >>> Stream.toList $ Stream.take 4 $ Stream.unfold Unfold.enumerateFromThen (0, 2) -- [0,2,4,6] --- -- >>> Stream.fold Fold.toList $ Stream.take 4 $ Stream.unfold Unfold.enumerateFromThen (0,(-2))+ -- >>> Stream.toList $ Stream.take 4 $ Stream.unfold Unfold.enumerateFromThen (0,(-2)) -- [0,-2,-4,-6] --- -- @- -- -- /Pre-release/ enumerateFromThen :: Monad m => Unfold m (a, a) a @@ -465,17 +440,11 @@ -- @to@. Enumeration can occur downwards or upwards depending on whether @then@ -- comes before or after @from@. --- -- >>> import qualified Streamly.Data.Stream as Stream- -- >>> import qualified Streamly.Internal.Data.Unfold as Unfold- --- -- @- -- >>> Stream.fold Fold.toList $ Stream.unfold Unfold.enumerateFromThenTo (0, 2, 6)+ -- >>> Stream.toList $ Stream.unfold Unfold.enumerateFromThenTo (0, 2, 6) -- [0,2,4,6] --- -- >>> Stream.fold Fold.toList $ Stream.unfold Unfold.enumerateFromThenTo (0, (-2), (-6))+ -- >>> Stream.toList $ Stream.unfold Unfold.enumerateFromThenTo (0, (-2), (-6)) -- [0,-2,-4,-6]- --- -- @ -- -- /Pre-release/ enumerateFromThenTo :: Monad m => Unfold m (a, a, a) a
src/Streamly/Internal/Data/Unfold/Type.hs view
@@ -26,7 +26,7 @@ -- much less efficient when compared to combinators using 'Unfold'. For -- example, the 'Streamly.Data.Stream.concatMap' combinator which uses @a -> t m b@ -- (where @t@ is a stream type) to generate streams is much less efficient--- compared to 'Streamly.Data.Stream.unfoldMany'.+-- compared to 'Streamly.Data.Stream.unfoldEach'. -- -- On the other hand, transformation operations on stream types are as -- efficient as transformations on 'Unfold'.@@ -39,17 +39,13 @@ module Streamly.Internal.Data.Unfold.Type (- -- * Setup- -- | To execute the code examples provided in this module in ghci, please- -- run the following commands first.- --- -- $setup- -- * General Notes -- $notes -- * Type- Unfold (..)+ -- StreamD Step type re-exported+ Step(..)+ , Unfold (..) -- * Basic Constructors , mkUnfoldM@@ -66,34 +62,36 @@ -- * From Containers , fromList+ , fromTuple -- * Transformations , lmap , lmapM , map- , map2 , mapM- , mapM2 , both+ , supply , first , second+ , carry -- * Trimming- , takeWhileMWithInput , takeWhileM , takeWhile -- * Nesting+ , interleave , ConcatState (..)- , many- , many2- , manyInterleave- -- , manyInterleave2+ , unfoldEach+ , unfoldEachInterleave -- Applicative , crossApplySnd , crossApplyFst , crossWithM+ , fairCrossWithM+ , fairCrossWith+ , fairCross , crossWith , cross , crossApply@@ -103,11 +101,22 @@ , concatMap , bind + , zipArrowWithM+ , zipArrowWith , zipWithM , zipWith++ -- * Deprecated+ , many+ , many2+ , manyInterleave+ , map2+ , mapM2+ , takeWhileMWithInput ) where +#include "deprecation.h" #include "inline.hs" -- import Control.Arrow (Arrow(..))@@ -115,8 +124,7 @@ import Control.Monad ((>=>)) import Data.Void (Void) import Fusion.Plugin.Types (Fuse(..))-import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))-import Streamly.Internal.Data.Stream.StreamD.Step (Step(..))+import Streamly.Internal.Data.Stream.Step (Step(..)) import Prelude hiding (map, mapM, concatMap, zipWith, takeWhile) @@ -150,13 +158,13 @@ -- -- This allows an important optimization to occur in several cases, making the -- 'Unfold' a more efficient abstraction. Consider the 'concatMap' and--- 'unfoldMany' operations, the latter is more efficient. 'concatMap'+-- 'unfoldEach' operations, the latter is more efficient. 'concatMap' -- generates a new stream object from each element in the stream by applying -- the supplied function to the element, the stream object includes the "step" -- function as well as the initial "state" of the stream. Since the stream is -- generated dynamically the compiler does not know the step function or the -- state type statically at compile time, therefore, it cannot inline it. On--- the other hand in case of 'unfoldMany' the compiler has visibility into+-- the other hand in case of 'unfoldEach' the compiler has visibility into -- the unfold's state generation function, therefore, the compiler knows all -- the types statically and it can inline the inject as well as the step -- functions, generating efficient code. Essentially, the stream is not opaque@@ -221,6 +229,8 @@ -- Basic constructors ------------------------------------------------------------------------------ +-- XXX unfoldWith?+ -- | Make an unfold from @step@ and @inject@ functions. -- -- /Pre-release/@@ -279,32 +289,35 @@ -- >>> Unfold.fold Fold.toList u [1..5] -- [2,3,4,5,6] ----- @--- lmap f = Unfold.many (Unfold.function f)--- @+-- Definition: --+-- >>> lmap f = Unfold.unfoldEach (Unfold.function f)+-- {-# INLINE_NORMAL lmap #-} lmap :: (a -> c) -> Unfold m c b -> Unfold m a b lmap f (Unfold ustep uinject) = Unfold ustep (uinject Prelude.. f) -- | Map an action on the input argument of the 'Unfold'. ----- @--- lmapM f = Unfold.many (Unfold.functionM f)--- @+-- Definition: --+-- lmapM f = Unfold.unfoldEach (Unfold.functionM f)+-- {-# INLINE_NORMAL lmapM #-} lmapM :: Monad m => (a -> m c) -> Unfold m c b -> Unfold m a b lmapM f (Unfold ustep uinject) = Unfold ustep (f >=> uinject) -- | Supply the seed to an unfold closing the input end of the unfold. ----- @--- both a = Unfold.lmap (Prelude.const a)--- @+-- >>> supply a = Unfold.lmap (Prelude.const a) -- -- /Pre-release/ --+{-# INLINE_NORMAL supply #-}+supply :: a -> Unfold m a b -> Unfold m () b+supply a = lmap (Prelude.const a)++{-# DEPRECATED both "Use supply instead." #-} {-# INLINE_NORMAL both #-} both :: a -> Unfold m a b -> Unfold m Void b both a = lmap (Prelude.const a)@@ -341,9 +354,13 @@ -- Filter input ------------------------------------------------------------------------------ +-- |+-- >>> takeWhileMWithInput f u = Unfold.map snd $ Unfold.takeWhileM (\(a,b) -> f a b) (Unfold.carry u) {-# INLINE_NORMAL takeWhileMWithInput #-} takeWhileMWithInput :: Monad m => (a -> b -> m Bool) -> Unfold m a b -> Unfold m a b+takeWhileMWithInput f u = map snd $ takeWhileM (\(a,b) -> f a b) (carry u)+{- takeWhileMWithInput f (Unfold step1 inject1) = Unfold step inject where@@ -361,6 +378,7 @@ return $ if b then Yield x (Tuple' a s) else Stop Skip s -> return $ Skip (Tuple' a s) Stop -> return Stop+-} -- | Same as 'takeWhile' but with a monadic predicate. --@@ -393,8 +411,11 @@ -- Functor ------------------------------------------------------------------------------ +{-# DEPRECATED mapM2 "Use carry with mapM instead." #-} {-# INLINE_NORMAL mapM2 #-} mapM2 :: Monad m => (a -> b -> m c) -> Unfold m a b -> Unfold m a c+mapM2 f = mapM (uncurry f) . carry+{- mapM2 f (Unfold ustep uinject) = Unfold step inject where inject a = do@@ -408,12 +429,11 @@ Yield x s -> f inp x >>= \a -> return $ Yield a (inp, s) Skip s -> return $ Skip (inp, s) Stop -> return Stop+-} -- | Apply a monadic function to each element of the stream and replace it -- with the output of the resulting action. ----- >>> mapM f = Unfold.mapM2 (const f)--- {-# INLINE_NORMAL mapM #-} mapM :: Monad m => (b -> m c) -> Unfold m a b -> Unfold m a c -- mapM f = mapM2 (const f)@@ -427,37 +447,37 @@ Skip s -> return $ Skip s Stop -> return Stop --- XXX We can also introduce a withInput combinator which will output the input--- seed along with the output as a tuple.---- |+-- | Carry the input along with the output as the first element of the output+-- tuple. ----- >>> map2 f = Unfold.mapM2 (\a b -> pure (f a b))+-- carry = Unfold.lmap (\x -> (x,x)) . Unfold.zipRepeat ----- Note that the seed may mutate (e.g. if the seed is a Handle or IORef) as--- stream is generated from it, so we need to be careful when reusing the seed--- while the stream is being generated from it.+-- Note that the input seed may mutate (e.g. if the seed is a Handle or IORef)+-- as stream is generated from it, so we need to be careful when reusing the+-- seed while the stream is being generated from it. ---{-# INLINE_NORMAL map2 #-}-map2 :: Functor m => (a -> b -> c) -> Unfold m a b -> Unfold m a c--- map2 f = mapM2 (\a b -> pure (f a b))-map2 f (Unfold ustep uinject) = Unfold step (\a -> (a,) <$> uinject a)+{-# INLINE_NORMAL carry #-}+carry :: Functor m => Unfold m a b -> Unfold m a (a,b)+carry (Unfold ustep uinject) = Unfold step (\a -> (a,) <$> uinject a) where func a r = case r of- Yield x s -> Yield (f a x) (a, s)+ Yield x s -> Yield (a, x) (a, s) Skip s -> Skip (a, s) Stop -> Stop {-# INLINE_LATE step #-} step (a, st) = fmap (func a) (ustep st) +{-# DEPRECATED map2 "Use carry with map instead." #-}+{-# INLINE_NORMAL map2 #-}+map2 :: Functor m => (a -> b -> c) -> Unfold m a b -> Unfold m a c+map2 f = map (uncurry f) . carry+ -- | Map a function on the output of the unfold (the type @b@). ----- >>> map f = Unfold.map2 (const f)--- -- /Pre-release/ {-# INLINE_NORMAL map #-} map :: Functor m => (b -> c) -> Unfold m a b -> Unfold m a c@@ -503,9 +523,25 @@ -- > fromPure = fromEffect . pure -- -- /Pre-release/+{-# INLINE fromPure #-} fromPure :: Applicative m => b -> Unfold m a b fromPure = fromEffect Prelude.. pure +data TupleState a = TupleBoth a a | TupleOne a | TupleNone++-- | Convert a tuple to a 'Stream'.+--+{-# INLINE_LATE fromTuple #-}+fromTuple :: Applicative m => Unfold m (a,a) a+fromTuple = Unfold step (\(x,y) -> pure $ TupleBoth x y)++ where++ {-# INLINE_LATE step #-}+ step (TupleBoth x y) = pure $ Yield x (TupleOne y)+ step (TupleOne y) = pure $ Yield y TupleNone+ step TupleNone = pure Stop+ -- XXX Check if "unfold (fromList [1..10])" fuses, if it doesn't we can use -- rewrite rules to rewrite list enumerations to unfold enumerations. @@ -539,12 +575,18 @@ Unfold m a b -> Unfold m a c -> Unfold m a b crossApplyFst (Unfold _step1 _inject1) (Unfold _step2 _inject2) = undefined +{- {-# ANN type Many2State Fuse #-} data Many2State x s1 s2 = Many2Outer x s1 | Many2Inner x s1 s2+-} +{-# DEPRECATED many2 "Use carry with unfoldEach instead." #-} {-# INLINE_NORMAL many2 #-}-many2 :: Monad m => Unfold m (a, b) c -> Unfold m a b -> Unfold m a c-many2 (Unfold step2 inject2) (Unfold step1 inject1) = Unfold step inject+many2 :: Monad m =>+ Unfold m (a, b) c -> Unfold m a b -> Unfold m a c+many2 u1 u2 = unfoldEach u1 (carry u2)+{-+unfoldEach2 (Unfold step2 inject2) (Unfold step1 inject1) = Unfold step inject where @@ -568,15 +610,16 @@ Yield x s -> Yield x (Many2Inner a ost s) Skip s -> Skip (Many2Inner a ost s) Stop -> Skip (Many2Outer a ost)+-} data Cross a s1 b s2 = CrossOuter a s1 | CrossInner a s1 b s2 +-- >> f1 f u = Unfold.mapM (\((_, c), b) -> f b c) Unfold.carry (Unfold.lmap fst u))+-- >> crossWithM f u = Unfold.unfoldEach2 (f1 f u)+ -- | Create a cross product (vector product or cartesian product) of the -- output streams of two unfolds using a monadic combining function. ----- >>> f1 f u = Unfold.mapM2 (\(_, c) b -> f b c) (Unfold.lmap fst u)--- >>> crossWithM f u = Unfold.many2 (f1 f u)--- -- /Pre-release/ {-# INLINE_NORMAL crossWithM #-} crossWithM :: Monad m =>@@ -607,6 +650,59 @@ Skip s -> return $ Skip (CrossInner a s1 b s) Stop -> return $ Skip (CrossOuter a s1) +data FairUnfoldState a o i =+ FairUnfoldInit a o ([i] -> [i])+ | FairUnfoldNext a o ([i] -> [i]) [i]+ | FairUnfoldDrain ([i] -> [i]) [i]++{-# INLINE_NORMAL fairCrossWithM #-}+fairCrossWithM :: Monad m =>+ (b -> c -> m d) -> Unfold m a b -> Unfold m a c -> Unfold m a d+fairCrossWithM f (Unfold step1 inject1) (Unfold step2 inject2) =+ Unfold step inject++ where++ inject a = do+ s1 <- inject1 a+ return $ FairUnfoldInit a s1 id++ {-# INLINE_LATE step #-}+ step (FairUnfoldInit a o ls) = do+ r <- step1 o+ case r of+ Yield b o' -> do+ i <- inject2 a+ i `seq` return (Skip (FairUnfoldNext a o' id (ls [(b,i)])))+ Skip o' -> return $ Skip (FairUnfoldInit a o' ls)+ Stop -> return $ Skip (FairUnfoldDrain id (ls []))++ step (FairUnfoldNext a o ys []) =+ return $ Skip (FairUnfoldInit a o ys)++ step (FairUnfoldNext a o ys ((b,st):ls)) = do+ r <- step2 st+ case r of+ Yield c s ->+ f b c >>= \x ->+ return $ Yield x (FairUnfoldNext a o (ys . ((b, s) :)) ls)+ Skip s -> return $ Skip (FairUnfoldNext a o ys ((b,s) : ls))+ Stop -> return $ Skip (FairUnfoldNext a o ys ls)++ step (FairUnfoldDrain ys []) =+ case ys [] of+ [] -> return Stop+ xs -> return $ Skip (FairUnfoldDrain id xs)++ step (FairUnfoldDrain ys ((b,st):ls)) = do+ r <- step2 st+ case r of+ Yield c s ->+ f b c >>= \x ->+ return $ Yield x (FairUnfoldDrain (ys . ((b,s) :)) ls)+ Skip s -> return $ Skip (FairUnfoldDrain ys ((b,s) : ls))+ Stop -> return $ Skip (FairUnfoldDrain ys ls)+ -- | Like 'crossWithM' but uses a pure combining function. -- -- > crossWith f = crossWithM (\b c -> return $ f b c)@@ -622,6 +718,11 @@ (b -> c -> d) -> Unfold m a b -> Unfold m a c -> Unfold m a d crossWith f = crossWithM (\b c -> return $ f b c) +{-# INLINE fairCrossWith #-}+fairCrossWith :: Monad m =>+ (b -> c -> d) -> Unfold m a b -> Unfold m a c -> Unfold m a d+fairCrossWith f = fairCrossWithM (\b c -> return $ f b c)+ -- | See 'crossWith'. -- -- Definition:@@ -641,6 +742,11 @@ cross :: Monad m => Unfold m a b -> Unfold m a c -> Unfold m a (b, c) cross = crossWith (,) +{-# INLINE_NORMAL fairCross #-}+fairCross :: Monad m => Unfold m a b -> Unfold m a c -> Unfold m a (b, c)+fairCross = fairCrossWith (,)++{-# INLINE crossApply #-} crossApply :: Monad m => Unfold m a (b -> c) -> Unfold m a b -> Unfold m a c crossApply u1 u2 = fmap (\(a, b) -> a b) (cross u1 u2) @@ -796,12 +902,10 @@ -- | Apply the first unfold to each output element of the second unfold and -- flatten the output in a single stream. ----- >>> many u = Unfold.many2 (Unfold.lmap snd u)----{-# INLINE_NORMAL many #-}-many :: Monad m => Unfold m b c -> Unfold m a b -> Unfold m a c+{-# INLINE_NORMAL unfoldEach #-}+unfoldEach, many :: Monad m => Unfold m b c -> Unfold m a b -> Unfold m a c -- many u1 = many2 (lmap snd u1)-many (Unfold step2 inject2) (Unfold step1 inject1) = Unfold step inject+unfoldEach (Unfold step2 inject2) (Unfold step1 inject1) = Unfold step inject where @@ -826,6 +930,8 @@ Skip s -> Skip (ConcatInner ost s) Stop -> Skip (ConcatOuter ost) +RENAME(many,unfoldEach)+ {- -- XXX There are multiple possible ways to combine the unfolds, "many" appends -- them, we could also have other variants of "many" e.g. manyInterleave.@@ -844,15 +950,52 @@ -- Zipping ------------------------------------------------------------------------------- +-- XXX call the original zipWith as distribute and this one as zip? or this+-- could be called divide.+--+{-# INLINE_NORMAL zipArrowWithM #-}+zipArrowWithM :: Monad m+ => (b -> c -> m d) -> Unfold m a1 b -> Unfold m a2 c -> Unfold m (a1,a2) d+zipArrowWithM f (Unfold step1 inject1) (Unfold step2 inject2) = Unfold step inject++ where++ inject (x,y) = do+ s1 <- inject1 x+ s2 <- inject2 y+ return (s1, s2, Nothing)++ {-# INLINE_LATE step #-}+ step (s1, s2, Nothing) = do+ r <- step1 s1+ return $+ case r of+ Yield x s -> Skip (s, s2, Just x)+ Skip s -> Skip (s, s2, Nothing)+ Stop -> Stop++ step (s1, s2, Just x) = do+ r <- step2 s2+ case r of+ Yield y s -> do+ z <- f x y+ return $ Yield z (s1, s, Nothing)+ Skip s -> return $ Skip (s1, s, Just x)+ Stop -> return Stop+ -- | Distribute the input to two unfolds and then zip the outputs to a single -- stream using a monadic zip function. --+-- >>> zipWithM f u1 u2 = Unfold.lmap (\x -> (x,x)) (Unfold.zipArrowWithM f u1 u2)+-- -- Stops as soon as any of the unfolds stops. -- -- /Pre-release/ {-# INLINE_NORMAL zipWithM #-} zipWithM :: Monad m => (b -> c -> m d) -> Unfold m a b -> Unfold m a c -> Unfold m a d+zipWithM f u1 u2 = lmap (\x -> (x,x)) (zipArrowWithM f u1 u2)+{- zipWithM f (Unfold step1 inject1) (Unfold step2 inject2) = Unfold step inject where@@ -879,6 +1022,7 @@ return $ Yield z (s1, s, Nothing) Skip s -> return $ Skip (s1, s, Just x) Stop -> return Stop+-} -- | Like 'zipWithM' but with a pure zip function. --@@ -895,6 +1039,11 @@ => (b -> c -> d) -> Unfold m a b -> Unfold m a c -> Unfold m a d zipWith f = zipWithM (\a b -> return (f a b)) +{-# INLINE zipArrowWith #-}+zipArrowWith :: Monad m+ => (b -> c -> d) -> Unfold m a1 b -> Unfold m a2 c -> Unfold m (a1,a2) d+zipArrowWith f = zipArrowWithM (\a b -> return (f a b))+ ------------------------------------------------------------------------------- -- Arrow -------------------------------------------------------------------------------@@ -935,25 +1084,69 @@ -- -- Similarly we can also have other binary combining ops like append, mergeBy. -- We already have zipWith.--- +data InterleaveState s1 s2 =+ InterleaveFirst s1 s2+ | InterleaveSecond s1 s2+ | InterleaveSecondOnly s2+ | InterleaveFirstOnly s1++-- | Interleave the streams generated by two unfolds.+{-# INLINE_NORMAL interleave #-}+interleave :: Monad m => Unfold m a c -> Unfold m b c -> Unfold m (a,b) c+interleave (Unfold step1 inject1) (Unfold step2 inject2) =+ Unfold step inject++ where++ inject (a,b) = do+ s1 <- inject1 a+ s2 <- inject2 b+ return (InterleaveFirst s1 s2)++ {-# INLINE_LATE step #-}+ step (InterleaveFirst st1 st2) = do+ r <- step1 st1+ return $ case r of+ Yield a s -> Yield a (InterleaveSecond s st2)+ Skip s -> Skip (InterleaveFirst s st2)+ Stop -> Skip (InterleaveSecondOnly st2)++ step (InterleaveSecond st1 st2) = do+ r <- step2 st2+ return $ case r of+ Yield a s -> Yield a (InterleaveFirst st1 s)+ Skip s -> Skip (InterleaveSecond st1 s)+ Stop -> Skip (InterleaveFirstOnly st1)++ step (InterleaveFirstOnly st1) = do+ r <- step1 st1+ return $ case r of+ Yield a s -> Yield a (InterleaveFirstOnly s)+ Skip s -> Skip (InterleaveFirstOnly s)+ Stop -> Stop++ step (InterleaveSecondOnly st2) = do+ r <- step2 st2+ return $ case r of+ Yield a s -> Yield a (InterleaveSecondOnly s)+ Skip s -> Skip (InterleaveSecondOnly s)+ Stop -> Stop+ data ManyInterleaveState o i = ManyInterleaveOuter o [i] | ManyInterleaveInner o [i] | ManyInterleaveInnerL [i] [i] | ManyInterleaveInnerR [i] [i] --- | 'Streamly.Internal.Data.Stream.unfoldManyInterleave' for+-- | See 'Streamly.Internal.Data.Stream.unfoldEachInterleave' for -- documentation and notes. ----- This is almost identical to unfoldManyInterleave in StreamD module.------ The 'many' combinator is in fact 'manyAppend' to be more explicit in naming.--- -- /Internal/-{-# INLINE_NORMAL manyInterleave #-}-manyInterleave :: Monad m => Unfold m a b -> Unfold m c a -> Unfold m c b-manyInterleave (Unfold istep iinject) (Unfold ostep oinject) =+{-# INLINE_NORMAL unfoldEachInterleave #-}+unfoldEachInterleave, manyInterleave :: Monad m =>+ Unfold m a b -> Unfold m c a -> Unfold m c b+unfoldEachInterleave (Unfold istep iinject) (Unfold ostep oinject) = Unfold step inject where@@ -1001,3 +1194,5 @@ Yield x s -> Yield x (ManyInterleaveInnerR (s:ls) rs) Skip s -> Skip (ManyInterleaveInnerR ls (s:rs)) Stop -> Skip (ManyInterleaveInnerR ls rs)++RENAME(manyInterleave,unfoldEachInterleave)
src/Streamly/Internal/FileSystem/Dir.hs view
@@ -6,10 +6,10 @@ -- -- License : BSD3 -- Maintainer : streamly@composewell.com--- Stability : pre-release -- Portability : GHC module Streamly.Internal.FileSystem.Dir+{-# DEPRECATED "Please use \"Streamly.Internal.FileSystem.DirIO\" instead." #-} ( -- * Streams read@@ -83,153 +83,51 @@ ) where +import Control.Monad.Catch (MonadCatch) import Control.Monad.IO.Class (MonadIO(..)) import Data.Bifunctor (bimap)-import Data.Either (isRight, isLeft, fromLeft, fromRight) import Streamly.Data.Stream (Stream) import Streamly.Internal.Data.Unfold.Type (Unfold(..)) import System.FilePath ((</>))--import qualified Streamly.Data.Unfold as UF-import qualified Streamly.Internal.Data.Unfold as UF (mapM2) import qualified Streamly.Data.Stream as S-import qualified System.Directory as Dir -import Prelude hiding (read)--{--{-# INLINABLE readArrayUpto #-}-readArrayUpto :: Int -> Handle -> IO (Array Word8)-readArrayUpto size h = do- ptr <- mallocPlainForeignPtrBytes size- -- ptr <- mallocPlainForeignPtrAlignedBytes size (alignment (undefined :: Word8))- withForeignPtr ptr $ \p -> do- n <- hGetBufSome h p size- let v = Array- { aStart = ptr- , arrEnd = p `plusPtr` n- , arrBound = p `plusPtr` size- }- -- XXX shrink only if the diff is significant- shrinkToFit v------------------------------------------------------------------------------------ Stream of Arrays IO------------------------------------------------------------------------------------ | @toChunksWithBufferOf size h@ reads a stream of arrays from file handle @h@.--- The maximum size of a single array is specified by @size@. The actual size--- read may be less than or equal to @size@.-{-# INLINE _toChunksWithBufferOf #-}-_toChunksWithBufferOf :: MonadIO m => Int -> Handle -> Stream m (Array Word8)-_toChunksWithBufferOf size h = go- where- -- XXX use cons/nil instead- go = mkStream $ \_ yld _ stp -> do- arr <- liftIO $ readArrayUpto size h- if A.length arr == 0- then stp- else yld arr go---- | @toChunksWithBufferOf size handle@ reads a stream of arrays from the file--- handle @handle@. The maximum size of a single array is limited to @size@.--- The actual size read may be less than or equal to @size@.------ @since 0.7.0-{-# INLINE_NORMAL toChunksWithBufferOf #-}-toChunksWithBufferOf :: MonadIO m => Int -> Handle -> Stream m (Array Word8)-toChunksWithBufferOf size h = D.fromStreamD (D.Stream step ())- where- {-# INLINE_LATE step #-}- step _ _ = do- arr <- liftIO $ readArrayUpto size h- return $- case A.length arr of- 0 -> D.Stop- _ -> D.Yield arr ()---- | Unfold the tuple @(bufsize, handle)@ into a stream of 'Word8' arrays.--- Read requests to the IO device are performed using a buffer of size--- @bufsize@. The size of an array in the resulting stream is always less than--- or equal to @bufsize@.------ @since 0.7.0-{-# INLINE_NORMAL readChunksWithBufferOf #-}-readChunksWithBufferOf :: MonadIO m => Unfold m (Int, Handle) (Array Word8)-readChunksWithBufferOf = Unfold step return- where- {-# INLINE_LATE step #-}- step (size, h) = do- arr <- liftIO $ readArrayUpto size h- return $- case A.length arr of- 0 -> D.Stop- _ -> D.Yield arr (size, h)+import Streamly.Internal.FileSystem.Path (Path) --- XXX read 'Array a' instead of Word8------ | @toChunks handle@ reads a stream of arrays from the specified file--- handle. The maximum size of a single array is limited to--- @defaultChunkSize@. The actual size read may be less than or equal to--- @defaultChunkSize@.------ > toChunks = toChunksWithBufferOf defaultChunkSize------ @since 0.7.0-{-# INLINE toChunks #-}-toChunks :: MonadIO m => Handle -> Stream m (Array Word8)-toChunks = toChunksWithBufferOf defaultChunkSize+import qualified Streamly.Internal.FileSystem.Path as Path+import qualified Streamly.Internal.FileSystem.DirIO as DirIO+import qualified Streamly.Internal.Data.Unfold as Unfold --- | Unfolds a handle into a stream of 'Word8' arrays. Requests to the IO--- device are performed using a buffer of size--- 'Streamly.Internal.Data.Array.Type.defaultChunkSize'. The--- size of arrays in the resulting stream are therefore less than or equal to--- 'Streamly.Internal.Data.Array.Type.defaultChunkSize'.------ @since 0.7.0-{-# INLINE readChunks #-}-readChunks :: MonadIO m => Unfold m Handle (Array Word8)-readChunks = UF.first readChunksWithBufferOf defaultChunkSize+import Prelude hiding (read) ----------------------------------------------------------------------------------- Read a Directory to Stream--------------------------------------------------------------------------------+--------------------------------------------------------------------------------+-- Helpers+-------------------------------------------------------------------------------- --- TODO for concurrent streams implement readahead IO. We can send multiple--- read requests at the same time. For serial case we can use async IO. We can--- also control the read throughput in mbps or IOPS.+{-# INLINE ePathMap #-}+ePathMap :: Either Path Path -> Either FilePath FilePath+ePathMap (Left a) = Left (Path.toString a)+ePathMap (Right a) = Right (Path.toString a) --- | Unfolds the tuple @(bufsize, handle)@ into a byte stream, read requests--- to the IO device are performed using buffers of @bufsize@.------ @since 0.7.0-{-# INLINE readWithBufferOf #-}-readWithBufferOf :: MonadIO m => Unfold m (Int, Handle) Word8-readWithBufferOf = UF.many readChunksWithBufferOf A.read+{-# INLINE pMapUnfold #-}+pMapUnfold :: MonadCatch m => Unfold m Path Path -> Unfold m FilePath FilePath+pMapUnfold = fmap Path.toString . Unfold.lmapM Path.fromString --- | @toStreamWithBufferOf bufsize handle@ reads a byte stream from a file--- handle, reads are performed in chunks of up to @bufsize@.------ /Pre-release/-{-# INLINE toStreamWithBufferOf #-}-toStreamWithBufferOf :: MonadIO m => Int -> Handle -> Stream m Word8-toStreamWithBufferOf chunkSize h = AS.concat $ toChunksWithBufferOf chunkSize h--}+{-# INLINE pMapUnfoldE #-}+pMapUnfoldE+ :: MonadCatch m+ => Unfold m Path (Either Path Path)+ -> Unfold m FilePath (Either FilePath FilePath)+pMapUnfoldE = fmap ePathMap . Unfold.lmapM Path.fromString --- read child node names from a dir filtering out . and ..------ . and .. are an implementation artifact, and should probably not be used in--- user level abstractions.------ . does not seem to have any useful purpose. If we have the path of the dir--- then we will resolve it to get the inode of the dir so the . entry would be--- redundant. If we have the inode of the dir to read the dir then it is--- redundant. Is this for cross check when doing fsck?------ For .. we have the readAncestors API, we should not have this in the--- readChildren API.+--------------------------------------------------------------------------------+-- Functions+-------------------------------------------------------------------------------- --- XXX exception handling+#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+#define CONF id+#else+#define CONF (DirIO.followSymlinks True)+#endif -- | Read a directory emitting a stream with names of the children. Filter out -- "." and ".." entries.@@ -237,16 +135,8 @@ -- /Internal/ -- {-# INLINE reader #-}-reader :: MonadIO m => Unfold m FilePath FilePath-reader =- -- XXX use proper streaming read of the dir- UF.filter (\x -> x /= "." && x /= "..")- $ UF.lmapM (liftIO . Dir.getDirectoryContents) UF.fromList---- XXX We can use a more general mechanism to filter the contents of a--- directory. We can just stat each child and pass on the stat information. We--- can then use that info to do a general filtering. "find" like filters can be--- created.+reader :: (MonadIO m, MonadCatch m) => Unfold m FilePath FilePath+reader = fmap Path.toString $ Unfold.lmapM Path.fromString DirIO.reader -- | Read directories as Left and files as Right. Filter out "." and ".." -- entries.@@ -254,19 +144,13 @@ -- /Internal/ -- {-# INLINE eitherReader #-}-eitherReader :: MonadIO m => Unfold m FilePath (Either FilePath FilePath)-eitherReader = UF.mapM2 classify reader+eitherReader :: (MonadIO m, MonadCatch m) => Unfold m FilePath (Either FilePath FilePath)+eitherReader = pMapUnfoldE (DirIO.eitherReader CONF) - where - classify dir x = do- r <- liftIO $ Dir.doesDirectoryExist (dir ++ "/" ++ x)- return $ if r then Left x else Right x- {-# INLINE eitherReaderPaths #-}-eitherReaderPaths :: MonadIO m => Unfold m FilePath (Either FilePath FilePath)-eitherReaderPaths =- UF.mapM2 (\dir -> return . bimap (dir </>) (dir </>)) eitherReader+eitherReaderPaths ::(MonadIO m, MonadCatch m) => Unfold m FilePath (Either FilePath FilePath)+eitherReaderPaths = pMapUnfoldE (DirIO.eitherReaderPaths CONF) -- -- | Read files only.@@ -274,27 +158,27 @@ -- /Internal/ -- {-# INLINE fileReader #-}-fileReader :: MonadIO m => Unfold m FilePath FilePath-fileReader = fmap (fromRight undefined) $ UF.filter isRight eitherReader+fileReader :: (MonadIO m, MonadCatch m) => Unfold m FilePath FilePath+fileReader = pMapUnfold (DirIO.fileReader CONF) -- | Read directories only. Filter out "." and ".." entries. -- -- /Internal/ -- {-# INLINE dirReader #-}-dirReader :: MonadIO m => Unfold m FilePath FilePath-dirReader = fmap (fromLeft undefined) $ UF.filter isLeft eitherReader+dirReader :: (MonadIO m, MonadCatch m) => Unfold m FilePath FilePath+dirReader = pMapUnfold (DirIO.dirReader CONF) -- | Raw read of a directory. -- -- /Pre-release/ {-# INLINE read #-}-read :: MonadIO m => FilePath -> Stream m FilePath+read :: (MonadIO m, MonadCatch m) => FilePath -> Stream m FilePath read = S.unfold reader {-# DEPRECATED toStream "Please use 'read' instead" #-} {-# INLINE toStream #-}-toStream :: MonadIO m => String -> Stream m String+toStream :: (MonadIO m, MonadCatch m) => String -> Stream m String toStream = read -- | Read directories as Left and files as Right. Filter out "." and ".."@@ -302,18 +186,18 @@ -- -- /Pre-release/ {-# INLINE readEither #-}-readEither :: MonadIO m => FilePath -> Stream m (Either FilePath FilePath)+readEither :: (MonadIO m, MonadCatch m) => FilePath -> Stream m (Either FilePath FilePath) readEither = S.unfold eitherReader -- | Like 'readEither' but prefix the names of the files and directories with -- the supplied directory path. {-# INLINE readEitherPaths #-}-readEitherPaths :: MonadIO m => FilePath -> Stream m (Either FilePath FilePath)+readEitherPaths :: (MonadIO m, MonadCatch m) => FilePath -> Stream m (Either FilePath FilePath) readEitherPaths dir = fmap (bimap (dir </>) (dir </>)) $ readEither dir {-# DEPRECATED toEither "Please use 'readEither' instead" #-} {-# INLINE toEither #-}-toEither :: MonadIO m => FilePath -> Stream m (Either FilePath FilePath)+toEither :: (MonadIO m, MonadCatch m) => FilePath -> Stream m (Either FilePath FilePath) toEither = readEither -- | Read files only.@@ -321,12 +205,12 @@ -- /Internal/ -- {-# INLINE readFiles #-}-readFiles :: MonadIO m => FilePath -> Stream m FilePath+readFiles :: (MonadIO m, MonadCatch m) => FilePath -> Stream m FilePath readFiles = S.unfold fileReader {-# DEPRECATED toFiles "Please use 'readFiles' instead" #-} {-# INLINE toFiles #-}-toFiles :: MonadIO m => FilePath -> Stream m FilePath+toFiles :: (MonadIO m, MonadCatch m) => FilePath -> Stream m FilePath toFiles = readFiles -- | Read directories only.@@ -334,130 +218,10 @@ -- /Internal/ -- {-# INLINE readDirs #-}-readDirs :: MonadIO m => FilePath -> Stream m FilePath+readDirs :: (MonadIO m, MonadCatch m) => FilePath -> Stream m FilePath readDirs = S.unfold dirReader {-# DEPRECATED toDirs "Please use 'readDirs' instead" #-} {-# INLINE toDirs #-}-toDirs :: MonadIO m => String -> Stream m String+toDirs :: (MonadIO m, MonadCatch m) => String -> Stream m String toDirs = readDirs--{------------------------------------------------------------------------------------ Writing-------------------------------------------------------------------------------------------------------------------------------------------------------------------- Array IO (output)------------------------------------------------------------------------------------ | Write an 'Array' to a file handle.------ @since 0.7.0-{-# INLINABLE writeArray #-}-writeArray :: Storable a => Handle -> Array a -> IO ()-writeArray _ arr | A.length arr == 0 = return ()-writeArray h Array{..} = withForeignPtr aStart $ \p -> hPutBuf h p aLen- where- aLen =- let p = unsafeForeignPtrToPtr aStart- in arrEnd `minusPtr` p------------------------------------------------------------------------------------ Stream of Arrays IO-------------------------------------------------------------------------------------------------------------------------------------------------------------------- Writing------------------------------------------------------------------------------------ | Write a stream of arrays to a handle.------ @since 0.7.0-{-# INLINE fromChunks #-}-fromChunks :: (MonadIO m, Storable a)- => Handle -> Stream m (Array a) -> m ()-fromChunks h m = S.mapM_ (liftIO . writeArray h) m---- | @fromChunksWithBufferOf bufsize handle stream@ writes a stream of arrays--- to @handle@ after coalescing the adjacent arrays in chunks of @bufsize@.--- The chunk size is only a maximum and the actual writes could be smaller as--- we do not split the arrays to fit exactly to the specified size.------ @since 0.7.0-{-# INLINE fromChunksWithBufferOf #-}-fromChunksWithBufferOf :: (MonadIO m, Storable a)- => Int -> Handle -> Stream m (Array a) -> m ()-fromChunksWithBufferOf n h xs = fromChunks h $ AS.compact n xs---- | @fromStreamWithBufferOf bufsize handle stream@ writes @stream@ to @handle@--- in chunks of @bufsize@. A write is performed to the IO device as soon as we--- collect the required input size.------ @since 0.7.0-{-# INLINE fromStreamWithBufferOf #-}-fromStreamWithBufferOf :: MonadIO m => Int -> Handle -> Stream m Word8 -> m ()-fromStreamWithBufferOf n h m = fromChunks h $ S.chunksOf n m--- fromStreamWithBufferOf n h m = fromChunks h $ AS.chunksOf n m---- > write = 'writeWithBufferOf' A.defaultChunkSize------ | Write a byte stream to a file handle. Accumulates the input in chunks of--- up to 'Streamly.Internal.Data.Array.Type.defaultChunkSize' before writing.------ NOTE: This may perform better than the 'write' fold, you can try this if you--- need some extra perf boost.------ @since 0.7.0-{-# INLINE fromStream #-}-fromStream :: MonadIO m => Handle -> Stream m Word8 -> m ()-fromStream = fromStreamWithBufferOf defaultChunkSize---- | Write a stream of arrays to a handle. Each array in the stream is written--- to the device as a separate IO request.------ @since 0.7.0-{-# INLINE writeChunks #-}-writeChunks :: (MonadIO m, Storable a) => Handle -> Fold m (Array a) ()-writeChunks h = FL.drainBy (liftIO . writeArray h)---- | @writeChunksWithBufferOf bufsize handle@ writes a stream of arrays--- to @handle@ 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 -> Handle -> Fold m (Array a) ()-writeChunksWithBufferOf n h = lpackArraysChunksOf n (writeChunks h)---- GHC buffer size dEFAULT_FD_BUFFER_SIZE=8192 bytes.------ XXX test this--- Note that if you use a chunk size less than 8K (GHC's default buffer--- size) then you are advised to use 'NOBuffering' mode on the 'Handle' in case you--- do not want buffering to occur at GHC level as well. Same thing applies to--- writes as well.---- | @writeWithBufferOf reqSize handle@ writes the input stream to @handle@.--- Bytes in the input stream are collected into a buffer until we have a chunk--- of @reqSize@ and then written to the IO device.------ @since 0.7.0-{-# INLINE writeWithBufferOf #-}-writeWithBufferOf :: MonadIO m => Int -> Handle -> Fold m Word8 ()-writeWithBufferOf n h = FL.groupsOf n (writeNUnsafe n) (writeChunks h)---- > write = 'writeWithBufferOf' A.defaultChunkSize------ | Write a byte stream to a file handle. Accumulates the input in chunks of--- up to 'Streamly.Internal.Data.Array.Type.defaultChunkSize' before writing--- to the IO device.------ @since 0.7.0-{-# INLINE write #-}-write :: MonadIO m => Handle -> Fold m Word8 ()-write = writeWithBufferOf defaultChunkSize--}
+ src/Streamly/Internal/FileSystem/DirIO.hs view
@@ -0,0 +1,532 @@+#include "inline.hs"++-- |+-- Module : Streamly.Internal.FileSystem.DirIO+-- Copyright : (c) 2018 Composewell Technologies+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--+-- API Design notes:+--+-- The paths returned by "read" can be absolute (/usr/bin/ls), relative to+-- current directory (./bin/ls) or path segments relative to current dir+-- (bin/ls). To accomodate all the cases we can provide a prefix to attach+-- to the paths being generated. Alternatively, we could take the approach+-- of the higher layer doing that, but it is more efficient to allocate the+-- path buffer once rather than modifying it later. We can do this by+-- passing a fold to transform the output.+--+-- Also it may be more efficient to apply a filter to the paths right here+-- instead of applying it in a layer above. Cut the output at the source+-- rather than generate and then discard it later. We can do this by+-- passing a fold to filter the input.+--+-- When reading a symlink directory we can resolve the symlink and read the+-- destination directory or we can just emit the file it is pointing to and+-- the read can happen next at the higher level, in the traversal logic+-- (concatIterate). Not sure if one approach has any significant perf impact+-- over the other. Similar thinking applies to a mount point as well. Also, if+-- we resolve the symlinks in concatIterate, then each resolution will be+-- counted as depth level increment whereas if we resolve that at lower level+-- then it won't. We can do this by passing an option to modify the behavior.+--+-- When resolving cyclic directory symlinks one way to curtail it is ELOOP+-- which gives up if it encounters too many level. Another way is to use+-- the inode information to check if we are traversing an already traversed+-- inode, this is in general helpful in a graph traversal. We can ignore+-- ELOOP by passing an option but it may be inefficient because we may+-- encounter the loop from any node in the cycle.+--+-- If we encounter an error reading a directory because of permission+-- issues should we ignore it in this low level API or catch it in the+-- higher level traversal functionality? Similarly, if there are broken+-- symlinks, where to handle the error? Need to check performance when+-- handling it in ListDir. Suppressing the error at the lower level may be+-- more efficient than propagating it up and then handling it there. We can+-- do this by passing an option.+--+-- Returning the metadata:+--+-- Specific scans can be used to return the metadata in the output stream if+-- needed. However, we may need three different APIs:+-- one with fast metadata, and+-- another with full metadata. In the two cases the fold input would be+-- different.+--+-- * readMinimal: read only the path names, no metadata+-- * readStandard: read the path and minimal metadata+-- * readFull: read full metadata+--+-- NOTE: Full metadata can be read by mapping a stat call to a stream of paths+-- rather than via readdir API. Does it help the performance to do it in the+-- readdir API?++-- Design pattern:+--+-- By passing a scan we can process the output right at the source and produce+-- a cooked output. Otherwise we may have to produce a stream of intermediate+-- structures which may have more per item overhead and that overhead may not+-- get eliminated by fusion. For example, a fold can directly write the CString+-- from readdir to the output buffer whereas if we output the Path then we will+-- incur an overhead of intermediate structure.++module Streamly.Internal.FileSystem.DirIO+ (+ -- XXX Create a Metadata or Meta module for stat, access, getxattr, chmod,+ -- chown, utime, rename operations.+ --+ -- * Metadata+ -- getMetadata GetMetadata (followSymlinks, noAutoMount - see fstatat)++ -- * Configuration+ module Streamly.Internal.FileSystem.DirOptions++ -- * Streams+ , read++ -- Is there a benefit in providing a low level recursive read or+ -- concatIterate is good enough? Could be more efficient for non-concurrent+ -- reads by using a local loop. Or during concurrent reads use+ -- non-concurrent reads as we go deeper down in the tree.+ -- , readAttrsRecursive++ , readFiles+ , readDirs+ , readEither+ , readEitherPaths+ , readEitherChunks++ -- We can implement this in terms of readAttrsRecursive without losing+ -- perf.+ -- , readEitherRecursive -- Options: acyclic, follow symlinks+ -- , readAncestors -- read the parent chain using the .. entry.+ -- , readAncestorsAttrs++ -- * Unfolds+ -- | Use the more convenient stream APIs instead of unfolds where possible.+ , reader+ , fileReader+ , dirReader+ , eitherReader+ , eitherReaderPaths++ {-+ , toStreamWithBufferOf++ , readChunks+ , readChunksWithBufferOf++ , toChunksWithBufferOf+ , toChunks++ , write+ , writeWithBufferOf++ -- Byte stream write (Streams)+ , fromStream+ , fromStreamWithBufferOf++ -- -- * Array Write+ , writeArray+ , writeChunks+ , writeChunksWithBufferOf++ -- -- * Array stream Write+ , fromChunks+ , fromChunksWithBufferOf+ -}+ )+where++import Control.Monad.Catch (MonadCatch)+import Control.Monad.IO.Class (MonadIO(..))+import Data.Bifunctor (bimap)+import Data.Either (isRight, isLeft, fromLeft, fromRight)+import Streamly.Data.Stream (Stream)+import Streamly.Internal.Data.Unfold.Type (Unfold(..))+import Streamly.Internal.FileSystem.Path (Path)+#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+import qualified Streamly.Internal.Data.Fold as Fold+import Streamly.Internal.FileSystem.Windows.ReadDir (eitherReader, reader)+#else+import Streamly.Internal.FileSystem.Posix.ReadDir+ ( readEitherChunks, eitherReader, reader)+#endif+import qualified Streamly.Internal.Data.Stream as S+import qualified Streamly.Data.Unfold as UF+import qualified Streamly.Internal.FileSystem.Path as Path++import Streamly.Internal.FileSystem.DirOptions+import Prelude hiding (read)++{-+{-# INLINABLE readArrayUpto #-}+readArrayUpto :: Int -> Handle -> IO (Array Word8)+readArrayUpto size h = do+ ptr <- mallocPlainForeignPtrBytes size+ -- ptr <- mallocPlainForeignPtrAlignedBytes size (alignment (undefined :: Word8))+ withForeignPtr ptr $ \p -> do+ n <- hGetBufSome h p size+ let v = Array+ { aStart = ptr+ , arrEnd = p `plusPtr` n+ , arrBound = p `plusPtr` size+ }+ -- XXX shrink only if the diff is significant+ shrinkToFit v++-------------------------------------------------------------------------------+-- Stream of Arrays IO+-------------------------------------------------------------------------------++-- | @toChunksWithBufferOf size h@ reads a stream of arrays from file handle @h@.+-- The maximum size of a single array is specified by @size@. The actual size+-- read may be less than or equal to @size@.+{-# INLINE _toChunksWithBufferOf #-}+_toChunksWithBufferOf :: MonadIO m => Int -> Handle -> Stream m (Array Word8)+_toChunksWithBufferOf size h = go+ where+ -- XXX use cons/nil instead+ go = mkStream $ \_ yld _ stp -> do+ arr <- liftIO $ readArrayUpto size h+ if A.length arr == 0+ then stp+ else yld arr go++-- | @toChunksWithBufferOf size handle@ reads a stream of arrays from the file+-- handle @handle@. The maximum size of a single array is limited to @size@.+-- The actual size read may be less than or equal to @size@.+--+-- @since 0.7.0+{-# INLINE_NORMAL toChunksWithBufferOf #-}+toChunksWithBufferOf :: MonadIO m => Int -> Handle -> Stream m (Array Word8)+toChunksWithBufferOf size h = D.fromStreamD (D.Stream step ())+ where+ {-# INLINE_LATE step #-}+ step _ _ = do+ arr <- liftIO $ readArrayUpto size h+ return $+ case A.length arr of+ 0 -> D.Stop+ _ -> D.Yield arr ()++-- | Unfold the tuple @(bufsize, handle)@ into a stream of 'Word8' arrays.+-- Read requests to the IO device are performed using a buffer of size+-- @bufsize@. The size of an array in the resulting stream is always less than+-- or equal to @bufsize@.+--+-- @since 0.7.0+{-# INLINE_NORMAL readChunksWithBufferOf #-}+readChunksWithBufferOf :: MonadIO m => Unfold m (Int, Handle) (Array Word8)+readChunksWithBufferOf = Unfold step return+ where+ {-# INLINE_LATE step #-}+ step (size, h) = do+ arr <- liftIO $ readArrayUpto size h+ return $+ case A.length arr of+ 0 -> D.Stop+ _ -> D.Yield arr (size, h)++-- XXX read 'Array a' instead of Word8++-- | @toChunks handle@ reads a stream of arrays from the specified file+-- handle. The maximum size of a single array is limited to+-- @defaultChunkSize@. The actual size read may be less than or equal to+-- @defaultChunkSize@.+--+-- > toChunks = toChunksWithBufferOf defaultChunkSize+--+-- @since 0.7.0+{-# INLINE toChunks #-}+toChunks :: MonadIO m => Handle -> Stream m (Array Word8)+toChunks = toChunksWithBufferOf defaultChunkSize++-- | Unfolds a handle into a stream of 'Word8' arrays. Requests to the IO+-- device are performed using a buffer of size+-- 'Streamly.Internal.Data.Array.Type.defaultChunkSize'. The+-- size of arrays in the resulting stream are therefore less than or equal to+-- 'Streamly.Internal.Data.Array.Type.defaultChunkSize'.+--+-- @since 0.7.0+{-# INLINE readChunks #-}+readChunks :: MonadIO m => Unfold m Handle (Array Word8)+readChunks = UF.first readChunksWithBufferOf defaultChunkSize++-------------------------------------------------------------------------------+-- Read a Directory to Stream+-------------------------------------------------------------------------------++-- TODO for concurrent streams implement readahead IO. We can send multiple+-- read requests at the same time. For serial case we can use async IO. We can+-- also control the read throughput in mbps or IOPS.++-- | Unfolds the tuple @(bufsize, handle)@ into a byte stream, read requests+-- to the IO device are performed using buffers of @bufsize@.+--+-- @since 0.7.0+{-# INLINE readWithBufferOf #-}+readWithBufferOf :: MonadIO m => Unfold m (Int, Handle) Word8+readWithBufferOf = UF.many readChunksWithBufferOf A.read++-- | @toStreamWithBufferOf bufsize handle@ reads a byte stream from a file+-- handle, reads are performed in chunks of up to @bufsize@.+--+-- /Pre-release/+{-# INLINE toStreamWithBufferOf #-}+toStreamWithBufferOf :: MonadIO m => Int -> Handle -> Stream m Word8+toStreamWithBufferOf chunkSize h = AS.concat $ toChunksWithBufferOf chunkSize h+-}++-- read child node names from a dir filtering out . and ..+--+-- . and .. are an implementation artifact, and should probably not be used in+-- user level abstractions.+--+-- . does not seem to have any useful purpose. If we have the path of the dir+-- then we will resolve it to get the inode of the dir so the . entry would be+-- redundant. If we have the inode of the dir to read the dir then it is+-- redundant. Is this for cross check when doing fsck?+--+-- For .. we have the readAncestors API, we should not have this in the+-- readChildren API.++-- XXX exception handling++-- XXX We can use a more general mechanism to filter the contents of a+-- directory. We can just stat each child and pass on the stat information. We+-- can then use that info to do a general filtering. "find" like filters can be+-- created.++{-# INLINE eitherReaderPaths #-}+eitherReaderPaths ::(MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) ->+ Unfold m Path (Either Path Path)+eitherReaderPaths f =+ let (</>) = Path.join+ in fmap (\(dir, x) -> bimap (dir </>) (dir </>) x)+ $ UF.carry (eitherReader f)++--+-- | Read files only.+--+-- /Internal/+--+{-# INLINE fileReader #-}+fileReader :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) ->+ Unfold m Path Path+fileReader f = fmap (fromRight undefined) $ UF.filter isRight (eitherReader f)++-- | Read directories only. Filter out "." and ".." entries.+--+-- /Internal/+--+{-# INLINE dirReader #-}+dirReader :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) ->+ Unfold m Path Path+dirReader f = fmap (fromLeft undefined) $ UF.filter isLeft (eitherReader f)++-- | Raw read of a directory.+--+-- /Pre-release/+{-# INLINE read #-}+read :: (MonadIO m, MonadCatch m) =>+ Path -> Stream m Path+read = S.unfold reader++-- | Read directories as Left and files as Right. Filter out "." and ".."+-- entries. The output contains the names of the directories and files.+--+-- /Pre-release/+{-# INLINE readEither #-}+readEither :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) ->+ Path -> Stream m (Either Path Path)+readEither f = S.unfold (eitherReader f)++-- | Like 'readEither' but prefix the names of the files and directories with+-- the supplied directory path.+{-# INLINE readEitherPaths #-}+readEitherPaths :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) ->+ Path -> Stream m (Either Path Path)+readEitherPaths f dir =+ let (</>) = Path.join+ in fmap (bimap (dir </>) (dir </>)) $ readEither f dir++#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+-- XXX Implement a custom version of readEitherChunks (like for Posix) for+-- windows as well. Also implement readEitherByteChunks.+--+-- XXX For a fast custom implementation of traversal, the Right could be the+-- final array chunk including all files and dirs to be written to IO. The Left+-- could be list of dirs to be traversed.+--+-- This is a generic (but slower?) version of readEitherChunks using+-- eitherReaderPaths.+{-# INLINE readEitherChunks #-}+readEitherChunks :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) ->+ [Path] -> Stream m (Either [Path] [Path])+readEitherChunks f dirs =+ -- XXX Need to use a take to limit the group size. There will be separate+ -- limits for dir and files groups.+ S.groupsWhile grouper collector+ $ S.unfoldEach (eitherReaderPaths f)+ $ S.fromList dirs++ where++ -- XXX We can use a refold "Either dirs files" and yield the one that fills+ -- and pass the remainder to the next Refold.+ grouper first next =+ case first of+ Left _ -> isLeft next+ Right _ -> isRight next++ collector = Fold.foldl' step (Right [])++ step b x =+ case x of+ Left x1 ->+ case b of+ Right _ -> Left [x1] -- initial+ _ -> either (\xs -> Left (x1:xs)) Right b+ Right x1 -> fmap (x1:) b+#endif++-- | Read files only.+--+-- /Internal/+--+{-# INLINE readFiles #-}+readFiles :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) ->+ Path -> Stream m Path+readFiles f = S.unfold (fileReader f)++-- | Read directories only.+--+-- /Internal/+--+{-# INLINE readDirs #-}+readDirs :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) ->+ Path -> Stream m Path+readDirs f = S.unfold (dirReader f)++{-+-------------------------------------------------------------------------------+-- Writing+-------------------------------------------------------------------------------++-------------------------------------------------------------------------------+-- Array IO (output)+-------------------------------------------------------------------------------++-- | Write an 'Array' to a file handle.+--+-- @since 0.7.0+{-# INLINABLE writeArray #-}+writeArray :: Storable a => Handle -> Array a -> IO ()+writeArray _ arr | A.length arr == 0 = return ()+writeArray h Array{..} = withForeignPtr aStart $ \p -> hPutBuf h p aLen+ where+ aLen =+ let p = unsafeForeignPtrToPtr aStart+ in arrEnd `minusPtr` p++-------------------------------------------------------------------------------+-- Stream of Arrays IO+-------------------------------------------------------------------------------++-------------------------------------------------------------------------------+-- Writing+-------------------------------------------------------------------------------++-- | Write a stream of arrays to a handle.+--+-- @since 0.7.0+{-# INLINE fromChunks #-}+fromChunks :: (MonadIO m, Storable a)+ => Handle -> Stream m (Array a) -> m ()+fromChunks h m = S.mapM_ (liftIO . writeArray h) m++-- | @fromChunksWithBufferOf bufsize handle stream@ writes a stream of arrays+-- to @handle@ after coalescing the adjacent arrays in chunks of @bufsize@.+-- The chunk size is only a maximum and the actual writes could be smaller as+-- we do not split the arrays to fit exactly to the specified size.+--+-- @since 0.7.0+{-# INLINE fromChunksWithBufferOf #-}+fromChunksWithBufferOf :: (MonadIO m, Storable a)+ => Int -> Handle -> Stream m (Array a) -> m ()+fromChunksWithBufferOf n h xs = fromChunks h $ AS.compact n xs++-- | @fromStreamWithBufferOf bufsize handle stream@ writes @stream@ to @handle@+-- in chunks of @bufsize@. A write is performed to the IO device as soon as we+-- collect the required input size.+--+-- @since 0.7.0+{-# INLINE fromStreamWithBufferOf #-}+fromStreamWithBufferOf :: MonadIO m => Int -> Handle -> Stream m Word8 -> m ()+fromStreamWithBufferOf n h m = fromChunks h $ S.pinnedChunksOf n m+-- fromStreamWithBufferOf n h m = fromChunks h $ AS.chunksOf n m++-- > write = 'writeWithBufferOf' A.defaultChunkSize+--+-- | Write a byte stream to a file handle. Accumulates the input in chunks of+-- up to 'Streamly.Internal.Data.Array.Type.defaultChunkSize' before writing.+--+-- NOTE: This may perform better than the 'write' fold, you can try this if you+-- need some extra perf boost.+--+-- @since 0.7.0+{-# INLINE fromStream #-}+fromStream :: MonadIO m => Handle -> Stream m Word8 -> m ()+fromStream = fromStreamWithBufferOf defaultChunkSize++-- | Write a stream of arrays to a handle. Each array in the stream is written+-- to the device as a separate IO request.+--+-- @since 0.7.0+{-# INLINE writeChunks #-}+writeChunks :: (MonadIO m, Storable a) => Handle -> Fold m (Array a) ()+writeChunks h = FL.drainBy (liftIO . writeArray h)++-- | @writeChunksWithBufferOf bufsize handle@ writes a stream of arrays+-- to @handle@ 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 -> Handle -> Fold m (Array a) ()+writeChunksWithBufferOf n h = lpackArraysChunksOf n (writeChunks h)++-- GHC buffer size dEFAULT_FD_BUFFER_SIZE=8192 bytes.+--+-- XXX test this+-- Note that if you use a chunk size less than 8K (GHC's default buffer+-- size) then you are advised to use 'NOBuffering' mode on the 'Handle' in case you+-- do not want buffering to occur at GHC level as well. Same thing applies to+-- writes as well.++-- | @writeWithBufferOf reqSize handle@ writes the input stream to @handle@.+-- Bytes in the input stream are collected into a buffer until we have a chunk+-- of @reqSize@ and then written to the IO device.+--+-- @since 0.7.0+{-# INLINE writeWithBufferOf #-}+writeWithBufferOf :: MonadIO m => Int -> Handle -> Fold m Word8 ()+writeWithBufferOf n h = FL.groupsOf n (pinnedWriteNUnsafe n) (writeChunks h)++-- > write = 'writeWithBufferOf' A.defaultChunkSize+--+-- | Write a byte stream to a file handle. Accumulates the input in chunks of+-- up to 'Streamly.Internal.Data.Array.Type.defaultChunkSize' before writing+-- to the IO device.+--+-- @since 0.7.0+{-# INLINE write #-}+write :: MonadIO m => Handle -> Fold m Word8 ()+write = writeWithBufferOf defaultChunkSize+-}
+ src/Streamly/Internal/FileSystem/DirOptions.hs view
@@ -0,0 +1,125 @@+-- |+-- Module : Streamly.Internal.FileSystem.DirOptions+-- Copyright : (c) 2024 Composewell Technologies+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Portability : GHC++module Streamly.Internal.FileSystem.DirOptions+ (+ ReadOptions (..)+ , followSymlinks+ , ignoreMissing+ , ignoreSymlinkLoops+ , ignoreInaccessible+ , defaultReadOptions+ )+where++-- NOTE: If we are following symlinks, then we want to determine the type of+-- the link destination not the link itself, so we need to use stat instead of+-- lstat for resolving the symlink.+--+-- For recursive traversal, instead of classifying the dirents using stat, we+-- can leave them unclassified, and deal with ENOTDIR when doing an opendir. We+-- can just ignore that error if it is not a dir. This way we do not need to do+-- stat at all. Or we can basically say don't try to determine the type of+-- symlinks and always try to read symlinks as dirs. We can have an option for+-- classifying symlinks or DT_UNKNOWN as potential dirs.++-- When resolving a symlink we may encounter errors only if the directory entry+-- is a symlink. If the directory entry is not a symlink then stat on it will+-- have permissions, it will not give ELOOP or ENOENT unless the file was+-- deleted or recreated after we read the dirent.++-- | Options controlling the behavior of directory read.+data ReadOptions =+ ReadOptions+ { _followSymlinks :: Bool+ , _ignoreELOOP :: Bool+ , _ignoreENOENT :: Bool+ , _ignoreEACCESS :: Bool+ }++-- | Control how symbolic links are handled when determining the type+-- of a directory entry.+--+-- * If set to 'True', symbolic links are resolved before classification.+-- This means a symlink pointing to a directory will be treated as a+-- directory, and a symlink pointing to a file will be treated as a+-- non-directory.+--+-- * If set to 'False', all symbolic links are classified as non-directories,+-- without attempting to resolve their targets.+--+-- Enabling resolution may cause additional errors to occur due to+-- insufficient permissions, broken links, or symlink loops. Such errors+-- can be ignored or handled using the appropriate options.+--+-- The default is 'False'.+--+-- On Windows this option has no effect as of now, symlinks are not followed to+-- determine the type.+followSymlinks :: Bool -> ReadOptions -> ReadOptions+followSymlinks x opts = opts {_followSymlinks = x}++-- | When the 'followSymlinks' option is enabled and a directory entry is a+-- symbolic link, we resolve it to determine the type of the symlink target.+-- This option controls the behavior when encountering symlink loop errors+-- during resolution.+--+-- When set to 'True', symlink loop errors are ignored, and the type of the+-- entry is reported as not a directory. When set to 'False', the directory+-- read operation fails with an error.+--+-- The default is 'True'.+--+-- On Windows this option has no effect as of now, symlinks are not followed to+-- determine the type.+ignoreSymlinkLoops :: Bool -> ReadOptions -> ReadOptions+ignoreSymlinkLoops x opts = opts {_ignoreELOOP = x}++-- | When the 'followSymlinks' option is enabled and a directory entry is a+-- symbolic link, we resolve it to determine the type of the symlink target.+-- This option controls the behavior when encountering broken symlink errors+-- during resolution.+--+-- When set to 'True', broken symlink errors are ignored, and the type of the+-- entry is reported as not a directory. When set to 'False', the directory+-- read operation fails with an error.+--+-- The default is 'True'.+--+-- On Windows this option has no effect as of now, symlinks are not followed to+-- determine the type.+ignoreMissing :: Bool -> ReadOptions -> ReadOptions+ignoreMissing x opts = opts {_ignoreENOENT = x}++-- | When the 'followSymlinks' option is enabled and a directory entry is a+-- symbolic link, we resolve it to determine the type of the symlink target.+-- This option controls the behavior when encountering permission errors+-- during resolution.+--+-- When set to 'True', any permission errors are ignored, and the type of the+-- entry is reported as not a directory. When set to 'False', the directory+-- read operation fails with an error.+--+-- The default is 'True'.+--+-- On Windows this option has no effect as of now, symlinks are not followed to+-- determine the type.+ignoreInaccessible :: Bool -> ReadOptions -> ReadOptions+ignoreInaccessible x opts = opts {_ignoreEACCESS = x}++-- XXX find ignores errors when following symlinks, by default.+-- NOTE: The defaultReadOptions emulates the behaviour of "find".+--+defaultReadOptions :: ReadOptions+defaultReadOptions =+ ReadOptions+ { _followSymlinks = False+ , _ignoreELOOP = True+ , _ignoreENOENT = True+ , _ignoreEACCESS = True+ }
src/Streamly/Internal/FileSystem/File.hs view
@@ -6,7 +6,6 @@ -- -- License : BSD3 -- Maintainer : streamly@composewell.com--- Stability : pre-release -- Portability : GHC -- -- Read and write streams and arrays to and from files specified by their paths@@ -21,6 +20,7 @@ -- module Streamly.Internal.FileSystem.File+{-# DEPRECATED "Please use \"Streamly.Internal.FileSystem.FileIO\" instead." #-} ( -- * Streaming IO -- | Stream data to or from a file or device sequentially. When reading,@@ -76,11 +76,11 @@ , fromChunks -- ** Append To File- , append- , appendWith+ , writeAppend+ , writeAppendWith -- , appendShared- , appendArray- , appendChunks+ , writeAppendArray+ , writeAppendChunks -- * Deprecated , readWithBufferOf@@ -96,25 +96,27 @@ import Control.Monad.Catch (MonadCatch) import Control.Monad.IO.Class (MonadIO(..))+import Data.Kind (Type) import Data.Word (Word8)-import System.IO (Handle, openFile, IOMode(..), hClose)+import System.IO+ (Handle, IOMode(..), openFile, hClose, hSetBuffering, BufferMode(..)) import Prelude hiding (read) import qualified Control.Monad.Catch as MC import qualified System.IO as SIO import Streamly.Data.Fold (groupsOf, drain)-import Streamly.Internal.Data.Array.Type (Array(..), writeNUnsafe)+import Streamly.Internal.Data.Array.Type (Array(..)) import Streamly.Internal.Data.Fold.Type (Fold(..)) import Streamly.Data.Stream (Stream)-import Streamly.Internal.Data.Unboxed (Unbox) import Streamly.Internal.Data.Unfold.Type (Unfold(..)) -- import Streamly.String (encodeUtf8, decodeUtf8, foldLines) import Streamly.Internal.System.IO (defaultChunkSize) -import qualified Streamly.Data.Array as A+import qualified Streamly.Internal.Data.Array as A import qualified Streamly.Data.Stream as S import qualified Streamly.Data.Unfold as UF+import qualified Streamly.Internal.Data.Array.Type as IA (chunksOf') import qualified Streamly.Internal.Data.Unfold as UF (bracketIO) import qualified Streamly.Internal.Data.Fold.Type as FL (Step(..), snoc, reduce)@@ -149,8 +151,15 @@ {-# INLINE withFile #-} withFile :: (MonadIO m, MonadCatch m) => FilePath -> IOMode -> (Handle -> Stream m a) -> Stream m a-withFile file mode = S.bracketIO (openFile file mode) hClose+withFile file mode = S.bracketIO open hClose + where++ open = do+ h <- openFile file mode+ hSetBuffering h NoBuffering+ return h+ -- | Transform an 'Unfold' from a 'Handle' to an unfold from a 'FilePath'. The -- resulting unfold opens a handle in 'ReadMode', uses it using the supplied -- unfold and then makes sure that the handle is closed on normal termination@@ -162,8 +171,16 @@ {-# INLINE usingFile #-} usingFile :: (MonadIO m, MonadCatch m) => Unfold m Handle a -> Unfold m FilePath a-usingFile = UF.bracketIO (`openFile` ReadMode) hClose+usingFile = UF.bracketIO open hClose + where++ open file = do+ h <- openFile file ReadMode+ hSetBuffering h NoBuffering+ return h++ {-# INLINE usingFile2 #-} usingFile2 :: (MonadIO m, MonadCatch m) => Unfold m (x, Handle) a -> Unfold m (x, FilePath) a@@ -173,6 +190,7 @@ before (x, file) = do h <- openFile file ReadMode+ hSetBuffering h NoBuffering return (x, h) after (_, h) = hClose h@@ -186,6 +204,7 @@ before (x, y, z, file) = do h <- openFile file ReadMode+ hSetBuffering h NoBuffering return (x, y, z, h) after (_, _, _, h) = hClose h@@ -205,16 +224,16 @@ -- /Pre-release/ -- {-# INLINABLE putChunk #-}-putChunk :: FilePath -> Array a -> IO ()+putChunk :: forall (a :: Type). FilePath -> Array a -> IO () putChunk file arr = SIO.withFile file WriteMode (`FH.putChunk` arr) -- | append an array to a file. -- -- /Pre-release/ ---{-# INLINABLE appendArray #-}-appendArray :: FilePath -> Array a -> IO ()-appendArray file arr = SIO.withFile file AppendMode (`FH.putChunk` arr)+{-# INLINABLE writeAppendArray #-}+writeAppendArray :: forall (a :: Type). FilePath -> Array a -> IO ()+writeAppendArray file arr = SIO.withFile file AppendMode (`FH.putChunk` arr) ------------------------------------------------------------------------------- -- Stream of Arrays IO@@ -337,11 +356,7 @@ -- /Pre-release/ {-# INLINE reader #-} reader :: (MonadIO m, MonadCatch m) => Unfold m FilePath Word8-reader = UF.many A.reader (usingFile FH.chunkReader)--{-# INLINE concatChunks #-}-concatChunks :: (Monad m, Unbox a) => Stream m (Array a) -> Stream m a-concatChunks = S.unfoldMany A.reader+reader = UF.unfoldEach A.reader (usingFile FH.chunkReader) -- | Generate a stream of bytes from a file specified by path. The stream ends -- when EOF is encountered. File is locked using multiple reader and single@@ -351,7 +366,7 @@ -- {-# INLINE read #-} read :: (MonadIO m, MonadCatch m) => FilePath -> Stream m Word8-read file = concatChunks $ withFile file ReadMode FH.readChunks+read file = A.concat $ withFile file ReadMode FH.readChunks {-# DEPRECATED toBytes "Please use 'read' instead" #-} {-# INLINE toBytes #-}@@ -374,7 +389,7 @@ ------------------------------------------------------------------------------- {-# INLINE fromChunksMode #-}-fromChunksMode :: (MonadIO m, MonadCatch m)+fromChunksMode :: forall m (a :: Type). (MonadIO m, MonadCatch m) => IOMode -> FilePath -> Stream m (Array a) -> m () fromChunksMode mode file xs = S.fold drain $ withFile file mode (\h -> S.mapM (FH.putChunk h) xs)@@ -384,7 +399,7 @@ -- /Pre-release/ -- {-# INLINE fromChunks #-}-fromChunks :: (MonadIO m, MonadCatch m)+fromChunks :: forall m (a :: Type). (MonadIO m, MonadCatch m) => FilePath -> Stream m (Array a) -> m () fromChunks = fromChunksMode WriteMode @@ -405,7 +420,7 @@ {-# INLINE fromBytesWith #-} fromBytesWith :: (MonadIO m, MonadCatch m) => Int -> FilePath -> Stream m Word8 -> m ()-fromBytesWith n file xs = fromChunks file $ S.chunksOf n xs+fromBytesWith n file xs = fromChunks file $ IA.chunksOf' n xs {-# DEPRECATED fromBytesWithBufferOf "Please use 'fromBytesWith' instead" #-} {-# INLINE fromBytesWithBufferOf #-}@@ -436,24 +451,28 @@ -- -- /Pre-release/ {-# INLINE writeChunks #-}-writeChunks :: (MonadIO m, MonadCatch m)+writeChunks :: forall m (a :: Type). (MonadIO m, MonadCatch m) => FilePath -> Fold m (Array a) ()-writeChunks path = Fold step initial extract+writeChunks path = Fold step initial extract final where initial = do h <- liftIO (openFile path WriteMode)+ liftIO $ hSetBuffering h NoBuffering fld <- FL.reduce (FH.writeChunks h) `MC.onException` liftIO (hClose h) return $ FL.Partial (fld, h) step (fld, h) x = do r <- FL.snoc fld x `MC.onException` liftIO (hClose h) return $ FL.Partial (r, h)- extract (Fold _ initial1 extract1, h) = do++ extract _ = return ()++ final (Fold _ initial1 _ final1, h) = do liftIO $ hClose h res <- initial1 case res of- FL.Partial fs -> extract1 fs- FL.Done fb -> return fb+ FL.Partial fs -> final1 fs+ FL.Done () -> return () -- | @writeWith chunkSize handle@ writes the input stream to @handle@. -- Bytes in the input stream are collected into a buffer until we have a chunk@@ -464,7 +483,7 @@ writeWith :: (MonadIO m, MonadCatch m) => Int -> FilePath -> Fold m Word8 () writeWith n path =- groupsOf n (writeNUnsafe n) (writeChunks path)+ groupsOf n (A.unsafeCreateOf' n) (writeChunks path) {-# DEPRECATED writeWithBufferOf "Please use 'writeWith' instead" #-} {-# INLINE writeWithBufferOf #-}@@ -488,10 +507,10 @@ -- -- /Pre-release/ ---{-# INLINE appendChunks #-}-appendChunks :: (MonadIO m, MonadCatch m)+{-# INLINE writeAppendChunks #-}+writeAppendChunks :: forall m (a :: Type). (MonadIO m, MonadCatch m) => FilePath -> Stream m (Array a) -> m ()-appendChunks = fromChunksMode AppendMode+writeAppendChunks = fromChunksMode AppendMode -- | Like 'append' but provides control over the write buffer. Output will -- be written to the IO device as soon as we collect the specified number of@@ -499,10 +518,11 @@ -- -- /Pre-release/ ---{-# INLINE appendWith #-}-appendWith :: (MonadIO m, MonadCatch m)+{-# INLINE writeAppendWith #-}+writeAppendWith :: (MonadIO m, MonadCatch m) => Int -> FilePath -> Stream m Word8 -> m ()-appendWith n file xs = appendChunks file $ S.chunksOf n xs+writeAppendWith n file xs =+ writeAppendChunks file $ IA.chunksOf' n xs -- | Append a byte stream to a file. Combines the bytes in chunks of size up to -- 'A.defaultChunkSize' before writing. If the file exists then the new data@@ -511,9 +531,9 @@ -- -- /Pre-release/ ---{-# INLINE append #-}-append :: (MonadIO m, MonadCatch m) => FilePath -> Stream m Word8 -> m ()-append = appendWith defaultChunkSize+{-# INLINE writeAppend #-}+writeAppend :: (MonadIO m, MonadCatch m) => FilePath -> Stream m Word8 -> m ()+writeAppend = writeAppendWith defaultChunkSize {- -- | Like 'append' but the file is not locked for exclusive writes.
+ src/Streamly/Internal/FileSystem/File/Common.hs view
@@ -0,0 +1,97 @@+module Streamly.Internal.FileSystem.File.Common+ ( withFile+ , openFile+ ) where++-------------------------------------------------------------------------------+-- Imports+-------------------------------------------------------------------------------++import Control.Exception (mask, onException, try)+import Control.Monad (when)+import GHC.IO (catchException)+import GHC.IO.Exception (IOException(..))+import GHC.IO.Handle.Internals (handleFinalizer)+import Streamly.Internal.FileSystem.Path (Path)+import System.IO (IOMode(..), Handle, hSetBinaryMode, hClose)++import qualified Streamly.Internal.FileSystem.Path as Path++#if MIN_VERSION_base(4,16,0)+import GHC.IO.Handle.Internals (addHandleFinalizer)+#else+import Control.Concurrent.MVar (MVar, addMVarFinalizer)+import GHC.IO.Handle.Types (Handle__, Handle(..))+#endif++-------------------------------------------------------------------------------+-- Utils+-------------------------------------------------------------------------------++#if !(MIN_VERSION_base(4,16,0))+type HandleFinalizer = FilePath -> MVar Handle__ -> IO ()++-- | Add a finalizer to a 'Handle'. Specifically, the finalizer+-- will be added to the 'MVar' of a file handle or the write-side+-- 'MVar' of a duplex handle. See Handle Finalizers for details.+addHandleFinalizer :: Handle -> HandleFinalizer -> IO ()+addHandleFinalizer handle finalizer = do+ addMVarFinalizer mv (finalizer filepath mv)+ where+ !(filepath, !mv) = case handle of+ FileHandle fp m -> (fp, m)+ DuplexHandle fp _ write_m -> (fp, write_m)+#endif++{-# INLINE withOpenFile #-}+withOpenFile+ :: Bool+ -> Bool+ -> (Path -> IOMode -> IO Handle)+ -> Path+ -> IOMode+ -> (Handle -> IO r)+ -> IO r+withOpenFile binary close_finally f fp iomode action =+ mask $ \restore -> do+ h <- f fp iomode+ -- XXX In case of withFile it will be closed anyway, so do we even need+ -- this?+ addHandleFinalizer h handleFinalizer+ when binary $ hSetBinaryMode h True+ r <- restore (action h) `onException` hClose h+ when close_finally $ hClose h+ pure r++addFilePathToIOError :: String -> Path -> IOException -> IOException+addFilePathToIOError fun fp ioe =+ let !str = Path.toString fp+ in ioe+ { ioe_location = fun+ , ioe_filename = Just str+ }++{-# INLINE catchWith #-}+catchWith :: String -> Path -> IO a -> IO a+catchWith str path io =+ catchException io (ioError . addFilePathToIOError str path)++{-# INLINE withFile #-}+withFile ::+ Bool+ -> (Path -> IOMode -> IO Handle)+ -> Path+ -> IOMode+ -> (Handle -> IO r)+ -> IO r+withFile binary f path iomode act =+ catchWith "withFile" path+ (withOpenFile binary True f path iomode (try . act))+ >>= either ioError pure++{-# INLINE openFile #-}+openFile ::+ Bool -> (Path -> IOMode -> IO Handle) -> Path -> IOMode -> IO Handle+openFile binary f path iomode =+ catchWith "openFile" path+ $ withOpenFile binary False f path iomode pure
+ src/Streamly/Internal/FileSystem/FileIO.hs view
@@ -0,0 +1,643 @@+-- |+-- Module : Streamly.Internal.FileSystem.FileIO+-- Copyright : (c) 2019 Composewell Technologies+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--++module Streamly.Internal.FileSystem.FileIO+ (+ -- * Streaming IO+ -- | Stream data to or from a file or device sequentially. When reading,+ -- the stream is lazy and generated on-demand as the consumer consumes it.+ -- Read IO requests to the IO device are performed in chunks limited to a+ -- maximum size of 32KiB, this is referred to as @defaultChunkSize@ in the+ -- documentation. One IO request may or may not read the full+ -- chunk. If the whole stream is not consumed, it is possible that we may+ -- read slightly more from the IO device than what the consumer needed.+ -- Unless specified otherwise in the API, writes are collected into chunks+ -- of @defaultChunkSize@ before they are written to the IO device.++ -- Streaming APIs work for all kind of devices, seekable or non-seekable;+ -- including disks, files, memory devices, terminals, pipes, sockets and+ -- fifos. While random access APIs work only for files or devices that have+ -- random access or seek capability for example disks, memory devices.+ -- Devices like terminals, pipes, sockets and fifos do not have random+ -- access capability.++ -- ** File IO Using Handle+ withFile++ -- ** Streams+ , read+ , readChunksWith+ , readChunks++ -- ** Unfolds+ , readerWith+ , reader+ -- , readShared+ -- , readUtf8+ -- , readLines+ -- , readFrames+ , chunkReaderWith+ , chunkReaderFromToWith+ , chunkReader++ -- ** Write To File+ , putChunk -- writeChunk?++ -- ** Folds+ , write+ -- , writeUtf8+ -- , writeUtf8ByLines+ -- , writeByFrames+ , writeWith+ , writeChunks++ -- ** Writing Streams+ , fromBytes -- XXX putBytes?+ , fromBytesWith -- putBytesWith+ , fromChunks -- putChunks?++ -- ** Append To File+ , writeAppend+ , writeAppendWith+ -- , appendShared+ , writeAppendArray+ , writeAppendChunks+ )+where++import Control.Monad.Catch (MonadCatch)+import Control.Monad.IO.Class (MonadIO(..))+import Data.Word (Word8)+import System.IO (Handle, IOMode(..), hClose, hSetBuffering, BufferMode(..))+import Prelude hiding (read)++import qualified Control.Monad.Catch as MC++import Streamly.Data.Fold (groupsOf, drain)+import Streamly.Internal.Data.Array.Type (Array(..))+import Streamly.Internal.Data.Fold.Type (Fold(..))+import Streamly.Data.Stream (Stream)+import Streamly.Internal.Data.Unfold.Type (Unfold(..))+-- import Streamly.String (encodeUtf8, decodeUtf8, foldLines)+import Streamly.Internal.System.IO (defaultChunkSize)+import Streamly.Internal.FileSystem.Path (Path)++import qualified Streamly.Internal.Data.Array as A+import qualified Streamly.Data.Stream as S+import qualified Streamly.Data.Unfold as UF+import qualified Streamly.Internal.Data.Unfold as UF (bracketIO)+import qualified Streamly.Internal.Data.Fold.Type as FL+ (Step(..), snoc, reduce)+import qualified Streamly.Internal.FileSystem.Handle as FH+#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)+import qualified Streamly.Internal.FileSystem.Posix.File as File+#else+import qualified Streamly.Internal.FileSystem.Windows.File as File+#endif++#include "inline.hs"++-------------------------------------------------------------------------------+-- References+-------------------------------------------------------------------------------+--+-- The following references may be useful to build an understanding about the+-- file API design:+--+-- http://www.linux-mag.com/id/308/ for blocking/non-blocking IO on linux.+-- https://lwn.net/Articles/612483/ Non-blocking buffered file read operations+-- https://en.wikipedia.org/wiki/C_file_input/output for C APIs.+-- https://docs.oracle.com/javase/tutorial/essential/io/file.html for Java API.+-- https://www.w3.org/TR/FileAPI/ for http file API.++-------------------------------------------------------------------------------+-- Safe file reading+-------------------------------------------------------------------------------++-- | @'withFile' name mode act@ opens a file and passes the resulting handle to+-- the computation @act@. The handle is closed on exit from 'withFile', whether+-- by normal termination or by raising an exception. If closing the handle+-- raises an exception, then that exception is raised by 'withFile' rather than+-- any exception raised by 'act'.+--+-- The file is opened in binary mode as encoding, decoding, and newline+-- translation can be handled explicitly by the streaming APIs.+--+-- The file is opened without buffering as buffering can be controlled+-- explicitly by the streaming APIs.+--+-- /Pre-release/+--+{-# INLINE withFile #-}+withFile :: (MonadIO m, MonadCatch m)+ => Path -> IOMode -> (Handle -> Stream m a) -> Stream m a+withFile file mode = S.bracketIO open hClose++ where++ open = do+ h <- File.openBinaryFile file mode+ hSetBuffering h NoBuffering+ return h++-- | Transform an 'Unfold' that takes 'Handle' as input to an unfold that takes+-- a 'Path' as input. The resulting unfold opens the file in 'ReadMode',+-- passes it to the supplied unfold and then makes sure that the handle is+-- closed on normal termination or in case of an exception. If closing the+-- handle raises an exception, then this exception will be raised by+-- 'usingFile'.+--+-- The file is opened in binary mode as encoding, decoding, and newline+-- translation can be handled explicitly by the streaming APIs.+--+-- The file is opened without buffering as buffering can be controlled+-- explicitly by the streaming APIs.+--+-- /Pre-release/+--+{-# INLINE usingFile #-}+usingFile :: (MonadIO m, MonadCatch m)+ => Unfold m Handle a -> Unfold m Path a+usingFile = UF.bracketIO open hClose++ where++ open file = do+ h <- File.openBinaryFile file ReadMode+ hSetBuffering h NoBuffering+ return h++{-# INLINE usingFile2 #-}+usingFile2 :: (MonadIO m, MonadCatch m)+ => Unfold m (x, Handle) a -> Unfold m (x, Path) a+usingFile2 = UF.bracketIO before after++ where++ before (x, file) = do+ h <- File.openBinaryFile file ReadMode+ hSetBuffering h NoBuffering+ return (x, h)++ after (_, h) = hClose h++{-# INLINE usingFile3 #-}+usingFile3 :: (MonadIO m, MonadCatch m)+ => Unfold m (x, y, z, Handle) a -> Unfold m (x, y, z, Path) a+usingFile3 = UF.bracketIO before after++ where++ before (x, y, z, file) = do+ h <- File.openBinaryFile file ReadMode+ hSetBuffering h NoBuffering+ return (x, y, z, h)++ after (_, _, _, h) = hClose h++-------------------------------------------------------------------------------+-- Array IO (Input)+-------------------------------------------------------------------------------++-- TODO readArrayOf++-------------------------------------------------------------------------------+-- Array IO (output)+-------------------------------------------------------------------------------++-- | Write an array to a file. Overwrites the file if it exists.+--+-- /Pre-release/+--+{-# INLINABLE putChunk #-}+putChunk :: Path -> Array a -> IO ()+putChunk file arr = File.withFile file WriteMode (`FH.putChunk` arr)++-- | Append an array to a file.+--+-- /Pre-release/+--+{-# INLINABLE writeAppendArray #-}+writeAppendArray :: Path -> Array a -> IO ()+writeAppendArray file arr = File.withFile file AppendMode (`FH.putChunk` arr)++-------------------------------------------------------------------------------+-- Stream of Arrays IO+-------------------------------------------------------------------------------++-- | @readChunksWith size file@ reads a stream of arrays from file @file@.+-- The maximum size of a single array is specified by @size@. The actual size+-- read may be less than or equal to @size@.+--+-- /Pre-release/+--+{-# INLINE readChunksWith #-}+readChunksWith :: (MonadIO m, MonadCatch m)+ => Int -> Path -> Stream m (Array Word8)+readChunksWith size file =+ withFile file ReadMode (FH.readChunksWith size)++-- XXX read 'Array a' instead of Word8+--+-- | @readChunks file@ reads a stream of arrays from file @file@.+-- The maximum size of a single array is limited to @defaultChunkSize@. The+-- actual size read may be less than @defaultChunkSize@.+--+-- > readChunks = readChunksWith defaultChunkSize+--+-- /Pre-release/+--+{-# INLINE readChunks #-}+readChunks :: (MonadIO m, MonadCatch m)+ => Path -> Stream m (Array Word8)+readChunks = readChunksWith defaultChunkSize++-------------------------------------------------------------------------------+-- Read File to Stream+-------------------------------------------------------------------------------++-- TODO for concurrent streams implement readahead IO. We can send multiple+-- read requests at the same time. For serial case we can use async IO. We can+-- also control the read throughput in mbps or IOPS.++-- | Unfold the tuple @(bufsize, filepath)@ into a stream of 'Word8' arrays.+-- Read requests to the IO device are performed using a buffer of size+-- @bufsize@. The size of an array in the resulting stream is always less than+-- or equal to @bufsize@.+--+-- /Pre-release/+--+{-# INLINE chunkReaderWith #-}+chunkReaderWith :: (MonadIO m, MonadCatch m)+ => Unfold m (Int, Path) (Array Word8)+chunkReaderWith = usingFile2 FH.chunkReaderWith++-- | Unfold the tuple @(from, to, bufsize, filepath)@ into a stream+-- of 'Word8' arrays.+-- Read requests to the IO device are performed using a buffer of size+-- @bufsize@ starting from absolute offset of @from@ till the absolute+-- position of @to@. The size of an array in the resulting stream is always+-- less than or equal to @bufsize@.+--+-- /Pre-release/+{-# INLINE chunkReaderFromToWith #-}+chunkReaderFromToWith :: (MonadIO m, MonadCatch m) =>+ Unfold m (Int, Int, Int, Path) (Array Word8)+chunkReaderFromToWith = usingFile3 FH.chunkReaderFromToWith++-- | Unfolds a 'Path' into a stream of 'Word8' arrays. Requests to the IO+-- device are performed using a buffer of size+-- 'Streamly.Internal.Data.Array.Type.defaultChunkSize'. The+-- size of arrays in the resulting stream are therefore less than or equal to+-- 'Streamly.Internal.Data.Array.Type.defaultChunkSize'.+--+-- /Pre-release/+{-# INLINE chunkReader #-}+chunkReader :: (MonadIO m, MonadCatch m) => Unfold m Path (Array Word8)+chunkReader = usingFile FH.chunkReader++-- | Unfolds the tuple @(bufsize, filepath)@ into a byte stream, read requests+-- to the IO device are performed using buffers of @bufsize@.+--+-- /Pre-release/+{-# INLINE readerWith #-}+readerWith :: (MonadIO m, MonadCatch m) => Unfold m (Int, Path) Word8+readerWith = usingFile2 FH.readerWith++-- | Unfolds a file path into a byte stream. IO requests to the device are+-- performed in sizes of+-- 'Streamly.Internal.Data.Array.Type.defaultChunkSize'.+--+-- /Pre-release/+{-# INLINE reader #-}+reader :: (MonadIO m, MonadCatch m) => Unfold m Path Word8+reader = UF.unfoldEach A.reader (usingFile FH.chunkReader)++-- | Generate a stream of bytes from a file specified by path. The stream ends+-- when EOF is encountered. File is locked using multiple reader and single+-- writer locking mode.+--+-- /Pre-release/+--+{-# INLINE read #-}+read :: (MonadIO m, MonadCatch m) => Path -> Stream m Word8+read file = A.concat $ withFile file ReadMode FH.readChunks++{-+-- | Generate a stream of elements of the given type from a file 'Handle'. The+-- stream ends when EOF is encountered. File is not locked for exclusive reads,+-- writers can keep writing to the file.+--+-- @since 0.7.0+{-# INLINE readShared #-}+readShared :: MonadIO m => Handle -> Stream m Word8+readShared = undefined+-}++-------------------------------------------------------------------------------+-- Writing+-------------------------------------------------------------------------------++{-# INLINE fromChunksMode #-}+fromChunksMode :: (MonadIO m, MonadCatch m)+ => IOMode -> Path -> Stream m (Array a) -> m ()+fromChunksMode mode file xs = S.fold drain $+ withFile file mode (\h -> S.mapM (FH.putChunk h) xs)++-- | Write a stream of arrays to a file. Overwrites the file if it exists.+--+-- /Pre-release/+--+{-# INLINE fromChunks #-}+fromChunks :: (MonadIO m, MonadCatch m)+ => Path -> Stream m (Array a) -> m ()+fromChunks = fromChunksMode WriteMode++-- GHC buffer size dEFAULT_FD_BUFFER_SIZE=8192 bytes.+--+-- XXX test this+-- Note that if you use a chunk size less than 8K (GHC's default buffer+-- size) then you are advised to use 'NOBuffering' mode on the 'Handle' in case you+-- do not want buffering to occur at GHC level as well. Same thing applies to+-- writes as well.++-- | Like 'write' but provides control over the write buffer. Output will+-- be written to the IO device as soon as we collect the specified number of+-- input elements.+--+-- /Pre-release/+--+{-# INLINE fromBytesWith #-}+fromBytesWith :: (MonadIO m, MonadCatch m)+ => Int -> Path -> Stream m Word8 -> m ()+fromBytesWith n file xs = fromChunks file $ A.chunksOf' n xs++-- > write = 'writeWith' defaultChunkSize+--+-- | Write a byte stream to a file. Combines the bytes in chunks of size+-- up to 'A.defaultChunkSize' before writing. If the file exists it is+-- truncated to zero size before writing. If the file does not exist it is+-- created. File is locked using single writer locking mode.+--+-- /Pre-release/+{-# INLINE fromBytes #-}+fromBytes :: (MonadIO m, MonadCatch m) => Path -> Stream m Word8 -> m ()+fromBytes = fromBytesWith defaultChunkSize++{-+{-# INLINE write #-}+write :: (MonadIO m, Storable a) => Handle -> Stream m a -> m ()+write = toHandleWith A.defaultChunkSize+-}++-- | Write a stream of chunks to a file. Each chunk in the stream is written+-- immediately to the device as a separate IO request, without coalescing or+-- buffering.+--+{-# INLINE writeChunks #-}+writeChunks :: (MonadIO m, MonadCatch m)+ => Path -> Fold m (Array a) ()+writeChunks path = Fold step initial extract final+ where+ initial = do+ h <- liftIO (File.openBinaryFile path WriteMode)+ liftIO $ hSetBuffering h NoBuffering+ fld <- FL.reduce (FH.writeChunks h)+ `MC.onException` liftIO (hClose h)+ return $ FL.Partial (fld, h)+ step (fld, h) x = do+ r <- FL.snoc fld x `MC.onException` liftIO (hClose h)+ return $ FL.Partial (r, h)++ extract _ = return ()++ final (Fold _ initial1 _ final1, h) = do+ liftIO $ hClose h+ res <- initial1+ case res of+ FL.Partial fs -> final1 fs+ FL.Done () -> return ()++-- | @writeWith chunkSize handle@ writes the input stream to @handle@.+-- Bytes in the input stream are collected into a buffer until we have a chunk+-- of size @chunkSize@ and then written to the IO device.+--+-- /Pre-release/+{-# INLINE writeWith #-}+writeWith :: (MonadIO m, MonadCatch m)+ => Int -> Path -> Fold m Word8 ()+writeWith n path =+ groupsOf n (A.unsafeCreateOf' n) (writeChunks path)++-- > write = 'writeWith' A.defaultChunkSize+--+-- | Write a byte stream to a file. Accumulates the input in chunks of up to+-- 'Streamly.Internal.Data.Array.Type.defaultChunkSize' before writing to+-- the IO device.+--+-- /Pre-release/+--+{-# INLINE write #-}+write :: (MonadIO m, MonadCatch m) => Path -> Fold m Word8 ()+write = writeWith defaultChunkSize++-- | Append a stream of arrays to a file.+--+-- /Pre-release/+--+{-# INLINE writeAppendChunks #-}+writeAppendChunks :: (MonadIO m, MonadCatch m)+ => Path -> Stream m (Array a) -> m ()+writeAppendChunks = fromChunksMode AppendMode++-- | Like 'append' but provides control over the write buffer. Output will+-- be written to the IO device as soon as we collect the specified number of+-- input elements.+--+-- /Pre-release/+--+{-# INLINE writeAppendWith #-}+writeAppendWith :: (MonadIO m, MonadCatch m)+ => Int -> Path -> Stream m Word8 -> m ()+writeAppendWith n file xs =+ writeAppendChunks file $ A.chunksOf' n xs++-- | Append a byte stream to a file. Combines the bytes in chunks of size up to+-- 'A.defaultChunkSize' before writing. If the file exists then the new data+-- is appended to the file. If the file does not exist it is created. File is+-- locked using single writer locking mode.+--+-- /Pre-release/+--+{-# INLINE writeAppend #-}+writeAppend :: (MonadIO m, MonadCatch m) => Path -> Stream m Word8 -> m ()+writeAppend = writeAppendWith defaultChunkSize++{-+-- | Like 'append' but the file is not locked for exclusive writes.+--+-- @since 0.7.0+{-# INLINE appendShared #-}+appendShared :: MonadIO m => Handle -> Stream m Word8 -> m ()+appendShared = undefined+-}++-------------------------------------------------------------------------------+-- IO with encoding/decoding Unicode characters+-------------------------------------------------------------------------------++{-+-- |+-- > readUtf8 = decodeUtf8 . read+--+-- Read a UTF8 encoded stream of unicode characters from a file handle.+--+-- @since 0.7.0+{-# INLINE readUtf8 #-}+readUtf8 :: MonadIO m => Handle -> Stream m Char+readUtf8 = decodeUtf8 . read++-- |+-- > writeUtf8 h s = write h $ encodeUtf8 s+--+-- Encode a stream of unicode characters to UTF8 and write it to the given file+-- handle. Default block buffering applies to the writes.+--+-- @since 0.7.0+{-# INLINE writeUtf8 #-}+writeUtf8 :: MonadIO m => Handle -> Stream m Char -> m ()+writeUtf8 h s = write h $ encodeUtf8 s++-- | Write a stream of unicode characters after encoding to UTF-8 in chunks+-- separated by a linefeed character @'\n'@. If the size of the buffer exceeds+-- @defaultChunkSize@ and a linefeed is not yet found, the buffer is written+-- anyway. This is similar to writing to a 'Handle' with the 'LineBuffering'+-- option.+--+-- @since 0.7.0+{-# INLINE writeUtf8ByLines #-}+writeUtf8ByLines :: MonadIO m => Handle -> Stream m Char -> m ()+writeUtf8ByLines = undefined++-- | Read UTF-8 lines from a file handle and apply the specified fold to each+-- line. This is similar to reading a 'Handle' with the 'LineBuffering' option.+--+-- @since 0.7.0+{-# INLINE readLines #-}+readLines :: MonadIO m => Handle -> Fold m Char b -> Stream m b+readLines h f = foldLines (readUtf8 h) f++-------------------------------------------------------------------------------+-- Framing on a sequence+-------------------------------------------------------------------------------++-- | Read a stream from a file handle and split it into frames delimited by+-- the specified sequence of elements. The supplied fold is applied on each+-- frame.+--+-- @since 0.7.0+{-# INLINE readFrames #-}+readFrames :: (MonadIO m, Storable a)+ => Array a -> Handle -> Fold m a b -> Stream m b+readFrames = undefined -- foldFrames . read++-- | Write a stream to the given file handle buffering up to frames separated+-- by the given sequence or up to a maximum of @defaultChunkSize@.+--+-- @since 0.7.0+{-# INLINE writeByFrames #-}+writeByFrames :: (MonadIO m, Storable a)+ => Array a -> Handle -> Stream m a -> m ()+writeByFrames = undefined++-------------------------------------------------------------------------------+-- Random Access IO (Seek)+-------------------------------------------------------------------------------++-- XXX handles could be shared, so we may not want to use the handle state at+-- all for these APIs. we can use pread and pwrite instead. On windows we will+-- need to use readFile/writeFile with an offset argument.++-------------------------------------------------------------------------------++-- | Read the element at the given index treating the file as an array.+--+-- @since 0.7.0+{-# INLINE readIndex #-}+readIndex :: Storable a => Handle -> Int -> Maybe a+readIndex arr i = undefined++-- NOTE: To represent a range to read we have chosen (start, size) instead of+-- (start, end). This helps in removing the ambiguity of whether "end" is+-- included in the range or not.+--+-- We could avoid specifying the range to be read and instead use "take size"+-- on the stream, but it may end up reading more and then consume it partially.++-- | @readSliceWith chunkSize handle pos len@ reads up to @len@ bytes+-- from @handle@ starting at the offset @pos@ from the beginning of the file.+--+-- Reads are performed in chunks of size @chunkSize@. For block devices, to+-- avoid reading partial blocks @chunkSize@ must align with the block size of+-- the underlying device. If the underlying block size is unknown, it is a good+-- idea to keep it a multiple 4KiB. This API ensures that the start of each+-- chunk is aligned with @chunkSize@ from second chunk onwards.+--+{-# INLINE readSliceWith #-}+readSliceWith :: (MonadIO m, Storable a)+ => Int -> Handle -> Int -> Int -> Stream m a+readSliceWith chunkSize h pos len = undefined++-- | @readSlice h i count@ streams a slice from the file handle @h@ starting+-- at index @i@ and reading up to @count@ elements in the forward direction+-- ending at the index @i + count - 1@.+--+-- @since 0.7.0+{-# INLINE readSlice #-}+readSlice :: (MonadIO m, Storable a)+ => Handle -> Int -> Int -> Stream m a+readSlice = readSliceWith defaultChunkSize++-- | @readSliceRev h i count@ streams a slice from the file handle @h@ starting+-- at index @i@ and reading up to @count@ elements in the reverse direction+-- ending at the index @i - count + 1@.+--+-- @since 0.7.0+{-# INLINE readSliceRev #-}+readSliceRev :: (MonadIO m, Storable a)+ => Handle -> Int -> Int -> Stream m a+readSliceRev h i count = undefined++-- | Write the given element at the given index in the file.+--+-- @since 0.7.0+{-# INLINE writeIndex #-}+writeIndex :: (MonadIO m, Storable a) => Handle -> Int -> a -> m ()+writeIndex h i a = undefined++-- | @writeSlice h i count stream@ writes a stream to the file handle @h@+-- starting at index @i@ and writing up to @count@ elements in the forward+-- direction ending at the index @i + count - 1@.+--+-- @since 0.7.0+{-# INLINE writeSlice #-}+writeSlice :: (Monad m, Storable a)+ => Handle -> Int -> Int -> Stream m a -> m ()+writeSlice h i len s = undefined++-- | @writeSliceRev h i count stream@ writes a stream to the file handle @h@+-- starting at index @i@ and writing up to @count@ elements in the reverse+-- direction ending at the index @i - count + 1@.+--+-- @since 0.7.0+{-# INLINE writeSliceRev #-}+writeSliceRev :: (Monad m, Storable a)+ => Handle -> Int -> Int -> Stream m a -> m ()+writeSliceRev arr i len s = undefined+-}
src/Streamly/Internal/FileSystem/Handle.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE CPP #-}+ #include "inline.hs" -- |@@ -25,6 +27,12 @@ -- module Streamly.Internal.FileSystem.Handle (+ -- * Setup+ -- | To execute the code examples provided in this module in ghci, please+ -- run the following commands first.+ --+ -- $setup+ -- * Singleton APIs getChunk , getChunkOf@@ -32,8 +40,8 @@ -- * Streams , read- , readWith- , readChunksWith+ , readWith -- readConcatChunksOf+ , readChunksWith -- readChunksOf , readChunks -- * Unfolds@@ -51,10 +59,10 @@ -- , writeUtf8ByLines -- , writeByFrames -- , writeLines- , writeWith+ , writeWith -- writeChunksOf , writeChunks- , writeChunksWith- , writeMaybesWith+ , writeChunksWith -- writeCompactChunksOf+ , writeMaybesWith -- writeCompactJustsOf -- * Refolds , writer@@ -113,9 +121,10 @@ import Control.Exception (assert) import Control.Monad.IO.Class (MonadIO(..)) import Data.Function ((&))+import Data.Kind (Type) import Data.Maybe (isNothing, fromJust) import Data.Word (Word8)-import Streamly.Internal.Data.Unboxed (Unbox)+import Streamly.Internal.Data.Unbox (Unbox) import System.IO (Handle, SeekMode(..), hGetBufSome, hPutBuf, hSeek) import Prelude hiding (read) @@ -123,36 +132,23 @@ import Streamly.Internal.Data.Refold.Type (Refold(..)) import Streamly.Internal.Data.Unfold.Type (Unfold(..)) import Streamly.Internal.Data.Array.Type- (Array(..), writeNUnsafe, unsafeFreezeWithShrink, byteLength)-import Streamly.Internal.Data.Stream.StreamD.Type (Stream)-import Streamly.Internal.Data.Stream.Chunked (lpackArraysChunksOf)+ (Array(..), unsafeFreezeWithShrink, byteLength)+import Streamly.Internal.Data.Stream.Type (Stream) -- import Streamly.String (encodeUtf8, decodeUtf8, foldLines) import Streamly.Internal.System.IO (defaultChunkSize) import qualified Streamly.Data.Fold as FL-import qualified Streamly.Data.Array as A-import qualified Streamly.Internal.Data.Array.Type as A-import qualified Streamly.Internal.Data.Stream.Chunked as AS-import qualified Streamly.Internal.Data.Array.Mut.Type as MArray+import qualified Streamly.Internal.Data.Array as A+import qualified Streamly.Internal.Data.MutArray.Type as MArray import qualified Streamly.Internal.Data.Refold.Type as Refold import qualified Streamly.Internal.Data.Fold.Type as FL(refoldMany)-import qualified Streamly.Internal.Data.Stream.StreamD as S-import qualified Streamly.Internal.Data.Stream.StreamD.Type as D+import qualified Streamly.Internal.Data.Stream as S+import qualified Streamly.Internal.Data.Stream as D (Stream(..), Step(..)) import qualified Streamly.Internal.Data.Unfold as UF-import qualified Streamly.Internal.Data.Stream.StreamK.Type as K (mkStream)+import qualified Streamly.Internal.Data.StreamK.Type as K (mkStream) --- $setup--- >>> import qualified Streamly.Data.Array as Array--- >>> import qualified Streamly.Data.Fold as Fold--- >>> import qualified Streamly.Data.Unfold as Unfold--- >>> import qualified Streamly.Data.Stream as Stream------ >>> import qualified Streamly.Internal.Data.Array.Type as Array (writeNUnsafe)--- >>> import qualified Streamly.Internal.Data.Stream as Stream--- >>> import qualified Streamly.Internal.Data.Unfold as Unfold (first)--- >>> import qualified Streamly.Internal.FileSystem.Handle as Handle--- >>> import qualified Streamly.Internal.System.IO as IO (defaultChunkSize)+#include "DocTestFileSystemHandle.hs" ------------------------------------------------------------------------------- -- References@@ -171,6 +167,8 @@ -- Array IO (Input) ------------------------------------------------------------------------------- +-- XXX add an API that compacts the arrays to an exact size.+ -- | Read a 'ByteArray' consisting of one or more bytes from a file handle. If -- no data is available on the handle it blocks until at least one byte becomes -- available. If any data is available then it immediately returns that data@@ -180,14 +178,10 @@ {-# INLINABLE getChunk #-} getChunk :: MonadIO m => Int -> Handle -> m (Array Word8) getChunk size h = liftIO $ do- arr <- MArray.newPinnedBytes size -- ptr <- mallocPlainForeignPtrAlignedBytes size (alignment (undefined :: Word8))- MArray.asPtrUnsafe arr $ \p -> do- n <- hGetBufSome h p size- -- XXX shrink only if the diff is significant- return $- unsafeFreezeWithShrink $- arr { MArray.arrEnd = n, MArray.arrBound = size }+ arr <- MArray.unsafeCreateWithPtr' size $ \p -> hGetBufSome h p size+ -- XXX shrink only if the diff is significant+ pure $ unsafeFreezeWithShrink arr -- This could be useful in implementing the "reverse" read APIs or if you want -- to read arrays of exact size instead of compacting them later. Compacting@@ -327,11 +321,11 @@ -- | Unfolds the tuple @(bufsize, handle)@ into a byte stream, read requests -- to the IO device are performed using buffers of @bufsize@. ----- >>> readerWith = Unfold.many Array.reader Handle.chunkReaderWith+-- >>> readerWith = Unfold.unfoldEach Array.reader Handle.chunkReaderWith -- {-# INLINE readerWith #-} readerWith :: MonadIO m => Unfold m (Int, Handle) Word8-readerWith = UF.many A.reader chunkReaderWith+readerWith = UF.unfoldEach A.reader chunkReaderWith -- | Same as 'readerWith' --@@ -340,38 +334,34 @@ readWithBufferOf :: MonadIO m => Unfold m (Int, Handle) Word8 readWithBufferOf = readerWith -{-# INLINE concatChunks #-}-concatChunks :: (Monad m, Unbox a) => Stream m (Array a) -> Stream m a-concatChunks = S.unfoldMany A.reader- -- | @readWith bufsize handle@ reads a byte stream from a file -- handle, reads are performed in chunks of up to @bufsize@. ----- >>> readWith size h = Stream.unfoldMany Array.reader $ Handle.readChunksWith size h+-- >>> readWith size h = Stream.unfoldEach Array.reader $ Handle.readChunksWith size h -- -- /Pre-release/ {-# INLINE readWith #-} readWith :: MonadIO m => Int -> Handle -> Stream m Word8-readWith size h = concatChunks $ readChunksWith size h+readWith size h = A.concat $ readChunksWith size h -- | Unfolds a file handle into a byte stream. IO requests to the device are -- performed in sizes of -- 'Streamly.Internal.Data.Array.Type.defaultChunkSize'. ----- >>> reader = Unfold.many Array.reader chunkReader+-- >>> reader = Unfold.unfoldEach Array.reader Handle.chunkReader -- {-# INLINE reader #-} reader :: MonadIO m => Unfold m Handle Word8-reader = UF.many A.reader chunkReader+reader = UF.unfoldEach A.reader chunkReader -- | Generate a byte stream from a file 'Handle'. ----- >>> read h = Stream.unfoldMany Array.reader $ Handle.readChunks h+-- >>> read h = Stream.unfoldEach Array.reader $ Handle.readChunks h -- -- /Pre-release/ {-# INLINE read #-} read :: MonadIO m => Handle -> Stream m Word8-read = concatChunks . readChunks+read = A.concat . readChunks ------------------------------------------------------------------------------- -- Writing@@ -384,15 +374,10 @@ -- | Write an 'Array' to a file handle. -- {-# INLINABLE putChunk #-}-putChunk :: MonadIO m => Handle -> Array a -> m ()+putChunk :: forall m (a :: Type). MonadIO m => Handle -> Array a -> m () putChunk _ arr | byteLength arr == 0 = return ()-putChunk h arr = A.asPtrUnsafe arr $ \ptr ->- liftIO $ hPutBuf h ptr aLen-- where-- -- XXX We should have the length passed by asPtrUnsafe itself.- aLen = A.byteLength arr+putChunk h arr = A.unsafePinnedAsPtr arr $ \ptr byteLen ->+ liftIO $ hPutBuf h ptr byteLen ------------------------------------------------------------------------------- -- Stream of Arrays IO@@ -408,7 +393,8 @@ -- >>> putChunks h = Stream.fold (Fold.drainBy (Handle.putChunk h)) -- {-# INLINE putChunks #-}-putChunks :: MonadIO m => Handle -> Stream m (Array a) -> m ()+putChunks :: forall m (a :: Type). MonadIO m =>+ Handle -> Stream m (Array a) -> m () putChunks h = S.fold (FL.drainMapM (putChunk h)) -- XXX AS.compact can be written idiomatically in terms of foldMany, just like@@ -423,17 +409,17 @@ {-# INLINE putChunksWith #-} putChunksWith :: (MonadIO m, Unbox a) => Int -> Handle -> Stream m (Array a) -> m ()-putChunksWith n h xs = putChunks h $ AS.compact n xs+putChunksWith n h xs = putChunks h $ A.compactMax n xs +-- > putBytesWith n h m = Handle.putChunks h $ A.pinnedChunksOf n m+ -- | @putBytesWith bufsize handle stream@ writes @stream@ to @handle@ -- in chunks of @bufsize@. A write is performed to the IO device as soon as we -- collect the required input size. ----- >>> putBytesWith n h m = Handle.putChunks h $ Stream.chunksOf n m--- {-# INLINE putBytesWith #-} putBytesWith :: MonadIO m => Int -> Handle -> Stream m Word8 -> m ()-putBytesWith n h m = putChunks h $ A.chunksOf n m+putBytesWith n h m = putChunks h $ A.chunksOf' n m -- | Write a byte stream to a file handle. Accumulates the input in chunks of -- up to 'Streamly.Internal.Data.Array.Type.defaultChunkSize' before writing.@@ -453,18 +439,16 @@ -- writeChunks h = Fold.drainBy (Handle.putChunk h) -- {-# INLINE writeChunks #-}-writeChunks :: MonadIO m => Handle -> Fold m (Array a) ()+writeChunks :: forall m (a :: Type). MonadIO m => Handle -> Fold m (Array a) () writeChunks h = FL.drainMapM (putChunk h) -- | Like writeChunks but uses the experimental 'Refold' API. -- -- /Internal/ {-# INLINE chunkWriter #-}-chunkWriter :: MonadIO m => Refold m Handle (Array a) ()+chunkWriter :: forall m (a :: Type). MonadIO m => Refold m Handle (Array a) () chunkWriter = Refold.drainBy putChunk --- XXX lpackArraysChunksOf should be written idiomatically- -- | @writeChunksWith bufsize handle@ writes a stream of arrays -- to @handle@ after coalescing the adjacent arrays in chunks of @bufsize@. -- We never split an array, if a single array is bigger than the specified size@@ -474,7 +458,10 @@ {-# INLINE writeChunksWith #-} writeChunksWith :: (MonadIO m, Unbox a) => Int -> Handle -> Fold m (Array a) ()-writeChunksWith n h = lpackArraysChunksOf n (writeChunks h)+-- writeChunksWith n h = A.lCompactGE n (writeChunks h)+writeChunksWith n h =+ FL.postscanl (A.scanCompactMin n)+ $ FL.catMaybes (writeChunks h) -- | Same as 'writeChunksWith' --@@ -492,17 +479,17 @@ -- do not want buffering to occur at GHC level as well. Same thing applies to -- writes as well. --- XXX Maybe we should have a Fold.chunksOf like we have Stream.chunksOf+-- XXX Maybe we should have a Fold.chunksOf like we have Array.chunksOf -- | @writeWith reqSize handle@ writes the input stream to @handle@. -- Bytes in the input stream are collected into a buffer until we have a chunk -- of @reqSize@ and then written to the IO device. ----- >>> writeWith n h = Fold.groupsOf n (Array.writeNUnsafe n) (Handle.writeChunks h)+-- >>> writeWith n h = Fold.groupsOf n (Array.unsafeCreateOf n) (Handle.writeChunks h) -- {-# INLINE writeWith #-} writeWith :: MonadIO m => Int -> Handle -> Fold m Word8 ()-writeWith n h = FL.groupsOf n (writeNUnsafe n) (writeChunks h)+writeWith n h = FL.groupsOf n (A.unsafeCreateOf' n) (writeChunks h) -- | Same as 'writeWith' --@@ -520,7 +507,7 @@ writeMaybesWith :: (MonadIO m ) => Int -> Handle -> Fold m (Maybe Word8) () writeMaybesWith n h =- let writeNJusts = FL.lmap fromJust $ A.writeN n+ let writeNJusts = FL.lmap fromJust $ A.createOf' n writeOnNothing = FL.takeEndBy_ isNothing writeNJusts in FL.many writeOnNothing (writeChunks h) @@ -530,7 +517,7 @@ {-# INLINE writerWith #-} writerWith :: MonadIO m => Int -> Refold m Handle Word8 () writerWith n =- FL.refoldMany (FL.take n $ writeNUnsafe n) chunkWriter+ FL.refoldMany (FL.take n $ A.unsafeCreateOf' n) chunkWriter -- | Write a byte stream to a file handle. Accumulates the input in chunks of -- up to 'Streamly.Internal.Data.Array.Type.defaultChunkSize' before writing
+ src/Streamly/Internal/FileSystem/Path.hs view
@@ -0,0 +1,129 @@+{-# LANGUAGE CPP #-}+-- |+-- Module : Streamly.Internal.FileSystem.Path+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--+-- == References+--+-- * https://en.wikipedia.org/wiki/Path_(computing)+-- * https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file+-- * https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/62e862f4-2a51-452e-8eeb-dc4ff5ee33cc+--+-- == Windows and Posix Paths+--+-- We should be able to manipulate windows paths on posix and posix paths on+-- windows as well. Therefore, we have WindowsPath and PosixPath types which+-- are supported on both platforms. However, the Path module aliases Path to+-- WindowsPath on Windows and PosixPath on Posix.+--+-- == File System as Tree vs Graph+--+-- A file system is a tree when there are no hard links or symbolic links. But+-- in the presence of symlinks it could be a DAG or a graph, because directory+-- symlinks can create cycles.+--+-- == Rooted and Branch paths+--+-- We make two distinctions for paths, a path may a specific filesystem root+-- attached to it or it may be a free branch without a root attached.+--+-- A path that has a root attached to it is called a rooted path e.g. /usr is a+-- rooted path, . is a rooted path, ./bin is a rooted path. A rooted path could+-- be absolute e.g. /usr or it could be relative e.g. ./bin . A rooted path+-- always has two components, a specific "root" which could be explicit or+-- implicit, and a path segment relative to the root. A rooted path with a+-- fixed root is known as an absolute path whereas a rooted path with an+-- implicit root e.g. "./bin" is known as a relative path.+--+-- A path that does not have a root attached but defines steps to go from some+-- place to another is a path branch. For example, "local/bin" is a path branch+-- whereas "./local/bin" is a rooted path.+--+-- Rooted paths can never be appended to any other path whereas a branch can be+-- appended.+--+-- == Comparing Paths+--+-- We can compare two absolute rooted paths or path branches but we cannot+-- compare two relative rooted paths. If each component of the path is the same+-- then the paths are considered to be equal.+--+-- == Implicit Roots (.)+--+-- On Posix and Windows "." implicitly refers to the current directory. On+-- Windows a path like @/Users/@ has the drive reference implicit. Such+-- references are contextual and may have different meanings at different+-- times.+--+-- @./bin@ may refer to a different location depending on what "." is+-- referring to. Thus we should not allow @./bin@ to be appended to another+-- path, @bin@ can be appended though. Similarly, we cannot compare @./bin@+-- with @./bin@ and say that they are equal because they may be referring to+-- different locations depending on in what context the paths were created.+--+-- The same arguments apply to paths with implicit drive on Windows.+--+-- We can treat @.\/bin\/ls@ as an absolute path with "." as an implicit root.+-- The relative path is "bin/ls" which represents steps from somewhere to+-- somewhere else rather than a particular location. We can also call @./bin@+-- as a "rooted path" as it starts from particular location rather than+-- defining "steps" to go from one place to another. If we want to append such+-- paths we need to first make them explicitly relative by dropping the+-- implicit root. Or we can use unsafeAppend to force it anyway or unsafeCast+-- to convert absolute to relative.+--+-- On these absolute (Rooted) paths if we use takeRoot, it should return+-- RootCurDir, RootCurDrive and @Root Path@ to distinguish @./@, @/@, @C:/@. We+-- could represent them by different types but that would make the types even+-- more complicated. So runtime checks are are a good balance.+--+-- Path comparison should return EqTrue, EqFalse or EqUnknown. If we compare+-- these absolute/located paths having implicit roots then result should be+-- EqUnknown or maybe we can just return False?. @./bin@ and @./bin@ should be+-- treated as paths with different roots/drives but same relative path. The+-- programmer can explicitly drop the root and compare the relative paths if+-- they want to check literal equality.+--+-- Note that a trailing . or a . in the middle of a path is different as it+-- refers to a known name.+--+-- == Ambiguous References (..)+--+-- ".." in a path refers to the parent directory relative to the current path.+-- For an absolute root directory ".." refers to the root itself because you+-- cannot go further up.+--+-- When resolving ".." it always resolves to the parent of a directory as+-- stored in the directory entry. So if we landed in a directory via a symlink,+-- ".." can take us back to a different directory and not to the symlink+-- itself. Thus @a\/b/..@ may not be the same as @a/@. Shells like bash keep+-- track of the old paths explicitly, so you may not see this behavior when+-- using a shell.+--+-- For this reason we cannot process ".." in the path statically. However, if+-- the components of two paths are exactly the same then they will always+-- resolve to the same target. But two paths with different components could+-- also point to the same target. So if there are ".." in the path we cannot+-- definitively say if they are the same without resolving them.+--+-- == Exception Handling+--+-- Path creation routines use MonadThrow which can be interpreted as an Either+-- type. It is rare to actually handle exceptions in path creation functions,+-- we would rather fix the issue, so partial functions should also be fine. But+-- there may be some cases where we are parsing paths from external inputs,+-- reading from a file etc where we may want to handle exceptions. We can+-- always create partial wrappers from these if that is convenient to use.+--++#define IS_PORTABLE++#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+#define IS_WINDOWS+#include "Streamly/Internal/FileSystem/WindowsPath.hs"+#else+#include "Streamly/Internal/FileSystem/PosixPath.hs"+#endif
+ src/Streamly/Internal/FileSystem/Path/Common.hs view
@@ -0,0 +1,1564 @@+{-# LANGUAGE UnliftedFFITypes #-}+-- |+-- Module : Streamly.Internal.FileSystem.Path.Common+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--+module Streamly.Internal.FileSystem.Path.Common+ (+ -- * Types+ OS (..)++ -- * Validation+ , validatePath+ , validatePath'+ , validateFile++ -- * Construction+ , fromArray+ , unsafeFromArray+ , fromChars+ , unsafeFromChars++ -- * Quasiquoters+ , mkQ++ -- * Elimination+ , toString+ , toChars++ -- * Separators+ , primarySeparator+ , isSeparator+ , isSeparatorWord+ , dropTrailingSeparators+ , dropTrailingBy+ , hasTrailingSeparator+ , hasLeadingSeparator++ -- * Tests+ , isBranch+ , isRooted+ , isAbsolute+ -- , isRelative -- not isAbsolute+ , isRootRelative -- XXX hasRelativeRoot+ , isRelativeWithDrive -- XXX hasRelativeDriveRoot+ , hasDrive++ -- * Joining+ , append+ , append'+ , unsafeAppend+ , appendCString+ , appendCString'+ , unsafeJoinPaths+ -- , joinRoot -- XXX append should be enough++ -- * Splitting++ -- Note: splitting the search path does not belong here, it is shell aware+ -- operation. search path is separated by : and : is allowed in paths on+ -- posix. Shell would escape it which needs to be handled.++ , splitRoot+ -- , dropRoot+ -- , dropRelRoot -- if relative then dropRoot+ , splitHead+ , splitTail+ , splitPath+ , splitPath_++ -- * Dir and File+ , splitFile+ , splitDir++ -- * Extensions+ , extensionWord+ , splitExtension+ , splitExtensionBy+ -- , addExtension++ -- * Equality+ -- , processParentRefs+ , normalizeSeparators+ -- , normalize -- separators and /./ components (split/combine)+ , eqPathBytes+ , EqCfg(..)+ , eqPath+ -- , commonPrefix -- common prefix of two paths+ -- , eqPrefix -- common prefix is equal to first path+ -- , dropPrefix++ -- * Utilities+ , wordToChar+ , charToWord+ , unsafeIndexChar++ -- * Internal+ , unsafeSplitTopLevel+ , unsafeSplitDrive+ , unsafeSplitUNC+ , splitCompact+ , splitWithFilter+ )+where++#include "assert.hs"++import Control.Monad (when)+import Control.Monad.Catch (MonadThrow(..))+import Control.Monad.IO.Class (MonadIO(..))+import Data.Char (chr, ord, isAlpha, toUpper)+import Data.Function ((&))+import Data.Functor.Identity (Identity(..))+import Data.Word (Word8, Word16)+import Foreign (castPtr)+import Foreign.C (CString, CSize(..))+import GHC.Base (unsafeChr, Addr#)+import GHC.Ptr (Ptr(..))+import Language.Haskell.TH (Q, Exp)+import Language.Haskell.TH.Quote (QuasiQuoter (..))+import Streamly.Internal.Data.Array (Array(..))+import Streamly.Internal.Data.MutArray (MutArray)+import Streamly.Internal.Data.MutByteArray (Unbox(..))+import Streamly.Internal.Data.Path (PathException(..))+import Streamly.Internal.Data.Stream (Stream)+import System.IO.Unsafe (unsafePerformIO)++import qualified Data.List as List+import qualified Streamly.Internal.Data.Array as Array+import qualified Streamly.Internal.Data.Fold as Fold+import qualified Streamly.Internal.Data.MutArray as MutArray+import qualified Streamly.Internal.Data.Stream as Stream+import qualified Streamly.Internal.Unicode.Stream as Unicode++{- $setup+>>> :m++>>> import Data.Functor.Identity (runIdentity)+>>> import System.IO.Unsafe (unsafePerformIO)+>>> import qualified Streamly.Data.Stream as Stream+>>> import qualified Streamly.Unicode.Stream as Unicode+>>> import qualified Streamly.Internal.Data.Array as Array+>>> import qualified Streamly.Internal.FileSystem.Path.Common as Common+>>> import qualified Streamly.Internal.Unicode.Stream as Unicode+>>> import Streamly.Internal.FileSystem.Path (ignoreTrailingSeparators, allowRelativeEquality, ignoreCase)++>>> packPosix = unsafePerformIO . Stream.fold Array.create . Unicode.encodeUtf8' . Stream.fromList+>>> unpackPosix = runIdentity . Stream.toList . Unicode.decodeUtf8' . Array.read++>>> packWindows = unsafePerformIO . Stream.fold Array.create . Unicode.encodeUtf16le' . Stream.fromList+>>> unpackWindows = runIdentity . Stream.toList . Unicode.decodeUtf16le' . Array.read+-}++data OS = Windows | Posix deriving Eq++------------------------------------------------------------------------------+-- Parsing Operations+------------------------------------------------------------------------------++-- XXX We can use Enum type class to include the Char type as well so that the+-- functions can work on Array Word8/Word16/Char but that may be slow.++-- XXX Windows is supported only on little endian machines so generally we do+-- not need covnersion from LE to BE format unless we want to manipulate+-- windows paths on big-endian machines.++-- | Unsafe, may tructate to shorter word types, can only be used safely for+-- characters that fit in the given word size.+charToWord :: Integral a => Char -> a+charToWord c =+ let n = ord c+ in assert (n <= 255) (fromIntegral n)++-- | Unsafe, should be a valid character.+wordToChar :: Integral a => a -> Char+wordToChar = unsafeChr . fromIntegral++------------------------------------------------------------------------------+-- Array utils+------------------------------------------------------------------------------++-- | Index a word in an array and convert it to Char.+unsafeIndexChar :: (Unbox a, Integral a) => Int -> Array a -> Char+unsafeIndexChar i a = wordToChar (Array.unsafeGetIndex i a)++-- XXX put this in array module, we can have Array.fold and Array.foldM+foldArr :: Unbox a => Fold.Fold Identity a b -> Array a -> b+foldArr f arr = runIdentity $ Array.foldM f arr++{-# INLINE countLeadingBy #-}+countLeadingBy :: Unbox a => (a -> Bool) -> Array a -> Int+countLeadingBy p = foldArr (Fold.takeEndBy_ (not . p) Fold.length)++countTrailingBy :: Unbox a => (a -> Bool) -> Array a -> Int+countTrailingBy p = Array.foldRev (Fold.takeEndBy_ (not . p) Fold.length)++------------------------------------------------------------------------------+-- Separator parsing+------------------------------------------------------------------------------++extensionWord :: Integral a => a+extensionWord = charToWord '.'++posixSeparator :: Char+posixSeparator = '/'++windowsSeparator :: Char+windowsSeparator = '\\'++-- | Primary path separator character, @/@ on Posix and @\\@ on Windows.+-- Windows supports @/@ too as a separator. Please use 'isSeparator' for+-- testing if a char is a separator char.+{-# INLINE primarySeparator #-}+primarySeparator :: OS -> Char+primarySeparator Posix = posixSeparator+primarySeparator Windows = windowsSeparator++-- | On Posix only @/@ is a path separator but in windows it could be either+-- @/@ or @\\@.+{-# INLINE isSeparator #-}+isSeparator :: OS -> Char -> Bool+isSeparator Posix c = c == posixSeparator+isSeparator Windows c = (c == windowsSeparator) || (c == posixSeparator)++{-# INLINE isSeparatorWord #-}+isSeparatorWord :: Integral a => OS -> a -> Bool+isSeparatorWord os = isSeparator os . wordToChar++------------------------------------------------------------------------------+-- Separator normalization+------------------------------------------------------------------------------++-- | If the path is @//@ the result is @/@. If it is @a//@ then the result is+-- @a@. On Windows "c:" and "c:/" are different paths, therefore, we do not+-- drop the trailing separator from "c:/" or for that matter a separator+-- preceded by a ':'.+--+-- Can't use any arbitrary predicate "p", the logic in this depends on assuming+-- that it is a path separator.+{-# INLINE dropTrailingBy #-}+dropTrailingBy :: (Unbox a, Integral a) =>+ OS -> (a -> Bool) -> Array a -> Array a+dropTrailingBy os p arr =+ let len = Array.length arr+ n = countTrailingBy p arr+ arr1 = fst $ Array.unsafeBreakAt (len - n) arr+ in if n == 0+ then arr+ else if n == len -- "////"+ then+ -- Even though "//" is not allowed as a valid path.+ -- We still handle that case in this low level function.+ if os == Windows+ && n >= 2+ && Array.unsafeGetIndex 0 arr == Array.unsafeGetIndex 1 arr+ then fst $ Array.unsafeBreakAt 2 arr -- make it "//" share name+ else fst $ Array.unsafeBreakAt 1 arr+ -- "c:////" - keep one "/" after colon in ".*:///" otherwise it will+ -- change the meaning. "c:/" may also appear, in the middle e.g.+ -- in UNC paths.+ else if (os == Windows)+ && (Array.unsafeGetIndex (len - n - 1) arr == charToWord ':')+ then fst $ Array.unsafeBreakAt (len - n + 1) arr+ else arr1++-- XXX we cannot compact "//" to "/" on windows+{-# INLINE compactTrailingBy #-}+compactTrailingBy :: Unbox a => (a -> Bool) -> Array a -> Array a+compactTrailingBy p arr =+ let len = Array.length arr+ n = countTrailingBy p arr+ in if n <= 1+ then arr+ else fst $ Array.unsafeBreakAt (len - n + 1) arr++{-# INLINE dropTrailingSeparators #-}+dropTrailingSeparators :: (Unbox a, Integral a) => OS -> Array a -> Array a+dropTrailingSeparators os =+ dropTrailingBy os (isSeparator os . wordToChar)++-- | A path starting with a separator.+hasLeadingSeparator :: (Unbox a, Integral a) => OS -> Array a -> Bool+hasLeadingSeparator os a+ | Array.null a = False -- empty path should not occur+ | isSeparatorWord os (Array.unsafeGetIndex 0 a) = True+ | otherwise = False++{-# INLINE hasTrailingSeparator #-}+hasTrailingSeparator :: (Integral a, Unbox a) => OS -> Array a -> Bool+hasTrailingSeparator os path =+ let e = Array.getIndexRev 0 path+ in case e of+ Nothing -> False+ Just x -> isSeparatorWord os x++{-# INLINE toDefaultSeparator #-}+toDefaultSeparator :: Integral a => a -> a+toDefaultSeparator x =+ if isSeparatorWord Windows x+ then charToWord (primarySeparator Windows)+ else x++-- | Change all separators in the path to default separator on windows.+{-# INLINE normalizeSeparators #-}+normalizeSeparators :: (Integral a, Unbox a) => Array a -> Array a+normalizeSeparators a =+ -- XXX We can check and return the original array if no change is needed.+ Array.fromPureStreamN (Array.length a)+ $ fmap toDefaultSeparator+ $ Array.read a++------------------------------------------------------------------------------+-- Windows drive parsing+------------------------------------------------------------------------------++-- | @C:...@, does not check array length.+{-# INLINE unsafeHasDrive #-}+unsafeHasDrive :: (Unbox a, Integral a) => Array a -> Bool+unsafeHasDrive a+ -- Check colon first for quicker return+ | unsafeIndexChar 1 a /= ':' = False+ -- XXX If we found a colon anyway this cannot be a valid path unless it has+ -- a drive prefix. colon is not a valid path character.+ -- XXX check isAlpha perf+ | not (isAlpha (unsafeIndexChar 0 a)) = False+ | otherwise = True++-- | A path that starts with a alphabet followed by a colon e.g. @C:...@.+hasDrive :: (Unbox a, Integral a) => Array a -> Bool+hasDrive a = Array.length a >= 2 && unsafeHasDrive a++-- | A path that contains only an alphabet followed by a colon e.g. @C:@.+isDrive :: (Unbox a, Integral a) => Array a -> Bool+isDrive a = Array.length a == 2 && unsafeHasDrive a++------------------------------------------------------------------------------+-- Relative or Absolute+------------------------------------------------------------------------------++-- | A path relative to cur dir it is either @.@ or starts with @./@.+isRelativeCurDir :: (Unbox a, Integral a) => OS -> Array a -> Bool+isRelativeCurDir os a+ | len == 0 = False -- empty path should not occur+ | wordToChar (Array.unsafeGetIndex 0 a) /= '.' = False+ | len < 2 = True+ | otherwise = isSeparatorWord os (Array.unsafeGetIndex 1 a)++ where++ len = Array.length a++-- | A non-UNC path starting with a separator.+-- Note that "\\/share/x" is treated as "C:/share/x".+isRelativeCurDriveRoot :: (Unbox a, Integral a) => Array a -> Bool+isRelativeCurDriveRoot a+ | len == 0 = False -- empty path should not occur+ | len == 1 && sep0 = True+ | sep0 && c0 /= c1 = True -- "\\/share/x" is treated as "C:/share/x".+ | otherwise = False++ where++ len = Array.length a+ c0 = Array.unsafeGetIndex 0 a+ c1 = Array.unsafeGetIndex 1 a+ sep0 = isSeparatorWord Windows c0++-- | @C:@ or @C:a...@.+isRelativeWithDrive :: (Unbox a, Integral a) => Array a -> Bool+isRelativeWithDrive a =+ hasDrive a+ && ( Array.length a < 3+ || not (isSeparator Windows (unsafeIndexChar 2 a))+ )++isRootRelative :: (Unbox a, Integral a) => OS -> Array a -> Bool+isRootRelative Posix a = isRelativeCurDir Posix a+isRootRelative Windows a =+ isRelativeCurDir Windows a+ || isRelativeCurDriveRoot a+ || isRelativeWithDrive a++-- | @C:\...@. Note that "C:" or "C:a" is not absolute.+isAbsoluteWithDrive :: (Unbox a, Integral a) => Array a -> Bool+isAbsoluteWithDrive a =+ Array.length a >= 3+ && unsafeHasDrive a+ && isSeparator Windows (unsafeIndexChar 2 a)++-- | @\\\\...@ or @//...@+isAbsoluteUNC :: (Unbox a, Integral a) => Array a -> Bool+isAbsoluteUNC a+ | Array.length a < 2 = False+ | isSeparatorWord Windows c0 && c0 == c1 = True+ | otherwise = False++ where++ c0 = Array.unsafeGetIndex 0 a+ c1 = Array.unsafeGetIndex 1 a++-- XXX rename to isRootAbsolute++-- | Note that on Windows a path starting with a separator is relative to+-- current drive while on Posix this is absolute path as there is only one+-- drive.+isAbsolute :: (Unbox a, Integral a) => OS -> Array a -> Bool+isAbsolute Posix arr =+ hasLeadingSeparator Posix arr+isAbsolute Windows arr =+ isAbsoluteWithDrive arr || isAbsoluteUNC arr++------------------------------------------------------------------------------+-- Location or Segment+------------------------------------------------------------------------------++-- XXX API for static processing of .. (normalizeParentRefs)+--+-- Note: paths starting with . or .. are ambiguous and can be considered+-- segments or rooted. We consider a path starting with "." as rooted, when+-- someone uses "./x" they explicitly mean x in the current directory whereas+-- just "x" can be taken to mean a path segment without any specific root.+-- However, in typed paths the programmer can convey the meaning whether they+-- mean it as a segment or a rooted path. So even "./x" can potentially be used+-- as a segment which can just mean "x".+--+-- XXX For the untyped Path we can allow appending "./x" to other paths. We can+-- leave this to the programmer. In typed paths we can allow "./x" in segments.+-- XXX Empty path can be taken to mean "." except in case of UNC paths++isRooted :: (Unbox a, Integral a) => OS -> Array a -> Bool+isRooted Posix a =+ hasLeadingSeparator Posix a+ || isRelativeCurDir Posix a+isRooted Windows a =+ hasLeadingSeparator Windows a+ || isRelativeCurDir Windows a+ || hasDrive a -- curdir-in-drive relative, drive absolute++isBranch :: (Unbox a, Integral a) => OS -> Array a -> Bool+isBranch os = not . isRooted os++------------------------------------------------------------------------------+-- Split root+------------------------------------------------------------------------------++unsafeSplitPrefix :: (Unbox a, Integral a) =>+ OS -> Int -> Array a -> (Array a, Array a)+unsafeSplitPrefix os prefixLen arr =+ Array.unsafeBreakAt cnt arr++ where++ afterDrive = snd $ Array.unsafeBreakAt prefixLen arr+ n = countLeadingBy (isSeparatorWord os) afterDrive+ cnt = prefixLen + n++-- Note: We can have normalized splitting functions to normalize as we split+-- for efficiency. But then we will have to allocate new arrays instead of+-- slicing which can make it inefficient.++-- | Split a path prefixed with a separator into (drive, path) tuple.+--+-- >>> toListPosix (a,b) = (unpackPosix a, unpackPosix b)+-- >>> splitPosix = toListPosix . Common.unsafeSplitTopLevel Common.Posix . packPosix+--+-- >>> toListWin (a,b) = (unpackWindows a, unpackWindows b)+-- >>> splitWin = toListWin . Common.unsafeSplitTopLevel Common.Windows . packWindows+--+-- >>> splitPosix "/"+-- ("/","")+--+-- >>> splitPosix "//"+-- ("//","")+--+-- >>> splitPosix "/home"+-- ("/","home")+--+-- >>> splitPosix "/home/user"+-- ("/","home/user")+--+-- >>> splitWin "\\"+-- ("\\","")+--+-- >>> splitWin "\\home"+-- ("\\","home")+unsafeSplitTopLevel :: (Unbox a, Integral a) =>+ OS -> Array a -> (Array a, Array a)+-- Note on Windows we should be here only when the path starts with exactly one+-- separator, otherwise it would be UNC path. But on posix multiple separators+-- are valid.+unsafeSplitTopLevel os = unsafeSplitPrefix os 1++-- In some cases there is no valid drive component e.g. "\\a\\b", though if we+-- consider relative roots then we could use "\\" as the root in this case. In+-- other cases there is no valid path component e.g. "C:" or "\\share\\" though+-- the latter is not a valid path and in the former case we can use "." as the+-- path component.++-- | Split a path prefixed with drive into (drive, path) tuple.+--+-- >>> toList (a,b) = (unpackPosix a, unpackPosix b)+-- >>> split = toList . Common.unsafeSplitDrive . packPosix+--+-- >>> split "C:"+-- ("C:","")+--+-- >>> split "C:a"+-- ("C:","a")+--+-- >>> split "C:\\"+-- ("C:\\","")+--+-- >>> split "C:\\\\" -- this is invalid path+-- ("C:\\\\","")+--+-- >>> split "C:\\\\a" -- this is invalid path+-- ("C:\\\\","a")+--+-- >>> split "C:\\/a/b" -- is this valid path?+-- ("C:\\/","a/b")+unsafeSplitDrive :: (Unbox a, Integral a) => Array a -> (Array a, Array a)+unsafeSplitDrive = unsafeSplitPrefix Windows 2++-- | Skip separators and then parse the next path segment.+-- Return (segment offset, segment length).+parseSegment :: (Unbox a, Integral a) => Array a -> Int -> (Int, Int)+parseSegment arr sepOff = (segOff, segCnt)++ where++ arr1 = snd $ Array.unsafeBreakAt sepOff arr+ sepCnt = countLeadingBy (isSeparatorWord Windows) arr1+ segOff = sepOff + sepCnt++ arr2 = snd $ Array.unsafeBreakAt segOff arr+ segCnt = countLeadingBy (not . isSeparatorWord Windows) arr2++-- XXX We can split a path as "root, . , rest" or "root, /, rest".+-- XXX We can remove the redundant path separator after the root. With that+-- joining root vs other paths will become similar. But there are some special+-- cases e.g. (1) "C:a" does not have a separator, can we make this "C:.\\a"?+-- (2) In case of "/home" we have "/" as root - while joining root and path we+-- should not add another separator between root and path - thus joining root+-- and path in this case is anyway special.++-- | Split a path prefixed with "\\" into (drive, path) tuple.+--+-- >>> toList (a,b) = (unpackPosix a, unpackPosix b)+-- >>> split = toList . Common.unsafeSplitUNC . packPosix+--+-- >> split ""+-- ("","")+--+-- >>> split "\\\\"+-- ("\\\\","")+--+-- >>> split "\\\\server"+-- ("\\\\server","")+--+-- >>> split "\\\\server\\"+-- ("\\\\server\\","")+--+-- >>> split "\\\\server\\home"+-- ("\\\\server\\","home")+--+-- >>> split "\\\\?\\c:"+-- ("\\\\?\\c:","")+--+-- >>> split "\\\\?\\c:/"+-- ("\\\\?\\c:/","")+--+-- >>> split "\\\\?\\c:\\home"+-- ("\\\\?\\c:\\","home")+--+-- >>> split "\\\\?\\UNC/"+-- ("\\\\?\\UNC/","")+--+-- >>> split "\\\\?\\UNC\\server"+-- ("\\\\?\\UNC\\server","")+--+-- >>> split "\\\\?\\UNC/server\\home"+-- ("\\\\?\\UNC/server\\","home")+--+unsafeSplitUNC :: (Unbox a, Integral a) => Array a -> (Array a, Array a)+unsafeSplitUNC arr =+ if cnt1 == 1 && unsafeIndexChar 2 arr == '?'+ then do+ if uncLen == 3+ && unsafeIndexChar uncOff arr == 'U'+ && unsafeIndexChar (uncOff + 1) arr == 'N'+ && unsafeIndexChar (uncOff + 2) arr == 'C'+ then unsafeSplitPrefix Windows (serverOff + serverLen) arr+ else unsafeSplitPrefix Windows sepOff1 arr+ else unsafeSplitPrefix Windows sepOff arr++ where++ arr1 = snd $ Array.unsafeBreakAt 2 arr+ cnt1 = countLeadingBy (not . isSeparatorWord Windows) arr1+ sepOff = 2 + cnt1++ -- XXX there should be only one separator in a valid path?+ -- XXX it should either be UNC or two letter drive in a valid path+ (uncOff, uncLen) = parseSegment arr sepOff+ sepOff1 = uncOff + uncLen+ (serverOff, serverLen) = parseSegment arr sepOff1++-- XXX should we make the root Maybe? Both components will have to be Maybe to+-- avoid an empty path.+-- XXX Should we keep the trailing separator in the directory components?++{-# INLINE splitRoot #-}+splitRoot :: (Unbox a, Integral a) => OS -> Array a -> (Array a, Array a)+-- NOTE: validatePath depends on splitRoot splitting the path without removing+-- any redundant chars etc. It should just split and do nothing else.+-- XXX We can put an assert here "arrLen == rootLen + stemLen".+-- XXX assert (isValidPath path == isValidPath root)+--+-- NOTE: we cannot drop the trailing "/" on the root even if we want to -+-- because "c:/" will become "c:" and the two are not equivalent.+splitRoot Posix arr+ | isRooted Posix arr+ = unsafeSplitTopLevel Posix arr+ | otherwise = (Array.empty, arr)+splitRoot Windows arr+ | isRelativeCurDriveRoot arr || isRelativeCurDir Windows arr+ = unsafeSplitTopLevel Windows arr+ | hasDrive arr = unsafeSplitDrive arr+ | isAbsoluteUNC arr = unsafeSplitUNC arr+ | otherwise = (Array.empty, arr)++------------------------------------------------------------------------------+-- Split path+------------------------------------------------------------------------------++-- | Raw split an array on path separartor word using a filter to filter out+-- some splits.+{-# INLINE splitWithFilter #-}+splitWithFilter+ :: (Unbox a, Integral a, Monad m)+ => ((Int, Int) -> Bool)+ -> Bool+ -> OS+ -> Array a+ -> Stream m (Array a)+splitWithFilter filt withSep os arr =+ f (isSeparatorWord os) (Array.read arr)+ & Stream.filter filt+ & fmap (\(i, len) -> Array.unsafeSliceOffLen i len arr)++ where++ f = if withSep then Stream.indexEndBy else Stream.indexEndBy_++-- | Split a path on separator chars and compact contiguous separators and+-- remove /./ components. Note this does not treat the path root in a special+-- way.+{-# INLINE splitCompact #-}+splitCompact+ :: (Unbox a, Integral a, Monad m)+ => Bool+ -> OS+ -> Array a+ -> Stream m (Array a)+splitCompact withSep os arr =+ splitWithFilter (not . shouldFilterOut) withSep os arr++ where++ sepFilter (off, len) =+ ( len == 1+ && isSeparator os (unsafeIndexChar off arr)+ )+ ||+ -- Note, last component may have len == 2 but second char may not+ -- be slash, so we need to check for slash explicitly.+ --+ ( len == 2+ && unsafeIndexChar off arr == '.'+ && isSeparator os (unsafeIndexChar (off + 1) arr)+ )++ {-# INLINE shouldFilterOut #-}+ shouldFilterOut (off, len) =+ len == 0+ -- Note this is needed even when withSep is true - for the last+ -- component case.+ || (len == 1 && unsafeIndexChar off arr == '.')+ -- XXX Ensure that these are statically removed by GHC when withSep+ -- is False.+ || (withSep && sepFilter (off, len))++-- Split a path into its components.+--+-- Usage:+-- @+-- splitPathUsing withSep ignoreLeading os arr+-- @+--+-- if withSep == True then keep the trailing separators.+--+-- if ignoreLeading == True we drop all leading separators and relative paths.+-- Example behaviour (psuedo-code):+-- @+-- > f = splitPathUsing (withSep = False) (ignoreLeading = True)+-- > f "./a/b/c" == ["a","b","c"]+-- > f "./a/./b/c" == ["a","b","c"]+-- > f "/a/./b/c" == ["a","b","c"]+-- > f "/./a/./b/c" == ["a","b","c"]+-- > f "././a/./b/c" == ["a","b","c"]+-- > f "a/./b/c" == ["a","b","c"]+-- @+--+-- We can safely set @ignoreLeading = True@ if we splitRoot prior and only pass+-- the stem of the path to this function.+{-# INLINE splitPathUsing #-}+splitPathUsing+ :: (Unbox a, Integral a, Monad m)+ => Bool+ -> Bool+ -> OS+ -> Array a+ -> Stream m (Array a)+splitPathUsing withSep ignoreLeading os arr =+ let stream = splitCompact withSep os rest+ in if ignoreLeading || Array.null root+ then stream+ else Stream.cons root1 stream++ where++ -- We should not filter out a leading '.' on Posix or Windows.+ -- We should not filter out a '.' in the middle of a UNC root on windows.+ -- Therefore, we split the root and treat it in a special way.+ (root, rest) = splitRoot os arr+ root1 =+ if withSep+ then compactTrailingBy (isSeparator os . wordToChar) root+ else dropTrailingSeparators os root++{-# INLINE splitPath_ #-}+splitPath_+ :: (Unbox a, Integral a, Monad m)+ => OS -> Array a -> Stream m (Array a)+splitPath_ = splitPathUsing False False++{-# INLINE splitPath #-}+splitPath+ :: (Unbox a, Integral a, Monad m)+ => OS -> Array a -> Stream m (Array a)+splitPath = splitPathUsing True False++-- | Split the first non-empty path component.+--+-- /Unimplemented/+{-# INLINE splitHead #-}+splitHead :: -- (Unbox a, Integral a) =>+ OS -> Array a -> (Array a, Maybe (Array a))+splitHead _os _arr = undefined++-- | Split the last non-empty path component.+--+-- /Unimplemented/+{-# INLINE splitTail #-}+splitTail :: -- (Unbox a, Integral a) =>+ OS -> Array a -> (Maybe (Array a), Array a)+splitTail _os _arr = undefined++------------------------------------------------------------------------------+-- File or Dir+------------------------------------------------------------------------------++-- | Returns () if the path can be a valid file, otherwise throws an+-- exception.+validateFile :: (MonadThrow m, Unbox a, Integral a) => OS -> Array a -> m ()+validateFile os arr = do+ s1 <-+ Stream.toList+ $ Stream.take 3+ $ Stream.takeWhile (not . isSeparator os)+ $ fmap wordToChar+ $ Array.readRev arr+ -- XXX On posix we just need to check last 3 bytes of the array+ -- XXX Display the path in the exception messages.+ case s1 of+ [] -> throwM $ InvalidPath "A file name cannot have a trailing separator"+ '.' : xs ->+ case xs of+ [] -> throwM $ InvalidPath "A file name cannot have a trailing \".\""+ '.' : [] ->+ throwM $ InvalidPath "A file name cannot have a trailing \"..\""+ _ -> pure ()+ _ -> pure ()++ case os of+ Windows ->+ -- XXX We can exclude a UNC root as well but just the UNC root is+ -- not even a valid path.+ when (isDrive arr)+ $ throwM $ InvalidPath "A drive root is not a valid file name"+ Posix -> pure ()++{-# INLINE splitFile #-}+splitFile :: (Unbox a, Integral a) =>+ OS -> Array a -> Maybe (Maybe (Array a), Array a)+splitFile os arr =+ let p x =+ if os == Windows+ then x == charToWord ':' || isSeparatorWord os x+ else isSeparatorWord os x+ -- XXX Use Array.revBreakEndBy?+ fileLen = runIdentity+ $ Stream.fold (Fold.takeEndBy_ p Fold.length)+ $ Array.readRev arr+ arrLen = Array.length arr+ baseLen = arrLen - fileLen+ (base, file) = Array.unsafeBreakAt baseLen arr+ fileFirst = Array.unsafeGetIndex 0 file+ fileSecond = Array.unsafeGetIndex 1 file+ in+ if fileLen > 0+ -- exclude the file == '.' case+ && not (fileLen == 1 && fileFirst == charToWord '.')+ -- exclude the file == '..' case+ && not (fileLen == 2+ && fileFirst == charToWord '.'+ && fileSecond == charToWord '.')+ then+ if baseLen <= 0+ then Just (Nothing, arr)+ else Just (Just $ Array.unsafeSliceOffLen 0 baseLen base, file) -- "/"+ else Nothing++-- | Split a multi-component path into (dir, last component). If the path has a+-- single component and it is a root then return (path, "") otherwise return+-- ("", path).+--+-- Split a single component into (dir, "") if it can be a dir i.e. it is either+-- a path root, "." or ".." or has a trailing separator.+--+-- The only difference between splitFile and splitDir:+--+-- >> splitFile "a/b/"+-- ("a/b/", "")+-- >> splitDir "a/b/"+-- ("a/", "b/")+--+-- This is equivalent to splitPath and keeping the last component but is usually+-- faster.+--+-- >>> toList (a,b) = (unpackPosix a, unpackPosix b)+-- >>> splitPosix = toList . Common.splitDir Common.Posix . packPosix+--+-- >> splitPosix "/"+-- ("/","")+--+-- >> splitPosix "."+-- (".","")+--+-- >> splitPosix "/."+-- ("/.","")+--+-- >> splitPosix "/x"+-- ("/","x")+--+-- >> splitPosix "/x/"+-- ("/","x/")+--+-- >> splitPosix "//"+-- ("//","")+--+-- >> splitPosix "./x"+-- ("./","x")+--+-- >> splitPosix "x"+-- ("","x")+--+-- >> splitPosix "x/"+-- ("x/","")+--+-- >> splitPosix "x/y"+-- ("x/","y")+--+-- >> splitPosix "x/y/"+-- ("x/","y/")+--+-- >> splitPosix "x/y//"+-- ("x/","y//")+--+-- >> splitPosix "x//y"+-- ("x//","y")+--+-- >> splitPosix "x/./y"+-- ("x/./","y")+--+-- /Unimplemented/+{-# INLINE splitDir #-}+splitDir :: -- (Unbox a, Integral a) =>+ OS -> Array a -> (Array a, Array a)+splitDir _os _arr = undefined++------------------------------------------------------------------------------+-- Split extensions+------------------------------------------------------------------------------++-- | Like split extension but we can specify the extension char to be used.+{-# INLINE splitExtensionBy #-}+splitExtensionBy :: (Unbox a, Integral a) =>+ a -> OS -> Array a -> Maybe (Array a, Array a)+splitExtensionBy c os arr =+ let p x = x == c || isSeparatorWord os x+ -- XXX Use Array.revBreakEndBy_+ extLen = runIdentity+ $ Stream.fold (Fold.takeEndBy p Fold.length)+ $ Array.readRev arr+ arrLen = Array.length arr+ baseLen = arrLen - extLen+ -- XXX We can use reverse split operation on the array+ res@(base, ext) = Array.unsafeBreakAt baseLen arr+ baseLast = Array.unsafeGetIndexRev 0 base+ extFirst = Array.unsafeGetIndex 0 ext+ in+ -- For an extension to be present the path must be at least 3 chars.+ -- non-empty base followed by extension char followed by non-empty+ -- extension.+ if arrLen > 2+ -- If ext is empty, then there is no extension and we should not+ -- strip an extension char if any at the end of base.+ && extLen > 1+ && extFirst == c+ -- baseLast is always either base name char or '/' unless empty+ -- if baseLen is 0 then we have not found an extension.+ && baseLen > 0+ -- If baseLast is '/' then base name is empty which means it is a+ -- dot file and there is no extension.+ && not (isSeparatorWord os baseLast)+ -- On Windows if base is 'c:.' or a UNC path ending in '/c:.' then+ -- it is a dot file, no extension.+ && not (os == Windows && baseLast == charToWord ':')+ then Just res+ else Nothing++{-# INLINE splitExtension #-}+splitExtension :: (Unbox a, Integral a) => OS -> Array a -> Maybe (Array a, Array a)+splitExtension = splitExtensionBy extensionWord++{-+-- Instead of this keep calling splitExtension until there is no more extension+-- returned.+{-# INLINE splitAllExtensionsBy #-}+splitAllExtensionsBy :: (Unbox a, Integral a) =>+ Bool -> a -> OS -> Array a -> (Array a, Array a)+-- If the isFileName arg is true, it means that the path supplied does not have+-- any separator chars, so we can do it more efficiently.+splitAllExtensionsBy isFileName extChar os arr =+ let file =+ if isFileName+ then arr+ else snd $ splitFile os arr+ fileLen = Array.length file+ arrLen = Array.length arr+ baseLen = foldArr (Fold.takeEndBy_ (== extChar) Fold.length) file+ extLen = fileLen - baseLen+ in+ -- XXX unsafeBreakAt itself should use Array.empty in case of no split+ if fileLen > 0 && extLen > 1 && extLen /= fileLen+ then (Array.unsafeBreakAt (arrLen - extLen) arr)+ else (arr, Array.empty)++-- |+--+-- TODO: This function needs to be consistent with splitExtension. It should+-- strip all valid extensions by that definition.+--+-- splitAllExtensions "x/y.tar.gz" gives ("x/y", ".tar.gz")+--+-- >>> toList (a,b) = (unpackPosix a, unpackPosix b)+-- >>> splitPosix = toList . Common.splitAllExtensions Common.Posix . packPosix+--+-- >>> toListWin (a,b) = (unpackWindows a, unpackWindows b)+-- >>> splitWin = toListWin . Common.splitAllExtensions Common.Windows . packWindows+--+-- >>> splitPosix "/"+-- ("/","")+--+-- >>> splitPosix "."+-- (".","")+--+-- >>> splitPosix "x"+-- ("x","")+--+-- >>> splitPosix "/x"+-- ("/x","")+--+-- >>> splitPosix "x/"+-- ("x/","")+--+-- >>> splitPosix "./x"+-- ("./x","")+--+-- >>> splitPosix "x/."+-- ("x/.","")+--+-- >>> splitPosix "x/y."+-- ("x/y.","")+--+-- >>> splitPosix "/x.y"+-- ("/x",".y")+--+-- >>> splitPosix "x/.y"+-- ("x/.y","")+--+-- >>> splitPosix ".x"+-- (".x","")+--+-- >>> splitPosix "x."+-- ("x.","")+--+-- >>> splitPosix ".x.y"+-- (".x",".y")+--+-- >>> splitPosix "x/y.z"+-- ("x/y",".z")+--+-- >>> splitPosix "x.y.z"+-- ("x",".y.z")+--+-- >>> splitPosix "x..y" -- ??+-- ("x.",".y")+--+-- >>> splitPosix ".."+-- ("..","")+--+-- >>> splitPosix "..."+-- ("...","")+--+-- >>> splitPosix "...x"+-- ("...x","")+--+-- >>> splitPosix "x/y.z/"+-- ("x/y.z/","")+--+-- >>> splitPosix "x/y"+-- ("x/y","")+--+-- >>> splitWin "x:y"+-- ("x:y","")+--+-- >>> splitWin "x:.y"+-- ("x:.y","")+--+{-# INLINE splitAllExtensions #-}+splitAllExtensions :: (Unbox a, Integral a) =>+ OS -> Array a -> (Array a, Array a)+splitAllExtensions = splitAllExtensionsBy False extensionWord+-}++------------------------------------------------------------------------------+-- Construction+------------------------------------------------------------------------------++{-# INLINE isInvalidPathChar #-}+isInvalidPathChar :: Integral a => OS -> a -> Bool+isInvalidPathChar Posix x = x == 0+isInvalidPathChar Windows x =+ -- case should be faster than list search+ case x of+ 34 -> True -- '"'+ 42 -> True -- '*'+ 58 -> True -- ':'+ 60 -> True -- '<'+ 62 -> True -- '>'+ 63 -> True -- '?'+ 124 -> True -- '|'+ _ -> x <= charToWord '\US'++countLeadingValid :: (Unbox a, Integral a) => OS -> Array a -> Int+countLeadingValid os path =+ let f = Fold.takeEndBy_ (isInvalidPathChar os) Fold.length+ in foldArr f path++-- XXX Supply it an array for checking and use a more efficient prefix matching+-- check.++-- | Only for windows.+isInvalidPathComponent :: Integral a => [[a]]+isInvalidPathComponent = fmap (fmap charToWord)+ [ "CON","PRN","AUX","NUL","CLOCK$"+ , "COM1","COM2","COM3","COM4","COM5","COM6","COM7","COM8","COM9"+ , "LPT1","LPT2","LPT3","LPT4","LPT5","LPT6","LPT7","LPT8","LPT9"+ ]++{- HLINT ignore "Use when" -}+validatePathWith :: (MonadThrow m, Integral a, Unbox a) =>+ Bool -> OS -> Array a -> m ()+validatePathWith _ Posix path =+ let pathLen = Array.length path+ validLen = countLeadingValid Posix path+ in if pathLen == 0+ then throwM $ InvalidPath "Empty path"+ else if pathLen /= validLen+ then throwM $ InvalidPath+ $ "Null char found after " ++ show validLen ++ " characters."+ else pure ()+validatePathWith allowRoot Windows path+ | Array.null path = throwM $ InvalidPath "Empty path"+ | otherwise = do+ if hasDrive path && postDriveSep > 1 -- "C://"+ then throwM $ InvalidPath+ "More than one separators between drive root and the path"+ else if isAbsoluteUNC path+ then+ if postDriveSep > 1 -- "///x"+ then throwM $ InvalidPath+ "Path starts with more than two separators"+ else if invalidRootComponent -- "//prn/x"+ then throwM $ InvalidPath+ -- XXX print the invalid component name+ "Special filename component found in share root"+ else if rootEndSeps /= 1 -- "//share//x"+ then throwM $ InvalidPath+ $ "Share name is needed and exactly one separator is needed "+ ++ "after the share root"+ else if not allowRoot && Array.null stem -- "//share/"+ then throwM $ InvalidPath+ "the share root must be followed by a non-empty path"+ else pure ()+ else pure ()++ if stemLen /= validStemLen -- "x/x>y"+ then throwM $ InvalidPath+ $ "Disallowed char found after "+ ++ show (rootLen + validStemLen)+ ++ " characters. The invalid char is: "+ ++ show (chr (fromIntegral invalidVal))+ ++ " [" ++ show invalidVal ++ "]"+ else if invalidComponent -- "x/prn/y"+ -- XXX print the invalid component name+ then throwM $ InvalidPath "Disallowed Windows filename in path"+ else pure ()++ where++ postDrive = snd $ Array.unsafeBreakAt 2 path+ postDriveSep = countLeadingBy (isSeparatorWord Windows) postDrive++ -- XXX check invalid chars in the path root as well - except . and '?'?+ (root, stem) = splitRoot Windows path+ rootLen = Array.length root+ stemLen = Array.length stem+ validStemLen = countLeadingValid Windows stem+ invalidVal = fromIntegral (Array.unsafeGetIndex validStemLen stem) :: Word16++ rootEndSeps = countTrailingBy (isSeparatorWord Windows) root++ -- TBD: We are not currently validating the sharenames against disallowed+ -- file names. Apparently windows does not allow even sharenames with those+ -- names. To match against sharenames we will have to strip the separators+ -- and drive etc from the root. Or we can use the parsing routines+ -- themselves to validate.+ toUp w16 =+ if w16 < 256+ then charToWord $ toUpper (wordToChar w16)+ else w16++ -- Should we strip all space chars as in Data.Char.isSpace?+ isSpace x = x == charToWord ' '++ -- XXX instead of using a list based check, pass the array to the checker.+ -- We do not need to upcase the array, it can be done in the checker. Thus+ -- we do not need to create a new array, the original slice can be checked.+ getBaseName x =+ runIdentity+ $ Stream.toList+ $ fmap toUp+ $ Array.read+ $ Array.dropAround isSpace+ $ fst $ Array.breakEndBy_ (== extensionWord) x++ components =+ runIdentity+ . Stream.toList+ . fmap getBaseName+ . splitCompact False Windows++ invalidRootComponent =+ List.any (`List.elem` isInvalidPathComponent) (components root)+ invalidComponent =+ List.any (`List.elem` isInvalidPathComponent) (components stem)++-- | A valid root, share root or a valid path.+{-# INLINE validatePath #-}+validatePath :: (MonadThrow m, Integral a, Unbox a) => OS -> Array a -> m ()+validatePath = validatePathWith True++{-# INLINE validatePath' #-}+validatePath' :: (MonadThrow m, Integral a, Unbox a) => OS -> Array a -> m ()+validatePath' = validatePathWith False++{-# INLINE unsafeFromArray #-}+unsafeFromArray :: Array a -> Array a+unsafeFromArray = id++{-# INLINE fromArray #-}+fromArray :: forall m a. (MonadThrow m, Unbox a, Integral a) =>+ OS -> Array a -> m (Array a)+fromArray os arr = validatePath os arr >> pure arr+{-+ let arr1 = Array.unsafeCast arr :: Array a+ in validatePath os arr1 >> pure arr1+fromArray Windows arr =+ case Array.cast arr of+ Nothing ->+ throwM+ $ InvalidPath+ $ "Encoded path length " ++ show (Array.byteLength arr)+ ++ " is not a multiple of 16-bit."+ Just x -> validatePath Windows x >> pure x+-}++{-# INLINE unsafeFromChars #-}+unsafeFromChars :: (Unbox a) =>+ (Stream Identity Char -> Stream Identity a)+ -> Stream Identity Char+ -> Array a+unsafeFromChars encode s =+ -- The encoded array may be longer than the char count. We are encoding it+ -- twice, but it may still be cheaper than reallocating the array or+ -- oversizing the array.+ let n = runIdentity $ Stream.fold Fold.length (encode s)+ in Array.fromPureStreamN n (encode s)++-- XXX Writing a custom fold for parsing a Posix path may be better for+-- efficient bulk parsing when needed. We need the same code to validate a+-- Chunk where we do not need to create an array.+{-# INLINE fromChars #-}+fromChars :: (MonadThrow m, Unbox a, Integral a) =>+ OS+ -> (Stream Identity Char -> Stream Identity a)+ -> Stream Identity Char+ -> m (Array a)+fromChars os encode s =+ let arr = unsafeFromChars encode s+ in fromArray os (Array.unsafeCast arr)++{-# INLINE toChars #-}+toChars :: (Monad m, Unbox a) =>+ (Stream m a -> Stream m Char) -> Array a -> Stream m Char+toChars decode arr = decode $ Array.read arr++{-# INLINE toString #-}+toString :: Unbox a =>+ (Stream Identity a -> Stream Identity Char) -> Array a -> [Char]+toString decode = runIdentity . Stream.toList . toChars decode++------------------------------------------------------------------------------+-- Statically Verified Literals+------------------------------------------------------------------------------++-- XXX pass the quote name for errors?+mkQ :: (String -> Q Exp) -> QuasiQuoter+mkQ f =+ QuasiQuoter+ { quoteExp = f+ , quotePat = err "pattern"+ , quoteType = err "type"+ , quoteDec = err "declaration"+ }++ where++ err x _ = fail $ "QuasiQuote used as a " ++ x+ ++ ", can be used only as an expression"++------------------------------------------------------------------------------+-- Operations of Path+------------------------------------------------------------------------------++-- See also cstringLength# in GHC.CString in ghc-prim+foreign import ccall unsafe "string.h strlen" c_strlen_pinned+ :: Addr# -> IO CSize++{-# INLINE appendCStringWith #-}+appendCStringWith ::+ (Int -> IO (MutArray Word8))+ -> OS+ -> Array Word8+ -> CString+ -> IO (Array Word8)+appendCStringWith create os a b@(Ptr addrB#) = do+ let lenA = Array.length a+ lenB <- fmap fromIntegral $ c_strlen_pinned addrB#+ assertM(lenA /= 0 && lenB /= 0)+ let len = lenA + 1 + lenB+ arr <- create len+ arr1 <- MutArray.unsafeSplice arr (Array.unsafeThaw a)+ arr2 <- MutArray.unsafeSnoc arr1 (charToWord (primarySeparator os))+ arr3 :: MutArray.MutArray Word8 <-+ MutArray.unsafeAppendPtrN arr2 (castPtr b) lenB+ return (Array.unsafeFreeze arr3)++{-# INLINE appendCString #-}+appendCString :: OS -> Array Word8 -> CString -> IO (Array Word8)+appendCString = appendCStringWith MutArray.emptyOf++{-# INLINE appendCString' #-}+appendCString' :: OS -> Array Word8 -> CString -> IO (Array Word8)+appendCString' = appendCStringWith MutArray.emptyOf'++{-# INLINE doAppend #-}+doAppend :: (Unbox a, Integral a) => OS -> Array a -> Array a -> Array a+doAppend os a b = unsafePerformIO $ do+ let lenA = Array.length a+ lenB = Array.length b+ assertM(lenA /= 0 && lenB /= 0)+ let lastA = Array.unsafeGetIndexRev 0 a+ sepA = isSeparatorWord os lastA+ sepB = isSeparatorWord os (Array.unsafeGetIndex 0 b)+ let len = lenA + 1 + lenB+ arr <- MutArray.emptyOf len+ arr1 <- MutArray.unsafeSplice arr (Array.unsafeThaw a)+ arr2 <-+ if lenA /= 0+ && lenB /= 0+ && not sepA+ && not sepB+ && not (os == Windows && lastA == charToWord ':')+ then MutArray.unsafeSnoc arr1 (charToWord (primarySeparator os))+ else pure arr1+ -- Note: if the last char on the first array is ":" and first char on the+ -- second array is "/" then we cannot drop the "/". We drop only if both+ -- are separators excluding ":".+ let arrB =+ if sepA && sepB+ then snd $ Array.unsafeBreakAt 1 b+ else b+ arr3 <- MutArray.unsafeSplice arr2 (Array.unsafeThaw arrB)+ return (Array.unsafeFreeze arr3)++{-# INLINE withAppendCheck #-}+withAppendCheck :: (Unbox b, Integral b) =>+ OS -> (Array b -> String) -> Array b -> a -> a+withAppendCheck os toStr arr f =+ if isRooted os arr+ then error $ "append: cannot append a rooted path " ++ toStr arr+ else f++{-# INLINE unsafeAppend #-}+unsafeAppend :: (Unbox a, Integral a) =>+ OS -> (Array a -> String) -> Array a -> Array a -> Array a+unsafeAppend os _toStr = doAppend os++{-# INLINE append #-}+append :: (Unbox a, Integral a) =>+ OS -> (Array a -> String) -> Array a -> Array a -> Array a+append os toStr a b = withAppendCheck os toStr b (doAppend os a b)++{-# INLINE append' #-}+append' :: (Unbox a, Integral a) =>+ OS -> (Array a -> String) -> Array a -> Array a -> Array a+append' os toStr a b =+ let hasSep = countTrailingBy (isSeparatorWord os) a > 0+ hasColon =+ os == Windows+ && Array.getIndexRev 0 a == Just (charToWord ':')+ in if hasSep || hasColon+ then withAppendCheck os toStr b (doAppend os a b)+ else error+ $ "append': first path must be dir like i.e. must have a "+ ++ "trailing separator or colon on windows: " ++ toStr a++-- XXX MonadIO?++-- | Join paths by path separator. Does not check if the paths being appended+-- are rooted or path segments. Note that splitting and joining may not give+-- exactly the original path but an equivalent, normalized path.+{-# INLINE unsafeJoinPaths #-}+unsafeJoinPaths+ :: (Unbox a, Integral a, MonadIO m)+ => OS -> Stream m (Array a) -> m (Array a)+unsafeJoinPaths os =+ -- XXX This can be implemented more efficiently using an Array intersperse+ -- operation. Which can be implemented by directly copying arrays rather+ -- than converting them to stream first. Also fromStreamN would be more+ -- efficient if we have to use streams.+ -- XXX We can remove leading and trailing separators first, if any, except+ -- the leading separator from the first path. But it is not necessary.+ -- Instead we can avoid adding a separator if it is already present.+ Array.fromStream . Array.concatSepBy (charToWord $ primarySeparator os)++------------------------------------------------------------------------------+-- Equality+------------------------------------------------------------------------------++eqPathBytes :: Array a -> Array a -> Bool+eqPathBytes = Array.byteEq++-- On posix macOs can have case insensitive comparison. On Windows also+-- case sensitive behavior may depend on the file system being used.++-- Use eq prefix?++-- | Options for path comparison operation. By default path comparison uses a+-- strict criteria for equality. The following options are provided to+-- control the strictness.+--+-- The default configuration is as follows:+--+-- >>> :{+-- defaultMod = ignoreTrailingSeparators False+-- . ignoreCase False+-- . allowRelativeEquality False+-- :}+--+data EqCfg =+ EqCfg+ { _ignoreTrailingSeparators :: Bool -- ^ Allows "x\/" == "x"+ , _ignoreCase :: Bool -- ^ Allows "x" == \"X\"+ , _allowRelativeEquality :: Bool+ -- ^ A leading dot is ignored, thus ".\/x" == ".\/x" and ".\/x" == "x".+ -- On Windows allows "\/x" == \/x" and "C:x == C:x"++ -- , resolveParentReferences -- "x\/..\/y" == "y"+ -- , noIgnoreRedundantSeparators -- "x\/\/y" \/= "x\/y"+ -- , noIgnoreRedundantDot -- "x\/.\/" \/= "x"+ }++data PosixRoot = PosixRootAbs | PosixRootRel deriving Eq++data WindowsRoot =+ WindowsRootPosix -- /x or ./x+ | WindowsRootNonPosix -- C:... or \\...+ deriving Eq++-- | Change to upper case and replace separators by primary separator+{-# INLINE normalizeCaseAndSeparators #-}+normalizeCaseAndSeparators :: Monad m => Array Word16 -> Stream m Char+normalizeCaseAndSeparators =+ fmap toUpper+ . Unicode.decodeUtf16le'+ . fmap toDefaultSeparator+ . Array.read++{-# INLINE normalizeCaseWith #-}+normalizeCaseWith :: (Monad m, Unbox a) =>+ (Stream m a -> Stream m Char) -> Array a -> Stream m Char+normalizeCaseWith decoder =+ fmap toUpper+ . decoder+ . Array.read++eqWindowsRootStrict :: (Unbox a, Integral a) =>+ Bool -> Array a -> Array a -> Bool+eqWindowsRootStrict ignCase a b =+ let f = normalizeCaseAndSeparators+ in if ignCase+ then+ -- XXX We probably do not want to equate UNC with UnC etc.+ runIdentity+ $ Stream.eqBy (==)+ (f $ Array.unsafeCast a) (f $ Array.unsafeCast b)+ else+ runIdentity+ $ Stream.eqBy (==)+ (fmap toDefaultSeparator $ Array.read a)+ (fmap toDefaultSeparator $ Array.read b)++{-# INLINE eqRootStrict #-}+eqRootStrict :: (Unbox a, Integral a) =>+ Bool -> OS -> Array a -> Array a -> Bool+eqRootStrict _ Posix a b =+ -- a can be "/" and b can be "//"+ -- We call this only when the roots are either absolute or null.+ Array.null a == Array.null b+eqRootStrict ignCase Windows a b = eqWindowsRootStrict ignCase a b++-- | Compare Posix roots or Windows roots without a drive or share name.+{-# INLINE eqPosixRootLax #-}+eqPosixRootLax :: (Unbox a, Integral a) => Array a -> Array a -> Bool+eqPosixRootLax a b = getRoot a == getRoot b++ where++ -- Can only be either "", '.', './' or '/' (or Windows separators)+ getRoot arr =+ if Array.null arr || unsafeIndexChar 0 arr == '.'+ then PosixRootRel+ else PosixRootAbs++{-# INLINABLE eqRootLax #-}+eqRootLax :: (Unbox a, Integral a) => Bool -> OS -> Array a -> Array a -> Bool+eqRootLax _ Posix a b = eqPosixRootLax a b+eqRootLax ignCase Windows a b =+ let aType = getRootType a+ bType = getRootType b+ in aType == bType+ && (+ (aType == WindowsRootPosix && eqPosixRootLax a b)+ || eqWindowsRootStrict ignCase a b+ )++ where++ getRootType arr =+ if isAbsoluteUNC arr || hasDrive arr+ then WindowsRootNonPosix+ else WindowsRootPosix++{-# INLINE eqComponentsWith #-}+eqComponentsWith :: (Unbox a, Integral a) =>+ EqCfg+ -> (Stream Identity a -> Stream Identity Char)+ -> OS+ -> Array a+ -> Array a+ -> Bool+eqComponentsWith EqCfg{..} decoder os a b =+ if _ignoreCase+ then+ let streamEq x y = runIdentity $ Stream.eqBy (==) x y+ toComponents = fmap (normalizeCaseWith decoder) . splitter os+ -- XXX check perf/fusion+ in runIdentity+ $ Stream.eqBy streamEq (toComponents a) (toComponents b)+ else+ runIdentity+ $ Stream.eqBy+ Array.byteEq (splitter os a) (splitter os b)+ where+ splitter = splitPathUsing False _allowRelativeEquality++-- XXX can we do something like SpecConstr for such functions e.g. without+-- inlining the function we can use two copies one for _allowRelativeEquality+-- True and other for False and so on for other values of PathEq.++{-# INLINE eqPath #-}+eqPath :: (Unbox a, Integral a) =>+ (Stream Identity a -> Stream Identity Char)+ -> OS -> EqCfg -> Array a -> Array a -> Bool+eqPath decoder os eqCfg@(EqCfg{..}) a b =+ let (rootA, stemA) = splitRoot os a+ (rootB, stemB) = splitRoot os b++ eqRelative =+ if _allowRelativeEquality+ then eqRootLax _ignoreCase os rootA rootB+ else (not (isRootRelative os rootA)+ && not (isRootRelative os rootB))+ && eqRootStrict _ignoreCase os rootA rootB++ -- XXX If one ends in a "." and the other ends in ./ (and same for ".."+ -- and "../") then they can be equal. We can append a slash in these two+ -- cases before comparing.+ eqTrailingSep =+ _ignoreTrailingSeparators+ || hasTrailingSeparator os a == hasTrailingSeparator os b++ in+ eqRelative+ && eqTrailingSep+ && eqComponentsWith eqCfg decoder os stemA stemB
+ src/Streamly/Internal/FileSystem/Path/Node.hs view
@@ -0,0 +1,20 @@+-- |+-- Module : Streamly.Internal.FileSystem.Path.Node+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Portability : GHC++#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+#define OS_PATH WindowsPath+#else+#define OS_PATH PosixPath+#endif++module Streamly.Internal.FileSystem.Path.Node+ (+ module Streamly.Internal.FileSystem.OS_PATH.Node+ )+where++import Streamly.Internal.FileSystem.OS_PATH.Node
+ src/Streamly/Internal/FileSystem/Path/Seg.hs view
@@ -0,0 +1,20 @@+-- |+-- Module : Streamly.Internal.FileSystem.Path.Seg+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Portability : GHC++#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+#define OS_PATH WindowsPath+#else+#define OS_PATH PosixPath+#endif++module Streamly.Internal.FileSystem.Path.Seg+ (+ module Streamly.Internal.FileSystem.OS_PATH.Seg+ )+where++import Streamly.Internal.FileSystem.OS_PATH.Seg
+ src/Streamly/Internal/FileSystem/Path/SegNode.hs view
@@ -0,0 +1,20 @@+-- |+-- Module : Streamly.Internal.FileSystem.Path.SegNode+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Portability : GHC++#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+#define OS_PATH WindowsPath+#else+#define OS_PATH PosixPath+#endif++module Streamly.Internal.FileSystem.Path.SegNode+ (+ module Streamly.Internal.FileSystem.OS_PATH.SegNode+ )+where++import Streamly.Internal.FileSystem.OS_PATH.SegNode
+ src/Streamly/Internal/FileSystem/Posix/Errno.hs view
@@ -0,0 +1,60 @@+-- |+-- Module : Streamly.Internal.FileSystem.Posix.Errno+-- Copyright : (c) 2024 Composewell Technologies+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Portability : GHC++module Streamly.Internal.FileSystem.Posix.Errno+ (+#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)+ throwErrnoPath+ , throwErrnoPathIfRetry+ , throwErrnoPathIfNullRetry+ , throwErrnoPathIfMinus1Retry+#endif+ )+where++#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)+import Foreign (Ptr, nullPtr)+import Foreign.C (getErrno, eINTR)+import Foreign.C.Error (errnoToIOError)+import Streamly.Internal.FileSystem.PosixPath (PosixPath(..))++import qualified Streamly.Internal.FileSystem.PosixPath as Path++-------------------------------------------------------------------------------+-- From unix+-------------------------------------------------------------------------------++-- | Same as 'throwErrno', but exceptions include the given path when+-- appropriate.+--+throwErrnoPath :: String -> PosixPath -> IO a+throwErrnoPath loc path =+ do+ errno <- getErrno+ -- XXX toString uses strict decoding, may fail+ ioError (errnoToIOError loc errno Nothing (Just (Path.toString path)))++throwErrnoPathIfRetry :: (a -> Bool) -> String -> PosixPath -> IO a -> IO a+throwErrnoPathIfRetry pr loc rpath f =+ do+ res <- f+ if pr res+ then do+ err <- getErrno+ if err == eINTR+ then throwErrnoPathIfRetry pr loc rpath f+ else throwErrnoPath loc rpath+ else return res++throwErrnoPathIfNullRetry :: String -> PosixPath -> IO (Ptr a) -> IO (Ptr a)+throwErrnoPathIfNullRetry = throwErrnoPathIfRetry (== nullPtr)++throwErrnoPathIfMinus1Retry :: (Eq a, Num a) =>+ String -> PosixPath -> IO a -> IO a+throwErrnoPathIfMinus1Retry = throwErrnoPathIfRetry (== -1)+#endif
+ src/Streamly/Internal/FileSystem/Posix/File.hsc view
@@ -0,0 +1,316 @@+module Streamly.Internal.FileSystem.Posix.File+ (+#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)++ -- * File open flags+ OpenFlags (..)+ , defaultOpenFlags++ -- * File status flags+ , setAppend+ , setNonBlock+ , setSync++ -- * File creation flags+ , setCloExec+ , setDirectory+ , setExcl+ , setNoCtty+ , setNoFollow+ -- setTmpFile+ , setTrunc++ -- * File create mode+ , defaultCreateMode++ -- ** User Permissions+ , setUr+ , setUw+ , setUx++ , clrUr+ , clrUw+ , clrUx++ -- ** Group Permissions+ , setGr+ , setGw+ , setGx++ , clrGr+ , clrGw+ , clrGx++ -- ** Other Permissions+ , setOr+ , setOw+ , setOx++ , clrOr+ , clrOw+ , clrOx++ -- ** Status bits+ , setSuid+ , setSgid+ , setSticky++ , clrSuid+ , clrSgid+ , clrSticky++ -- * Fd based Low Level+ , openAt+ , close++ -- * Handle based+ , openFile+ , withFile+ , openBinaryFile+ , withBinaryFile++ -- Re-exported+ , Fd+#endif+ ) where++#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)++-------------------------------------------------------------------------------+-- Imports+-------------------------------------------------------------------------------++import Data.Bits ((.|.), (.&.), complement)+import Foreign.C.Error (throwErrnoIfMinus1_)+import Foreign.C.String (CString)+import Foreign.C.Types (CInt(..))+import GHC.IO.Handle.FD (fdToHandle)+import Streamly.Internal.FileSystem.Posix.Errno (throwErrnoPathIfMinus1Retry)+import Streamly.Internal.FileSystem.PosixPath (PosixPath)+import System.IO (IOMode(..), Handle)+import System.Posix.Types (Fd(..), CMode(..))++import qualified Streamly.Internal.FileSystem.File.Common as File+import qualified Streamly.Internal.FileSystem.PosixPath as Path++-- We want to remain close to the Posix C API. A function based API to set and+-- clear the modes is simple, type safe and directly mirrors the C API. It does+-- not require explicit mapping from Haskell ADT to C types, we can dirctly+-- manipulate the C type.++#include <fcntl.h>++-------------------------------------------------------------------------------+-- Create mode+-------------------------------------------------------------------------------++-- | Open flags, see posix open system call man page.+newtype FileMode = FileMode CMode++##define MK_MODE_API(name1,name2,x) \+{-# INLINE name1 #-}; \+name1 :: FileMode -> FileMode; \+name1 (FileMode mode) = FileMode (x .|. mode); \+{-# INLINE name2 #-}; \+name2 :: FileMode -> FileMode; \+name2 (FileMode mode) = FileMode (x .&. complement mode)++{-+#define S_ISUID 0004000+#define S_ISGID 0002000+#define S_ISVTX 0001000++#define S_IRWXU 00700+#define S_IRUSR 00400+#define S_IWUSR 00200+#define S_IXUSR 00100++#define S_IRWXG 00070+#define S_IRGRP 00040+#define S_IWGRP 00020+#define S_IXGRP 00010++#define S_IRWXO 00007+#define S_IROTH 00004+#define S_IWOTH 00002+#define S_IXOTH 00001++#define AT_FDCWD (-100)+-}++MK_MODE_API(setSuid,clrSuid,S_ISUID)+MK_MODE_API(setSgid,clrSgid,S_ISGID)+MK_MODE_API(setSticky,clrSticky,S_ISVTX)++-- MK_MODE_API(setUrwx,clrUrwx,S_IRWXU)+MK_MODE_API(setUr,clrUr,S_IRUSR)+MK_MODE_API(setUw,clrUw,S_IWUSR)+MK_MODE_API(setUx,clrUx,S_IXUSR)++-- MK_MODE_API(setGrwx,clrGrwx,S_IRWXU)+MK_MODE_API(setGr,clrGr,S_IRUSR)+MK_MODE_API(setGw,clrGw,S_IWUSR)+MK_MODE_API(setGx,clrGx,S_IXUSR)++-- MK_MODE_API(setOrwx,clrOrwx,S_IRWXU)+MK_MODE_API(setOr,clrOr,S_IRUSR)+MK_MODE_API(setOw,clrOw,S_IWUSR)+MK_MODE_API(setOx,clrOx,S_IXUSR)++-- Uses the same default mode as openFileWith in base+defaultCreateMode :: FileMode+defaultCreateMode = FileMode 0o666++-------------------------------------------------------------------------------+-- Open Flags+-------------------------------------------------------------------------------++-- | Open flags, see posix open system call man page.+newtype OpenFlags = OpenFlags CInt++##define MK_FLAG_API(name,x) \+{-# INLINE name #-}; \+name :: OpenFlags -> OpenFlags; \+name (OpenFlags flags) = OpenFlags (flags .|. x)++-- foreign import ccall unsafe "HsBase.h __hscore_o_rdonly" o_RDONLY :: CInt+-- These affect the first two bits in flags.+MK_FLAG_API(setReadOnly,#{const O_RDONLY})+MK_FLAG_API(setWriteOnly,#{const O_WRONLY})+MK_FLAG_API(setReadWrite,#{const O_RDWR})++##define MK_BOOL_FLAG_API(name,x) \+{-# INLINE name #-}; \+name :: Bool -> OpenFlags -> OpenFlags; \+name True (OpenFlags flags) = OpenFlags (flags .|. x); \+name False (OpenFlags flags) = OpenFlags (flags .&. complement x)++-- setCreat is internal only, do not export this. This is automatically set+-- when create mode is passed, otherwise cleared.+MK_BOOL_FLAG_API(setCreat,#{const O_CREAT})++MK_BOOL_FLAG_API(setExcl,#{const O_EXCL})+MK_BOOL_FLAG_API(setNoCtty,#{const O_NOCTTY})+MK_BOOL_FLAG_API(setTrunc,#{const O_TRUNC})+MK_BOOL_FLAG_API(setAppend,#{const O_APPEND})+MK_BOOL_FLAG_API(setNonBlock,#{const O_NONBLOCK})+MK_BOOL_FLAG_API(setDirectory,#{const O_DIRECTORY})+MK_BOOL_FLAG_API(setNoFollow,#{const O_NOFOLLOW})+MK_BOOL_FLAG_API(setCloExec,#{const O_CLOEXEC})+MK_BOOL_FLAG_API(setSync,#{const O_SYNC})++-- | Default values for the 'OpenFlags'.+--+-- By default a 0 value is used, no flag is set. See the open system call man+-- page.+defaultOpenFlags :: OpenFlags+defaultOpenFlags = OpenFlags 0++-------------------------------------------------------------------------------+-- Low level (fd returning) file opening APIs+-------------------------------------------------------------------------------++-- XXX Should we use interruptible open as in base openFile?+foreign import capi unsafe "fcntl.h openat"+ c_openat :: CInt -> CString -> CInt -> CMode -> IO CInt++-- | Open and optionally create (when create mode is specified) a file relative+-- to an optional directory file descriptor. If directory fd is not specified+-- then opens relative to the current directory.+-- {-# INLINE openAtCString #-}+openAtCString ::+ Maybe Fd -- ^ Optional directory file descriptor+ -> CString -- ^ Pathname to open+ -> OpenFlags -- ^ Append, exclusive, etc.+ -> Maybe FileMode -- ^ Create mode+ -> IO Fd+openAtCString fdMay path flags cmode =+ Fd <$> c_openat c_fd path flags1 mode++ where++ c_fd = maybe (#{const AT_FDCWD}) (\ (Fd fd) -> fd) fdMay+ FileMode mode = maybe defaultCreateMode id cmode+ OpenFlags flags1 = maybe flags (\_ -> setCreat True flags) cmode++-- | Open a file relative to an optional directory file descriptor.+--+-- Note: In Haskell, using an fd directly for IO may be problematic as blocking+-- file system operations on the file might block the capability and GC for+-- "unsafe" calls. "safe" calls may be more expensive. Also, you may have to+-- synchronize concurrent access via multiple threads.+--+{-# INLINE openAt #-}+openAt ::+ Maybe Fd -- ^ Optional directory file descriptor+ -> PosixPath -- ^ Pathname to open+ -> OpenFlags -- ^ Append, exclusive, truncate, etc.+ -> Maybe FileMode -- ^ Create mode+ -> IO Fd+openAt fdMay path flags cmode =+ Path.asCString path $ \cstr -> do+ throwErrnoPathIfMinus1Retry "openAt" path+ $ openAtCString fdMay cstr flags cmode+++-- | Open a regular file, return an Fd.+--+-- Sets O_NOCTTY, O_NONBLOCK flags to be compatible with the base openFile+-- behavior. O_NOCTTY affects opening of terminal special files and O_NONBLOCK+-- affects fifo special files, and mandatory locking.+--+openFileFdWith :: OpenFlags -> PosixPath -> IOMode -> IO Fd+openFileFdWith oflags path iomode = do+ case iomode of+ ReadMode -> open1 (setReadOnly oflags1) Nothing+ WriteMode ->+ open1 (setWriteOnly oflags1) (Just defaultCreateMode)+ AppendMode ->+ open1+ ((setAppend True . setWriteOnly) oflags1)+ (Just defaultCreateMode)+ ReadWriteMode ->+ open1 (setReadWrite oflags) (Just defaultCreateMode)++ where++ oflags1 = setNoCtty True $ setNonBlock True oflags+ open1 = openAt Nothing path++openFileFd :: PosixPath -> IOMode -> IO Fd+openFileFd = openFileFdWith defaultOpenFlags++foreign import ccall unsafe "unistd.h close"+ c_close :: CInt -> IO CInt++close :: Fd -> IO ()+close (Fd fd) = throwErrnoIfMinus1_ ("close " ++ show fd) (c_close fd)++-------------------------------------------------------------------------------+-- base openFile compatible, Handle returning, APIs+-------------------------------------------------------------------------------++-- | Open a regular file, return a Handle. The file is locked, the Handle is+-- NOT set up to close the file on garbage collection.+{-# INLINE openFileHandle #-}+openFileHandle :: PosixPath -> IOMode -> IO Handle+openFileHandle p x = openFileFd p x >>= fdToHandle . fromIntegral++-- | Like openFile in base package but using Path instead of FilePath.+-- Use hSetBinaryMode on the handle if you want to use binary mode.+openFile :: PosixPath -> IOMode -> IO Handle+openFile = File.openFile False openFileHandle++-- | Like withFile in base package but using Path instead of FilePath.+-- Use hSetBinaryMode on the handle if you want to use binary mode.+withFile :: PosixPath -> IOMode -> (Handle -> IO r) -> IO r+withFile = File.withFile False openFileHandle++-- | Like openBinaryFile in base package but using Path instead of FilePath.+openBinaryFile :: PosixPath -> IOMode -> IO Handle+openBinaryFile = File.openFile True openFileHandle++-- | Like withBinaryFile in base package but using Path instead of FilePath.+withBinaryFile :: PosixPath -> IOMode -> (Handle -> IO r) -> IO r+withBinaryFile = File.withFile True openFileHandle+#endif
+ src/Streamly/Internal/FileSystem/Posix/ReadDir.hsc view
@@ -0,0 +1,931 @@+-- |+-- Module : Streamly.Internal.FileSystem.Posix.ReadDir+-- Copyright : (c) 2024 Composewell Technologies+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Portability : GHC++module Streamly.Internal.FileSystem.Posix.ReadDir+ (+#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)+ readScanWith_+ , readScanWith+ , readPlusScanWith++ , DirStream (..)+ , openDirStream+ , openDirStreamCString+ , closeDirStream+ , readDirStreamEither+ , readEitherChunks+ , readEitherByteChunks+ , readEitherByteChunksAt+ , eitherReader+ , reader+#endif+ )+where++#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)+import Control.Monad (unless)+import Control.Monad.Catch (MonadCatch)+import Control.Monad.IO.Class (MonadIO(..))+import Data.Char (ord)+import Foreign+ ( Ptr, Word8, nullPtr, peek, peekByteOff, castPtr, plusPtr, (.&.)+ , allocaBytes+ )+import Foreign.C+ ( resetErrno, throwErrno, throwErrnoIfMinus1Retry_, throwErrnoIfNullRetry+ , Errno(..), getErrno, eINTR, eNOENT, eACCES, eLOOP+ , CInt(..), CString, CChar, CSize(..)+ )+import Foreign.Storable (poke)+import Fusion.Plugin.Types (Fuse(..))+import Streamly.Internal.Data.Array (Array(..))+import Streamly.Internal.Data.MutByteArray (MutByteArray)+import Streamly.Internal.Data.Scanl (Scanl)+import Streamly.Internal.Data.Stream (Stream(..), Step(..))+import Streamly.Internal.Data.Unfold.Type (Unfold(..))+import Streamly.Internal.FileSystem.Path (Path)+import Streamly.Internal.FileSystem.Posix.Errno (throwErrnoPathIfNullRetry)+import Streamly.Internal.FileSystem.Posix.File+ (defaultOpenFlags, openAt, close)+import Streamly.Internal.FileSystem.PosixPath (PosixPath(..))+import System.Posix.Types (Fd(..), CMode)++import qualified Streamly.Internal.Data.Array as Array+import qualified Streamly.Internal.Data.MutByteArray as MutByteArray+import qualified Streamly.Internal.Data.Unfold as UF (bracketIO)+import qualified Streamly.Internal.FileSystem.Path.Common as PathC+import qualified Streamly.Internal.FileSystem.PosixPath as Path++import Streamly.Internal.FileSystem.DirOptions++#include <dirent.h>+#include <sys/stat.h>++-------------------------------------------------------------------------------++data {-# CTYPE "DIR" #-} CDir+data {-# CTYPE "struct dirent" #-} CDirent+data {-# CTYPE "struct stat" #-} CStat++newtype DirStream = DirStream (Ptr CDir)++-- | Minimal read without any metadata.+{-# INLINE readScanWith_ #-}+readScanWith_ :: -- (MonadIO m, MonadCatch m) =>+ Scanl m (Path, CString) a+ -> (ReadOptions -> ReadOptions)+ -> Path+ -> Stream m a+readScanWith_ = undefined++-- | Read with essential metadata. The scan takes the parent dir, the child+-- name, the child metadata and produces an output. The scan can do filtering,+-- formatting of the output, colorizing the output etc.+--+-- The options are to ignore errors encountered when reading a path, turn the+-- errors into a nil stream instead.+{-# INLINE readScanWith #-}+readScanWith :: -- (MonadIO m, MonadCatch m) =>+ Scanl m (Path, CString, Ptr CDirent) a+ -> (ReadOptions -> ReadOptions)+ -> Path+ -> Stream m a+readScanWith = undefined++-- NOTE: See https://www.manpagez.com/man/2/getattrlistbulk/ for BSD/macOS.++-- | Read with full metadata.+{-# INLINE readPlusScanWith #-}+readPlusScanWith :: -- (MonadIO m, MonadCatch m) =>+ Scanl m (Path, CString, Ptr CStat) a+ -> (ReadOptions -> ReadOptions)+ -> Path+ -> Stream m a+readPlusScanWith = undefined++-------------------------------------------------------------------------------+-- readdir operations+-------------------------------------------------------------------------------++-- XXX Marking the calls "safe" has significant perf impact because it runs on+-- a separate OS thread. "unsafe" is faster but can block the GC if the system+-- call blocks. The effect could be signifcant if the file system is on NFS. Is+-- it possible to have a faster safe - where we know the function is safe but+-- we run it on the current thread, and if it blocks for longer we can snatch+-- the capability and enable GC?+--+-- IMPORTANT NOTE: Use capi FFI for all readdir APIs. This is required at+-- least on macOS for correctness. We saw random directory entries when ccall+-- was used on macOS 15.3. Looks like it was picking the wrong version of+-- dirent structure. Did not see the problem in CIs on macOS 14.7.2 though.+foreign import capi unsafe "closedir"+ c_closedir :: Ptr CDir -> IO CInt++foreign import capi unsafe "dirent.h opendir"+ c_opendir :: CString -> IO (Ptr CDir)++foreign import capi unsafe "dirent.h fdopendir"+ c_fdopendir :: CInt -> IO (Ptr CDir)++-- XXX The "unix" package uses a wrapper over readdir __hscore_readdir (see+-- cbits/HsUnix.c in unix package) which uses readdir_r in some cases where+-- readdir is not known to be re-entrant. We are not doing that here. We are+-- assuming that readdir is re-entrant which may not be the case on some old+-- unix systems.+foreign import capi unsafe "dirent.h readdir"+ c_readdir :: Ptr CDir -> IO (Ptr CDirent)++--------------------------------------------------------------------------------+-- Stat+--------------------------------------------------------------------------------++foreign import capi unsafe "sys/stat.h lstat"+ c_lstat :: CString -> Ptr CStat -> IO CInt++foreign import capi unsafe "sys/stat.h stat"+ c_stat :: CString -> Ptr CStat -> IO CInt++s_IFMT :: CMode+s_IFMT = #{const S_IFMT}++s_IFDIR :: CMode+s_IFDIR = #{const S_IFDIR}++{-+s_IFREG :: CMode+s_IFREG = #{const S_IFREG}++s_IFLNK :: CMode+s_IFLNK = #{const S_IFLNK}+-}++-- NOTE: Using fstatat with a dirfd and relative path would be faster.+stat :: Bool -> CString -> IO (Either Errno CMode)+stat followSym cstr =+ allocaBytes #{size struct stat} $ \p_stat -> do+ resetErrno+ result <-+ if followSym+ then c_stat cstr p_stat+ else c_lstat cstr p_stat+ if result /= 0+ then do+ errno <- getErrno+ if errno == eINTR+ then stat followSym cstr+ else pure $ Left errno+ else do+ mode <- #{peek struct stat, st_mode} p_stat+ pure $ Right (mode .&. s_IFMT)++--------------------------------------------------------------------------------+-- Functions+--------------------------------------------------------------------------------++-- | The CString must be pinned.+{-# INLINE openDirStreamCString #-}+openDirStreamCString :: CString -> IO DirStream+openDirStreamCString s = do+ -- XXX we do not decode the path here, just print it as cstring+ -- XXX pass lazy concat of "openDirStream: " ++ s+ dirp <- throwErrnoIfNullRetry "openDirStream" $ c_opendir s+ return (DirStream dirp)++-- XXX Path is not null terminated therefore we need to make a copy even if the+-- array is pinned.+-- {-# INLINE openDirStream #-}+openDirStream :: PosixPath -> IO DirStream+openDirStream p =+ Array.asCStringUnsafe (Path.toArray p) $ \s -> do+ -- openDirStreamCString s+ dirp <- throwErrnoPathIfNullRetry "openDirStream" p $ c_opendir s+ return (DirStream dirp)++-- | Note that the supplied Fd is used by DirStream and when we close the+-- DirStream the fd will be closed.+openDirStreamAt :: Fd -> PosixPath -> IO DirStream+openDirStreamAt fd p = do+ -- XXX can pass O_DIRECTORY here, is O_NONBLOCK useful for dirs?+ -- Note this fd is not automatically closed, we have to take care of+ -- exceptions and closing the fd.+ fd1 <- openAt (Just fd) p defaultOpenFlags Nothing+ -- liftIO $ putStrLn $ "opened: " ++ show fd1+ dirp <- throwErrnoPathIfNullRetry "openDirStreamAt" p+ $ c_fdopendir (fromIntegral fd1)+ -- XXX can we somehow clone fd1 instead of opening again?+ return (DirStream dirp)++-- | @closeDirStream dp@ calls @closedir@ to close+-- the directory stream @dp@.+closeDirStream :: DirStream -> IO ()+closeDirStream (DirStream dirp) = do+ throwErrnoIfMinus1Retry_ "closeDirStream" (c_closedir dirp)++-------------------------------------------------------------------------------+-- determining filetype+-------------------------------------------------------------------------------++isMetaDir :: Ptr CChar -> IO Bool+isMetaDir dname = do+ -- XXX Assuming an encoding that maps "." to ".", this is true for+ -- UTF8.+ -- Load as soon as possible to optimize memory accesses+ c1 <- peek dname+ c2 :: Word8 <- peekByteOff dname 1+ if (c1 /= fromIntegral (ord '.'))+ then return False+ else do+ if (c2 == 0)+ then return True+ else do+ if (c2 /= fromIntegral (ord '.'))+ then return False+ else do+ c3 :: Word8 <- peekByteOff dname 2+ if (c3 == 0)+ then return True+ else return False++data EntryType = EntryIsDir | EntryIsNotDir | EntryIgnored++{-# NOINLINE statEntryType #-}+statEntryType+ :: ReadOptions -> PosixPath -> Ptr CChar -> IO EntryType+statEntryType conf parent dname = do+ -- XXX We can create a pinned array right here since the next call pins+ -- it anyway.+ path <- appendCString parent dname+ Array.asCStringUnsafe (Path.toArray path) $ \cStr -> do+ res <- stat (_followSymlinks conf) cStr+ case res of+ Right mode -> pure $+ if (mode == s_IFDIR)+ then EntryIsDir+ else EntryIsNotDir+ Left errno -> do+ if errno == eNOENT+ then unless (_ignoreENOENT conf) $+ throwErrno (errMsg path)+ else if errno == eACCES+ then unless (_ignoreEACCESS conf) $+ throwErrno (errMsg path)+ else if errno == eLOOP+ then unless (_ignoreELOOP conf) $+ throwErrno (errMsg path)+ else throwErrno (errMsg path)+ pure $ EntryIgnored+ where++ errMsg path =+ let pathStr = Path.toString_ path+ in "statEntryType: " ++ pathStr++-- | Checks if dname is a directory, not dir or should be ignored.+{-# INLINE getEntryType #-}+getEntryType+ :: ReadOptions+ -> PosixPath -> Ptr CChar -> #{type unsigned char} -> IO EntryType+getEntryType conf parent dname dtype = do+ let needStat =+#ifdef FORCE_LSTAT_READDIR+ True+#else+ (dtype == (#const DT_LNK) && _followSymlinks conf)+ || dtype == #const DT_UNKNOWN+#endif++ if dtype /= (#const DT_DIR) && not needStat+ then pure EntryIsNotDir+ else do+ isMeta <- liftIO $ isMetaDir dname+ if isMeta+ then pure EntryIgnored+ else if dtype == (#const DT_DIR)+ then pure EntryIsDir+ else statEntryType conf parent dname++-------------------------------------------------------------------------------+-- streaming reads+-------------------------------------------------------------------------------++-- XXX We can use getdents64 directly so that we can use array slices from the+-- same buffer that we passed to the OS. That way we can also avoid any+-- overhead of bracket.+-- XXX Make this as Unfold to avoid returning Maybe+-- XXX Or NOINLINE some parts and inline the rest to fuse it+-- {-# INLINE readDirStreamEither #-}+readDirStreamEither ::+ -- DirStream -> IO (Either (Rel (Dir Path)) (Rel (File Path)))+ (ReadOptions -> ReadOptions) ->+ (PosixPath, DirStream) -> IO (Maybe (Either PosixPath PosixPath))+readDirStreamEither confMod (curdir, (DirStream dirp)) = loop++ where++ conf = confMod defaultReadOptions++ -- mkPath :: IsPath (Rel (a Path)) => Array Word8 -> Rel (a Path)+ -- {-# INLINE mkPath #-}+ mkPath :: Array Word8 -> PosixPath+ mkPath = Path.unsafeFromArray++ loop = do+ resetErrno+ ptr <- c_readdir dirp+ if (ptr /= nullPtr)+ then do+ let dname = #{ptr struct dirent, d_name} ptr+ dtype :: #{type unsigned char} <- #{peek struct dirent, d_type} ptr+ -- dreclen :: #{type unsigned short} <- #{peek struct dirent, d_reclen} ptr+ -- It is possible to find the name length using dreclen and then use+ -- fromPtrN, but it is not straightforward because the reclen is+ -- padded to 8-byte boundary.+ name <- Array.fromCString (castPtr dname)+ etype <- getEntryType conf curdir dname dtype+ case etype of+ EntryIsDir -> return (Just (Left (mkPath name)))+ EntryIsNotDir -> return (Just (Right (mkPath name)))+ EntryIgnored -> loop+ else do+ errno <- getErrno+ if (errno == eINTR)+ then loop+ else do+ let (Errno n) = errno+ if (n == 0)+ -- then return (Left (mkPath (Array.fromList [46])))+ then return Nothing+ else throwErrno "readDirStreamEither"++-- XXX We can make this code common with windows, the path argument would be+-- redundant for windows case though.+{-# INLINE streamEitherReader #-}+streamEitherReader :: MonadIO m =>+ (ReadOptions -> ReadOptions) ->+ Unfold m (PosixPath, DirStream) (Either Path Path)+streamEitherReader confMod = Unfold step return+ where++ step s = do+ r <- liftIO $ readDirStreamEither confMod s+ case r of+ Nothing -> return Stop+ Just x -> return $ Yield x s++{-# INLINE streamReader #-}+streamReader :: MonadIO m => Unfold m (PosixPath, DirStream) Path+streamReader = fmap (either id id) (streamEitherReader id)++{-# INLINE before #-}+before :: PosixPath -> IO (PosixPath, DirStream)+before parent = (parent,) <$> openDirStream parent++{-# INLINE after #-}+after :: (PosixPath, DirStream) -> IO ()+after (_, dirStream) = closeDirStream dirStream++-- | Read a directory emitting a stream with names of the children. Filter out+-- "." and ".." entries.+--+-- /Internal/+--+{-# INLINE reader #-}+reader :: (MonadIO m, MonadCatch m) => Unfold m Path Path+reader =+ -- XXX Instead of using bracketIO for each iteration of the loop we should+ -- instead yield a buffer of dir entries in each iteration and then use an+ -- unfold and concat to flatten those entries. That should improve the+ -- performance.+ UF.bracketIO before after (streamReader)++-- | Read directories as Left and files as Right. Filter out "." and ".."+-- entries.+--+-- /Internal/+--+{-# INLINE eitherReader #-}+eitherReader :: (MonadIO m, MonadCatch m) =>+ (ReadOptions -> ReadOptions) -> Unfold m Path (Either Path Path)+eitherReader confMod =+ -- XXX The measured overhead of bracketIO is not noticeable, if it turns+ -- out to be a problem for small filenames we can use getdents64 to use+ -- chunked read to avoid the overhead.+ UF.bracketIO before after (streamEitherReader confMod)++{-# INLINE appendCString #-}+appendCString :: PosixPath -> CString -> IO PosixPath+appendCString (PosixPath a) b = do+ arr <- PathC.appendCString PathC.Posix a b+ pure $ PosixPath arr++{-# ANN type ChunkStreamState Fuse #-}+data ChunkStreamState =+ ChunkStreamInit [PosixPath] [PosixPath] Int [PosixPath] Int+ | ChunkStreamLoop+ PosixPath -- current dir path+ [PosixPath] -- remaining dirs+ (Ptr CDir) -- current dir+ [PosixPath] -- dirs buffered+ Int -- dir count+ [PosixPath] -- files buffered+ Int -- file count++-- XXX We can use a fold for collecting files and dirs.+-- A fold may be useful to translate the output to whatever format we want, we+-- can add a prefix or we can colorize it. The Right output would be the output+-- of the fold which can be any type not just a Path.++-- XXX We can write a two fold scan to buffer and yield whichever fills first+-- like foldMany, it would be foldEither.+{-# INLINE readEitherChunks #-}+readEitherChunks+ :: MonadIO m+ => (ReadOptions -> ReadOptions)+ -> [PosixPath] -> Stream m (Either [PosixPath] [PosixPath])+readEitherChunks confMod alldirs =+ Stream step (ChunkStreamInit alldirs [] 0 [] 0)++ where++ conf = confMod defaultReadOptions++ -- We want to keep the dir batching as low as possible for better+ -- concurrency esp when the number of dirs is low.+ dirMax = 4+ fileMax = 1000++ step _ (ChunkStreamInit (x:xs) dirs ndirs files nfiles) = do+ DirStream dirp <- liftIO $ openDirStream x+ return $ Skip (ChunkStreamLoop x xs dirp dirs ndirs files nfiles)++ step _ (ChunkStreamInit [] [] _ [] _) =+ return Stop++ step _ (ChunkStreamInit [] [] _ files _) =+ return $ Yield (Right files) (ChunkStreamInit [] [] 0 [] 0)++ step _ (ChunkStreamInit [] dirs _ files _) =+ return $ Yield (Left dirs) (ChunkStreamInit [] [] 0 files 0)++ step _ st@(ChunkStreamLoop curdir xs dirp dirs ndirs files nfiles) = do+ liftIO resetErrno+ dentPtr <- liftIO $ c_readdir dirp+ if (dentPtr /= nullPtr)+ then do+ let dname = #{ptr struct dirent, d_name} dentPtr+ dtype :: #{type unsigned char} <-+ liftIO $ #{peek struct dirent, d_type} dentPtr++ etype <- liftIO $ getEntryType conf curdir dname dtype+ case etype of+ EntryIsDir -> do+ path <- liftIO $ appendCString curdir dname+ let dirs1 = path : dirs+ ndirs1 = ndirs + 1+ in if ndirs1 >= dirMax+ then return $ Yield (Left dirs1)+ (ChunkStreamLoop curdir xs dirp [] 0 files nfiles)+ else return $ Skip+ (ChunkStreamLoop curdir xs dirp dirs1 ndirs1 files nfiles)+ EntryIsNotDir -> do+ path <- liftIO $ appendCString curdir dname+ let files1 = path : files+ nfiles1 = nfiles + 1+ in if nfiles1 >= fileMax+ then return $ Yield (Right files1)+ (ChunkStreamLoop curdir xs dirp dirs ndirs [] 0)+ else return $ Skip+ (ChunkStreamLoop curdir xs dirp dirs ndirs files1 nfiles1)+ EntryIgnored -> return $ Skip st+ else do+ errno <- liftIO getErrno+ if (errno == eINTR)+ then return $ Skip st+ else do+ let (Errno n) = errno+ -- XXX Exception safety+ liftIO $ closeDirStream (DirStream dirp)+ if (n == 0)+ then return $ Skip (ChunkStreamInit xs dirs ndirs files nfiles)+ else liftIO $ throwErrno "readEitherChunks"++foreign import ccall unsafe "string.h memcpy" c_memcpy+ :: Ptr Word8 -> Ptr Word8 -> CSize -> IO (Ptr Word8)++-- See also cstringLength# in GHC.CString in ghc-prim+foreign import ccall unsafe "string.h strlen" c_strlen+ :: Ptr CChar -> IO CSize++-- Split a list in half.+splitHalf :: [a] -> ([a], [a])+splitHalf xxs = split xxs xxs++ where++ split (x:xs) (_:_:ys) =+ let (f, s) = split xs ys+ in (x:f, s)+ split xs _ = ([], xs)++{-# ANN type ChunkStreamByteState Fuse #-}+data ChunkStreamByteState =+ ChunkStreamByteInit+ | ChunkStreamByteStop+ | ChunkStreamByteLoop+ PosixPath -- current dir path+ [PosixPath] -- remaining dirs+ (Ptr CDir) -- current dir stream+ MutByteArray+ Int+ | ChunkStreamReallocBuf+ (Ptr CChar) -- pending item+ PosixPath -- current dir path+ [PosixPath] -- remaining dirs+ (Ptr CDir) -- current dir stream+ MutByteArray+ Int+ | ChunkStreamDrainBuf+ MutByteArray+ Int++-- XXX Detect cycles. ELOOP can be used to avoid cycles, but we can also detect+-- them proactively.++-- XXX Since we are separating paths by newlines, it cannot support newlines in+-- paths. Or we can return null separated paths as well. Provide a Mut array+-- API to replace the nulls with newlines in-place.+--+-- We can pass a fold to make this modular, but if we are passing readdir+-- managed memory then we will have to consume it immediately. Otherwise we can+-- use getdents64 directly and use GHC managed memory instead.+--+-- A fold may be useful to translate the output to whatever format we want, we+-- can add a prefix or we can colorize it.+--+-- XXX Use bufSize, recursive traversal, split strategy, output entries+-- separator as config options. When not using concurrently we do not need to+-- split the work at all.+--+-- XXX Currently we are quite aggressive in splitting the work because we have+-- no knowledge of whether we need to or not. But this leads to more overhead.+-- Instead, we can measure the coarse monotonic and process cpu time after+-- every n system calls or n iterations. If the cpu utilization is low then+-- yield the dirs otherwise dont. We can use an async thread for computing cpu+-- utilization periodically and all other threads can just read it from an+-- IORef. So this can be shared across all such consumers.++-- | This function may not traverse all the directories supplied and it may+-- traverse the directories recursively. Left contains those directories that+-- were not traversed by this function, these my be the directories that were+-- supplied as input as well as newly discovered directories during traversal.+-- To traverse the entire tree we have to iterate this function on the Left+-- output.+--+-- Right is a buffer containing directories and files separated by newlines.+--+{-# INLINE readEitherByteChunks #-}+readEitherByteChunks :: MonadIO m =>+ (ReadOptions -> ReadOptions) ->+ [PosixPath] -> Stream m (Either [PosixPath] (Array Word8))+readEitherByteChunks confMod alldirs =+ Stream step ChunkStreamByteInit++ where++ conf = confMod defaultReadOptions++ -- XXX A single worker may not have enough directories to list at once to+ -- fill up a large buffer. We need to change the concurrency model such+ -- that a worker should be able to pick up another dir from the queue+ -- without emitting an output until the buffer fills.+ --+ -- XXX A worker can also pick up multiple work items in one go. However, we+ -- also need to keep in mind that any kind of batching might have+ -- pathological cases where concurrency may be reduced.+ --+ -- XXX Alternatively, we can distribute the dir stream over multiple+ -- concurrent folds and return (monadic output) a stream of arrays created+ -- from the output channel, then consume that stream by using a monad bind.++ bufSize = 32000++ copyToBuf dstArr pos dirPath name = do+ nameLen <- fmap fromIntegral (liftIO $ c_strlen name)+ -- We know it is already pinned.+ MutByteArray.unsafeAsPtr dstArr (\ptr -> liftIO $ do+ -- XXX We may need to decode and encode the path if the+ -- output encoding differs from fs encoding.+ let PosixPath (Array dirArr start end) = dirPath+ dirLen = end - start+ endDir = pos + dirLen+ endPos = endDir + nameLen + 2 -- sep + newline+ sepOff = ptr `plusPtr` endDir -- separator offset+ nameOff = sepOff `plusPtr` 1 -- file name offset+ nlOff = nameOff `plusPtr` nameLen -- newline offset+ separator = 47 :: Word8+ newline = 10 :: Word8+ if (endPos < bufSize)+ then do+ -- XXX We can keep a trailing separator on the dir itself.+ MutByteArray.unsafePutSlice dirArr start dstArr pos dirLen+ poke sepOff separator+ _ <- c_memcpy nameOff (castPtr name) (fromIntegral nameLen)+ poke nlOff newline+ return (Just endPos)+ else return Nothing+ )++ step _ ChunkStreamByteInit = do+ mbarr <- liftIO $ MutByteArray.new' bufSize+ case alldirs of+ (x:xs) -> do+ DirStream dirp <- liftIO $ openDirStream x+ return $ Skip $ ChunkStreamByteLoop x xs dirp mbarr 0+ [] -> return Stop++ step _ ChunkStreamByteStop = return Stop++ step _ (ChunkStreamReallocBuf pending curdir xs dirp mbarr pos) = do+ mbarr1 <- liftIO $ MutByteArray.new' bufSize+ r1 <- copyToBuf mbarr1 0 curdir pending+ case r1 of+ Just pos2 ->+ return $ Yield (Right (Array mbarr 0 pos))+ -- When we come in this state we have emitted dirs+ (ChunkStreamByteLoop curdir xs dirp mbarr1 pos2)+ Nothing -> error "Dirname too big for bufSize"++ step _ (ChunkStreamDrainBuf mbarr pos) =+ if pos == 0+ then return Stop+ else return $ Yield (Right (Array mbarr 0 pos)) ChunkStreamByteStop++ step _ (ChunkStreamByteLoop icurdir ixs idirp mbarr ipos) = do+ goOuter icurdir idirp ixs ipos++ where++ -- This is recursed only when we open the next dir+ -- Encapsulates curdir and dirp as static arguments+ goOuter curdir dirp = goInner++ where++ -- This is recursed each time we find a dir+ -- Encapsulates dirs as static argument+ goInner dirs = nextEntry++ where++ {-# INLINE nextEntry #-}+ nextEntry pos = do+ liftIO resetErrno+ dentPtr <- liftIO $ c_readdir dirp+ if dentPtr /= nullPtr+ then handleDentry pos dentPtr+ else handleErr pos++ openNextDir pos =+ case dirs of+ (x:xs) -> do+ DirStream dirp1 <- liftIO $ openDirStream x+ goOuter x dirp1 xs pos+ [] ->+ if pos == 0+ then return Stop+ else return+ $ Yield+ (Right (Array mbarr 0 pos))+ ChunkStreamByteStop++ handleErr pos = do+ errno <- liftIO getErrno+ if (errno /= eINTR)+ then do+ let (Errno n) = errno+ liftIO $ closeDirStream (DirStream dirp)+ if (n == 0)+ then openNextDir pos+ else liftIO $ throwErrno "readEitherByteChunks"+ else nextEntry pos++ splitAndRealloc pos dname xs =+ case xs of+ [] ->+ return $ Skip+ (ChunkStreamReallocBuf dname curdir+ [] dirp mbarr pos)+ _ -> do+ let (h,t) = splitHalf xs+ return $ Yield (Left t)+ (ChunkStreamReallocBuf dname curdir+ h dirp mbarr pos)++ {-# INLINE handleFileEnt #-}+ handleFileEnt pos dname = do+ r <- copyToBuf mbarr pos curdir dname+ case r of+ Just pos1 -> nextEntry pos1+ Nothing -> splitAndRealloc pos dname dirs++ {-# INLINE handleDirEnt #-}+ handleDirEnt pos dname = do+ path <- liftIO $ appendCString curdir dname+ let dirs1 = path : dirs+ r <- copyToBuf mbarr pos curdir dname+ case r of+ Just pos1 -> goInner dirs1 pos1+ Nothing -> splitAndRealloc pos dname dirs1++ handleDentry pos dentPtr = do+ let dname = #{ptr struct dirent, d_name} dentPtr+ dtype :: #{type unsigned char} <-+ liftIO $ #{peek struct dirent, d_type} dentPtr++ etype <- liftIO $ getEntryType conf curdir dname dtype+ case etype of+ EntryIsNotDir -> handleFileEnt pos dname+ EntryIsDir -> handleDirEnt pos dname+ EntryIgnored -> nextEntry pos++{-# ANN type ByteChunksAt Fuse #-}+data ByteChunksAt =+ ByteChunksAtInit0+ | ByteChunksAtInit+ Fd+ [PosixPath] -- input dirs+ -- (Handle, [PosixPath]) -- output dirs+ -- Int -- count of output dirs+ MutByteArray -- output files and dirs+ Int -- position in MutByteArray+ | ByteChunksAtLoop+ Fd+ (Ptr CDir) -- current dir stream+ PosixPath -- current dir path+ [PosixPath] -- remaining dirs+ [PosixPath] -- output dirs+ Int -- output dir count+ MutByteArray+ Int+ | ByteChunksAtRealloc+ (Ptr CChar) -- pending item+ Fd+ (Ptr CDir) -- current dir stream+ PosixPath -- current dir path+ [PosixPath] -- remaining dirs+ [PosixPath] -- output dirs+ Int -- output dir count+ MutByteArray+ Int++-- The advantage of readEitherByteChunks over readEitherByteChunksAt is that we+-- do not need to open the dir handles and thus requires less open fd.+{-# INLINE readEitherByteChunksAt #-}+readEitherByteChunksAt :: MonadIO m => (ReadOptions -> ReadOptions) ->+ -- (parent dir path, child dir paths rel to parent)+ (PosixPath, [PosixPath])+ -> Stream m (Either (PosixPath, [PosixPath]) (Array Word8))+readEitherByteChunksAt confMod (ppath, alldirs) =+ Stream step (ByteChunksAtInit0)++ where+ conf = confMod defaultReadOptions++ bufSize = 4000++ copyToBuf dstArr pos dirPath name = do+ nameLen <- fmap fromIntegral (liftIO $ c_strlen name)+ -- XXX prepend ppath to dirPath+ let PosixPath (Array dirArr start end) = dirPath+ dirLen = end - start+ -- XXX We may need to decode and encode the path if the+ -- output encoding differs from fs encoding.+ --+ -- Account for separator and newline bytes.+ byteCount = dirLen + nameLen + 2+ if pos + byteCount <= bufSize+ then do+ -- XXX append a path separator to a dir path+ -- We know it is already pinned.+ MutByteArray.unsafeAsPtr dstArr (\ptr -> liftIO $ do+ MutByteArray.unsafePutSlice dirArr start dstArr pos dirLen+ let ptr1 = ptr `plusPtr` (pos + dirLen)+ separator = 47 :: Word8+ poke ptr1 separator+ let ptr2 = ptr1 `plusPtr` 1+ _ <- c_memcpy ptr2 (castPtr name) (fromIntegral nameLen)+ let ptr3 = ptr2 `plusPtr` nameLen+ newline = 10 :: Word8+ poke ptr3 newline+ )+ return (Just (pos + byteCount))+ else return Nothing++ step _ ByteChunksAtInit0 = do+ -- Note this fd is not automatically closed, we have to take care of+ -- exceptions and closing the fd.+ pfd <- liftIO $ openAt Nothing ppath defaultOpenFlags Nothing+ mbarr <- liftIO $ MutByteArray.new' bufSize+ return $ Skip (ByteChunksAtInit pfd alldirs mbarr 0)++ step _ (ByteChunksAtInit ph (x:xs) mbarr pos) = do+ (DirStream dirp) <- liftIO $ openDirStreamAt ph x+ return $ Skip (ByteChunksAtLoop ph dirp x xs [] 0 mbarr pos)++ step _ (ByteChunksAtInit pfd [] _ 0) = do+ liftIO $ close (pfd)+ return Stop++ step _ (ByteChunksAtInit pfd [] mbarr pos) = do+ return+ $ Yield+ (Right (Array mbarr 0 pos))+ (ByteChunksAtInit pfd [] mbarr 0)++ step _ (ByteChunksAtRealloc pending pfd dirp curdir xs dirs ndirs mbarr pos) = do+ mbarr1 <- liftIO $ MutByteArray.new' bufSize+ r1 <- copyToBuf mbarr1 0 curdir pending+ case r1 of+ Just pos2 ->+ return $ Yield (Right (Array mbarr 0 pos))+ (ByteChunksAtLoop pfd dirp curdir xs dirs ndirs mbarr1 pos2)+ Nothing -> error "Dirname too big for bufSize"++ step _ st@(ByteChunksAtLoop pfd dirp curdir xs dirs ndirs mbarr pos) = do+ liftIO resetErrno+ dentPtr <- liftIO $ c_readdir dirp+ if (dentPtr /= nullPtr)+ then do+ let dname = #{ptr struct dirent, d_name} dentPtr+ dtype :: #{type unsigned char} <-+ liftIO $ #{peek struct dirent, d_type} dentPtr++ -- Keep the file check first as it is more likely+ etype <- liftIO $ getEntryType conf curdir dname dtype+ case etype of+ EntryIsNotDir -> do+ r <- copyToBuf mbarr pos curdir dname+ case r of+ Just pos1 ->+ return $ Skip+ (ByteChunksAtLoop+ pfd dirp curdir xs dirs ndirs mbarr pos1)+ Nothing ->+ return $ Skip+ (ByteChunksAtRealloc+ dname pfd dirp curdir xs dirs ndirs mbarr pos)+ EntryIsDir -> do+ arr <- Array.fromCString (castPtr dname)+ let path = Path.unsafeFromArray arr+ let dirs1 = path : dirs+ ndirs1 = ndirs + 1+ r <- copyToBuf mbarr pos curdir dname+ case r of+ Just pos1 ->+ -- XXX When there is less parallelization at the+ -- top of the tree, we should use smaller chunks.+ {-+ if ndirs > 64+ then do+ let fpath = Path.unsafeJoin ppath curdir+ return $ Yield+ (Left (fpath, dirs1))+ (ByteChunksAtLoop pfd dirp curdir xs [] 0 mbarr pos1)+ else+ -}+ return $ Skip+ (ByteChunksAtLoop+ pfd dirp curdir xs dirs1 ndirs1 mbarr pos1)+ Nothing -> do+ return $ Skip+ (ByteChunksAtRealloc+ dname pfd dirp curdir xs dirs1 ndirs1 mbarr pos)+ EntryIgnored -> return $ Skip st+ else do+ errno <- liftIO getErrno+ if (errno == eINTR)+ then return $ Skip st+ else do+ let (Errno n) = errno+ -- XXX What if an exception occurs in the code before this?+ -- Should we attach a weak IORef to close the fd on GC.+ liftIO $ closeDirStream (DirStream dirp)+ if (n == 0)+ then+ -- XXX Yielding on each dir completion may hurt perf when+ -- there are many small directories. However, it may also+ -- help parallelize more in IO bound case.+ if ndirs > 0+ then do+ let fpath = Path.unsafeJoin ppath curdir+ return $ Yield+ (Left (fpath, dirs))+ (ByteChunksAtInit pfd xs mbarr pos)+ else return $ Skip (ByteChunksAtInit pfd xs mbarr pos)+ else liftIO $ throwErrno "readEitherByteChunks"+#endif
+ src/Streamly/Internal/FileSystem/PosixPath.hs view
@@ -0,0 +1,1611 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE TemplateHaskell #-}++#if defined(IS_PORTABLE)+#define OS_PATH_TYPE Path+#define OS_WORD_TYPE OsWord+#define OS_CSTRING_TYPE OsCString+#define AS_OS_CSTRING asOsCString+#elif defined(IS_WINDOWS)+#define OS_PATH_TYPE WindowsPath+#define OS_WORD_TYPE Word16+#define OS_CSTRING_TYPE CWString+#define AS_OS_CSTRING asCWString+#else+#define OS_PATH_TYPE PosixPath+#define OS_WORD_TYPE Word8+#define OS_CSTRING_TYPE CString+#define AS_OS_CSTRING asCString+#endif++-- Anything other than windows (Linux/macOS/FreeBSD) is Posix+#if defined(IS_WINDOWS)+#define OS_NAME Windows+#define OS_PATH WindowsPath+#define OS_WORD Word16+#define OS_CSTRING CWString+#define UNICODE_ENCODER encodeUtf16le'+#define UNICODE_DECODER decodeUtf16le'+#define UNICODE_DECODER_LAX decodeUtf16le+#define CODEC_NAME UTF-16LE+#define SEPARATORS @/, \\@+#else+#define OS_NAME Posix+#define OS_PATH PosixPath+#define OS_WORD Word8+#define OS_CSTRING CString+#define UNICODE_ENCODER encodeUtf8'+#define UNICODE_DECODER decodeUtf8'+#define UNICODE_DECODER_LAX decodeUtf8+#define CODEC_NAME UTF-8+#define SEPARATORS @/@+#endif++-- |+-- Module : Streamly.Internal.FileSystem.OS_PATH_TYPE+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--+-- This module implements a OS_PATH_TYPE type representing a file system path for+-- OS_NAME operating systems. The only assumption about the encoding of the+-- path is that it maps the characters SEPARATORS and @.@ to OS_WORD_TYPE+-- representing their ASCII values. Operations are provided to encode and+-- decode using CODEC_NAME encoding.+--+-- This module has APIs that are equivalent to or can emulate all or most of+-- the filepath package APIs. It has some differences from the filepath+-- package:+--+-- * Empty paths are not allowed. Paths are validated before construction.+-- * The default Path type itself affords considerable safety regarding the+-- distinction of rooted or non-rooted paths, it also allows distinguishing+-- directory and file paths.+-- * It is designed to provide flexible typing to provide compile time safety+-- for rooted/non-rooted paths and file/dir paths. The Path type is just part+-- of that typed path ecosystem. Though the default Path type itself should be+-- enough for most cases.+-- * It leverages the streamly array module for most of the heavy lifting,+-- it is a thin wrapper on top of that, improving maintainability as well as+-- providing better performance. We can have pinned and unpinned paths, also+-- provide lower level operations for certain cases to interact more+-- efficinetly with low level code.++module Streamly.Internal.FileSystem.OS_PATH_TYPE+ (+ -- * Setup+ -- | To execute the code examples provided in this module in ghci, please+ -- run the following commands first.+ --+ -- $setup++ -- * Type+#if defined(IS_PORTABLE)+ OS_WORD_TYPE+ , OS_CSTRING_TYPE+ , OS_PATH_TYPE+#else+ OS_PATH_TYPE (..)+#endif+ -- * Conversions+ , IsPath (..)+ , adapt++ -- * Conversion to OsWord+ , charToWord+ , wordToChar++ -- * Validation+ , validatePath+ , isValidPath+#ifdef IS_WINDOWS+ , validatePath'+ , isValidPath'+#endif++ -- * Construction+ , fromArray+ , unsafeFromArray+ , fromChars+ , fromString+ , fromString_+ , encodeString+ , unsafeFromString+ -- , fromCString#+ -- , fromCWString#+ , readArray++ -- * Statically Verified String Literals+ -- | Quasiquoters.+ , path++ -- * Statically Verified Strings+ -- | Template Haskell expression splices.++ -- Note: We expose these even though we have quasiquoters as these TH+ -- helpers are more powerful. They are useful if we are generating strings+ -- statically using methods other than literals or if we are doing some+ -- text processing on strings before using them.+ , pathE++ -- * Elimination+ , toArray+ , toChars+ , toChars_+ , toString+ , AS_OS_CSTRING+ , toString_+ , showArray++ -- * Separators+ -- Do we need to export the separator char functions? They are not+ -- essential if operations to split and combine paths are provided. If+ -- someone wants to work on paths at low level then they know what they+ -- are. We should export the OsWord based operations to work with arrays.+ , separator+ , isSeparator+ , extSeparator++ -- * Dir or non-dir paths++ -- You do not need these, instead use eqPath with ignoreTrailingSeparators.+ , dropTrailingSeparators+ , hasTrailingSeparator+ , addTrailingSeparator++ -- * Path Segment Types+ , isRooted+ , isUnrooted++ -- * Joining+ , joinStr+ -- , concat+ , unsafeJoin+#ifndef IS_WINDOWS+ , joinCStr+ , joinCStr'+#endif+ , join+ , joinDir+ , unsafeJoinPaths++ -- * Splitting+ -- | Note: you can use 'unsafeJoin' as a replacement for the joinDrive+ -- function in the filepath package.+ , splitRoot+ , splitPath+ , splitPath_+ , splitFile++ , splitFirst+ , splitLast++ -- ** Extension+ , splitExtension+ , dropExtension+ , addExtension+ , replaceExtension++ -- ** Path View+ , takeFileName+ , takeDirectory+ -- , takeDirectory_ -- drops the trailing /+ , takeExtension+ , takeFileBase++ -- * Equality+ , EqCfg+ , ignoreTrailingSeparators+ , ignoreCase+ , allowRelativeEquality+ , eqPath+ , eqPathBytes+ , normalize+ )+where++import Control.Exception (throw)+import Control.Monad.Catch (MonadThrow(..))+import Data.Bifunctor (bimap)+import Data.Functor.Identity (Identity(..))+import Data.Maybe (fromJust, isJust)+#ifndef IS_WINDOWS+import Data.Word (Word8)+import Foreign.C (CString)+#else+import Data.Word (Word16)+import Foreign.C (CWString)+#endif+import Language.Haskell.TH.Syntax (lift)+import Streamly.Internal.Data.Array (Array(..))+import Streamly.Internal.Data.Stream (Stream)+import Streamly.Internal.FileSystem.Path.Common (mkQ, EqCfg(..))++import qualified Streamly.Internal.Data.Array as Array+import qualified Streamly.Internal.Data.Stream as Stream+import qualified Streamly.Internal.FileSystem.Path.Common as Common+import qualified Streamly.Internal.Unicode.Stream as Unicode++import Language.Haskell.TH+import Language.Haskell.TH.Quote+import Streamly.Internal.Data.Path++#if defined(IS_PORTABLE)+import Streamly.Internal.FileSystem.OS_PATH (OS_PATH(..))+#endif++-- NOTES about C preprocessor use.+--+-- docspec comment lines cannot use CPP macros, docspec does not expand them+-- before running tests.+--+-- We cannot use a CPP conditional inside haddock comments because the+-- conditional line replaced by a blank line by CPP and this breaks the haddock+-- comment. Therefore if the comment is slightly different on a different+-- platform we duplicate the entire comment inside a conditional.++#ifdef IS_PORTABLE+#include "DocTestFileSystemPath.hs"+#elif defined(IS_WINDOWS)+#include "DocTestFileSystemWindowsPath.hs"+#else+#include "DocTestFileSystemPosixPath.hs"+#endif++#if defined(IS_PORTABLE)+type OS_PATH_TYPE = OS_PATH+type OS_WORD_TYPE = OS_WORD+type OS_CSTRING_TYPE = OS_CSTRING+#else+-- | A type representing file system paths on OS_NAME.+--+-- A OS_PATH_TYPE is validated before construction unless unsafe constructors are+-- used to create it. For validations performed by the safe construction+-- methods see the 'fromChars' function.+--+-- Note that in some cases the file system may perform unicode normalization on+-- paths (e.g. Apple HFS), it may cause surprising results as the path used by+-- the user may not have the same bytes as later returned by the file system.+newtype OS_PATH = OS_PATH (Array OS_WORD_TYPE)++-- XXX The Eq instance may be provided but it will require some sensible+-- defaults for comparison. For example, should we use case sensitive or+-- insensitive comparison? It depends on the underlying file system. For now+-- now we have eqPath operations for equality comparison.++instance IsPath OS_PATH OS_PATH where+ unsafeFromPath = id+ fromPath = pure+ toPath = id+#endif++-- XXX Use rewrite rules to eliminate intermediate conversions for better+-- efficiency. If the argument path is already verfied for a property, we+-- should not verify it again e.g. if we adapt (Rooted path) as (Rooted (Dir+-- path)) then we should not verify it to be Rooted again.++-- XXX castPath?++-- | Convert a path type to another path type. This operation may fail with a+-- 'PathException' when converting a less restrictive path type to a more+-- restrictive one. This can be used to upgrade or downgrade type safety.+adapt :: (MonadThrow m, IsPath OS_PATH_TYPE a, IsPath OS_PATH_TYPE b) => a -> m b+adapt p = fromPath (toPath p :: OS_PATH_TYPE)++------------------------------------------------------------------------------+-- Char to word+------------------------------------------------------------------------------++-- | Unsafe, truncates the Char to Word8 on Posix and Word16 on Windows.+charToWord :: Char -> OS_WORD_TYPE+charToWord = Common.charToWord++-- | Unsafe, should be a valid character.+wordToChar :: OS_WORD_TYPE -> Char+wordToChar = Common.wordToChar++------------------------------------------------------------------------------+-- Separators+------------------------------------------------------------------------------++-- | The primary path separator word: @/@ on POSIX and @\\@ on Windows.+-- Windows also supports @/@ as a valid separator. Use 'isSeparator' to check+-- for any valid path separator.+{-# INLINE separator #-}+separator :: OS_WORD_TYPE+separator = charToWord $ Common.primarySeparator Common.OS_NAME++-- | On POSIX, only @/@ is a path separator, whereas on Windows both @/@ and+-- @\\@ are valid separators.+{-# INLINE isSeparator #-}+isSeparator :: OS_WORD_TYPE -> Bool+isSeparator = Common.isSeparatorWord Common.OS_NAME++-- | File extension separator word.+{-# INLINE extSeparator #-}+extSeparator :: OS_WORD_TYPE+extSeparator = Common.extensionWord++------------------------------------------------------------------------------+-- Path parsing utilities+------------------------------------------------------------------------------++-- XXX We can have prime suffixed versions where it drops or adds separator+-- unconditionally. Alternatively, we can convert the path to array and use+-- array operations instead.++-- | Remove all trailing path separators from the given 'Path'.+--+-- Instead of this operation you may want to use 'eqPath' with+-- 'ignoreTrailingSeparators' option.+--+-- This operation is careful not to alter the semantic meaning of the path.+-- For example, on Windows:+--+-- * Dropping the separator from "C:/" would change the meaning of the path+-- from referring to the root of the C: drive to the current directory on C:.+-- * If a path ends with a separator immediately after a colon (e.g., "C:/"),+-- the separator will not be removed.+--+-- If the input path is invalid, the behavior is not fully guaranteed:+--+-- * The separator may still be dropped.+-- * In some cases, dropping the separator may make an invalid path valid+-- (e.g., "C:\\\\" or "C:\\/").+--+-- This operation may convert a path that implicitly refers to a directory+-- into one that does not.+--+-- Typically, if the path is @dir//@, the result is @dir@. Special cases include:+--+-- * On POSIX: dropping from @"//"@ yields @"/"@.+-- * On Windows: dropping from @"C://"@ results in @"C:/"@.+--+-- Examples:+--+-- >>> f = Path.toString . Path.dropTrailingSeparators . Path.fromString_+-- >>> f "./"+-- "."+--+-- >> f "//" -- On POSIX+-- "/"+--+{-# INLINE dropTrailingSeparators #-}+dropTrailingSeparators :: OS_PATH_TYPE -> OS_PATH_TYPE+dropTrailingSeparators (OS_PATH arr) =+ OS_PATH (Common.dropTrailingSeparators Common.OS_NAME arr)++-- On windows a share name can also be reported to have a trailing separator,+-- but that is not a valid Path.++-- | Returns 'True' if the path ends with a trailing separator.+--+-- This typically indicates that the path is a directory, though this is not+-- guaranteed in all cases.+--+-- Example:+--+-- >>> Path.hasTrailingSeparator (Path.fromString_ "foo/")+-- True+--+-- >>> Path.hasTrailingSeparator (Path.fromString_ "foo")+-- False+{-# INLINE hasTrailingSeparator #-}+hasTrailingSeparator :: OS_PATH_TYPE -> Bool+hasTrailingSeparator (OS_PATH arr) =+ Common.hasTrailingSeparator Common.OS_NAME arr++-- | Add a trailing path separator to a path if it doesn't already have one.+--+-- Instead of this operation you may want to use 'eqPath' with+-- 'ignoreTrailingSeparators' option.+--+-- This function avoids modifying the path if doing so would change its meaning+-- or make it invalid. For example, on Windows:+--+-- * Adding a separator to "C:" would change it from referring to the current+-- directory on the C: drive to the root directory.+-- * Adding a separator to "\\" could turn it into a UNC share path, which may+-- not be intended.+-- * If the path ends with a colon (e.g., "C:"), a separator is not added.+--+-- This operation typically makes the path behave like an implicit directory path.+{-# INLINE addTrailingSeparator #-}+addTrailingSeparator :: OS_PATH_TYPE -> OS_PATH_TYPE+addTrailingSeparator p@(OS_PATH _arr) =+#ifdef IS_WINDOWS+ if Array.unsafeGetIndexRev 0 _arr == Common.charToWord ':'+ then p+ else unsafeJoin p sep+#else+ unsafeJoin p sep+#endif++ where++ sep = fromJust $ fromString [Common.primarySeparator Common.OS_NAME]++-- Path must not contain null char as system calls treat the path as a null+-- terminated C string. Also, they return null terminated strings as paths.+--+-- XXX Maintain the Array with null termination? To avoid copying the path for+-- null termination when passing to system calls. Path appends will have to+-- handle the null termination.++#ifndef IS_WINDOWS+-- | Checks whether the filepath is valid; i.e., whether the operating system+-- permits such a path for listing or creating files. These validations are+-- operating system specific and file system independent. Throws an exception+-- with a detailed explanation if the path is invalid.+--+-- >>> isValid = isJust . Path.validatePath . Path.encodeString+--+-- Validations:+--+-- >>> isValid ""+-- False+-- >>> isValid "\0"+-- False+--+-- Other than these there may be maximum path component length and maximum path+-- length restrictions enforced by the OS as well as the filesystem which we do+-- not validate.+--+#else+-- | Checks whether the filepath is valid; i.e., whether the operating system+-- permits such a path for listing or creating files. These validations are+-- operating system specific and file system independent. Throws an exception+-- with a detailed explanation if the path is invalid.+--+-- >>> isValid = isJust . Path.validatePath . Path.encodeString+--+-- General validations:+--+-- >>> isValid ""+-- False+-- >>> isValid "\0"+-- False+--+-- Windows invalid characters:+--+-- >>> isValid "c::"+-- False+-- >>> isValid "c:\\x:y"+-- False+-- >>> isValid "x*"+-- False+-- >>> isValid "x\ty" -- control characters+-- False+--+-- Windows invalid path components:+--+-- >>> isValid "pRn.txt"+-- False+-- >>> isValid " pRn .txt"+-- False+-- >>> isValid "c:\\x\\pRn"+-- False+-- >>> isValid "c:\\x\\pRn.txt"+-- False+-- >>> isValid "c:\\pRn\\x"+-- False+-- >>> isValid "c:\\ pRn \\x"+-- False+-- >>> isValid "pRn.x.txt"+-- False+--+-- Windows drive root validations:+--+-- >>> isValid "c:"+-- True+-- >>> isValid "c:a\\b"+-- True+-- >>> isValid "c:\\"+-- True+-- >>> isValid "c:\\\\"+-- False+-- >>> isValid "c:\\/"+-- False+-- >>> isValid "c:\\\\x"+-- False+-- >>> isValid "c:\\/x"+-- False+--+-- Mixing path separators:+-- >>> isValid "/x\\y"+-- True+-- >>> isValid "\\/" -- ?+-- True+-- >>> isValid "/\\" -- ?+-- True+-- >>> isValid "\\/x/y" -- ?+-- True+-- >>> isValid "/x/\\y" -- ?+-- True+-- >>> isValid "/x\\/y" -- ?+-- True+--+-- Windows share path validations:+--+-- >>> isValid "\\"+-- True+-- >>> isValid "\\\\"+-- False+-- >>> isValid "\\\\\\"+-- False+-- >>> isValid "\\\\x"+-- False+-- >>> isValid "\\\\x\\"+-- True+-- >>> isValid "\\\\x\\y"+-- True+-- >>> isValid "//x/y"+-- True+-- >>> isValid "\\\\prn\\y"+-- False+-- >>> isValid "\\\\x\\\\"+-- False+-- >>> isValid "\\\\x\\\\x"+-- False+-- >>> isValid "\\\\\\x"+-- False+--+-- Windows short UNC path validations:+--+-- >>> isValid "\\\\?\\c:"+-- False+-- >>> isValid "\\\\?\\c:\\"+-- True+-- >>> isValid "\\\\?\\c:x"+-- False+-- >>> isValid "\\\\?\\c:\\\\" -- XXX validate this+-- False+-- >>> isValid "\\\\?\\c:\\x"+-- True+-- >>> isValid "\\\\?\\c:\\\\\\"+-- False+-- >>> isValid "\\\\?\\c:\\\\x"+-- False+--+-- Windows long UNC path validations:+--+-- >>> isValid "\\\\?\\UnC\\x" -- UnC treated as share name+-- True+-- >>> isValid "\\\\?\\UNC\\x" -- XXX fix+-- False+-- >>> isValid "\\\\?\\UNC\\c:\\x"+-- True+--+-- DOS local/global device namespace+--+-- >>> isValid "\\\\.\\x"+-- True+-- >>> isValid "\\\\??\\x"+-- True+--+-- Other than these there may be maximum path component length and maximum path+-- length restrictions enforced by the OS as well as the filesystem which we do+-- not validate.+--+#endif+validatePath :: MonadThrow m => Array OS_WORD_TYPE -> m ()+validatePath = Common.validatePath Common.OS_NAME++-- | Returns 'True' if the filepath is valid:+--+-- >>> isValidPath = isJust . Path.validatePath+--+isValidPath :: Array OS_WORD_TYPE -> Bool+isValidPath = isJust . validatePath++-- Note: CPP gets confused by the prime suffix, so we have to put the CPP+-- macros on the next line to get it to work.++------------------------------------------------------------------------------+-- Construction+------------------------------------------------------------------------------++-- A chunk is essentially an untyped Array i.e. Array Word8. We can either use+-- the term ByteArray for that or just Chunk. The latter is shorter and we have+-- been using it consistently in streamly. We use "bytes" for a stream of+-- bytes.++-- | /Unsafe/: The user is responsible to make sure that the path is valid as+-- per 'validatePath'.+--+{-# INLINE unsafeFromArray #-}+unsafeFromArray :: Array OS_WORD_TYPE -> OS_PATH_TYPE+unsafeFromArray =+#ifndef DEBUG+ OS_PATH . Common.unsafeFromArray+#else+ fromJust . fromArray+#endif++#ifndef IS_WINDOWS+-- | Convert an encoded array of OS_WORD_TYPE into a value of type+-- OS_PATH_TYPE. The path is validated using 'validatePath'.+--+-- Each OS_WORD_TYPE should be encoded such that:+--+-- * The input does not contain a NUL word.+-- * Values from 1-128 are assumed to be ASCII characters.+--+-- Apart from the above, there are no restrictions on the encoding.+--+-- To bypass path validation checks, use 'unsafeFromArray'.+--+-- Throws 'InvalidPath' if 'validatePath' fails on the resulting path.+--+#else+-- | Convert an encoded array of OS_WORD_TYPE into a value of type+-- OS_PATH_TYPE. The path is validated using 'validatePath'.+--+-- Each OS_WORD_TYPE should be encoded such that:+--+-- * The input does not contain a NUL word.+-- * The OS_WORD_TYPE is encoded with little-endian ordering.+-- * Values from 1-128 are assumed to be ASCII characters.+--+-- Apart from the above, there are no restrictions on the encoding.+--+-- To bypass path validation checks, use 'unsafeFromArray'.+--+-- Throws 'InvalidPath' if 'validatePath' fails on the resulting path.+--+#endif+fromArray :: MonadThrow m => Array OS_WORD_TYPE -> m OS_PATH_TYPE+fromArray arr = OS_PATH <$> Common.fromArray Common.OS_NAME arr++-- XXX Should be a Fold instead?++-- | Like 'fromString' but a streaming operation.+--+-- >>> fromString = Path.fromChars . Stream.fromList+--+-- We do not sanitize the path i.e. we do not remove duplicate separators,+-- redundant @.@ segments, trailing separators etc because that would require+-- unnecessary checks and modifications to the path which may not be used ever+-- for any useful purpose, it is only needed for path equality and can be done+-- during the equality check.+--+-- Unicode normalization is not done. If normalization is needed the user can+-- normalize it and then use the 'fromArray' API.+{-# INLINE fromChars #-}+fromChars :: MonadThrow m => Stream Identity Char -> m OS_PATH_TYPE+fromChars s =+ OS_PATH <$> Common.fromChars Common.OS_NAME Unicode.UNICODE_ENCODER s++-- | Create an array from a path string using strict CODEC_NAME encoding. The+-- path is not validated, therefore, it may not be valid according to+-- 'validatePath'.+--+-- Same as @toArray . unsafeFromString@.+encodeString :: [Char] -> Array OS_WORD_TYPE+encodeString =+ Common.unsafeFromChars Unicode.UNICODE_ENCODER+ . Stream.fromList++-- | Like 'fromString' but does not perform any validations mentioned under+-- 'validatePath'. Fails only if unicode encoding fails.+unsafeFromString :: [Char] -> OS_PATH_TYPE+unsafeFromString =+#ifndef DEBUG+ OS_PATH+ . encodeString+#else+ fromJust . fromString+#endif++-- | Encode a Unicode character string to OS_PATH_TYPE using strict CODEC_NAME+-- encoding. The path is validated using 'validatePath'.+--+-- * Throws 'InvalidPath' if 'validatePath' fails on the path+-- * Fails if the stream contains invalid unicode characters+--+fromString :: MonadThrow m => [Char] -> m OS_PATH_TYPE+fromString = fromChars . Stream.fromList++-- | Like fromString but a pure and partial function that throws an+-- 'InvalidPath' exception.+fromString_ :: [Char] -> OS_PATH_TYPE+fromString_ x =+ case fromString x of+ Left e -> throw e+ Right v -> v++-- | Append a separator followed by the supplied string to a path.+--+-- Throws 'InvalidPath' if the resulting path is not a valid path as per+-- 'validatePath'.+--+joinStr :: OS_PATH_TYPE -> [Char] -> OS_PATH_TYPE+joinStr (OS_PATH a) b =+ OS_PATH $+ Common.append Common.OS_NAME+ (Common.toString Unicode.UNICODE_DECODER) a (encodeString b)+++------------------------------------------------------------------------------+-- Statically Verified Strings+------------------------------------------------------------------------------++-- XXX We can lift the array directly, ByteArray has a lift instance. Does that+-- work better?+--+-- XXX Make this polymorphic and reusable in other modules.++liftPath :: OS_PATH_TYPE -> Q Exp+liftPath p =+ [| unsafeFromString $(lift $ toString p) :: OS_PATH |]++-- | Generates a Haskell expression of type OS_PATH_TYPE from a String. Equivalent+-- to using 'fromString' on the string passed.+--+pathE :: String -> Q Exp+pathE = either (error . show) liftPath . fromString++------------------------------------------------------------------------------+-- Statically Verified Literals+------------------------------------------------------------------------------++-- XXX Define folds or parsers to parse the paths.+-- XXX Build these on top of the str quasiquoter so that we get interpolation+-- for free. Interpolated vars if any have to be of appropriate type depending+-- on the context so that we can splice them safely.++#ifdef IS_PORTABLE+-- | Generates a OS_PATH_TYPE type from a quoted literal. Equivalent to using+-- 'fromString' on the static literal.+--+-- >>> Path.toString ([path|/usr/bin|] :: Path)+-- "/usr/bin"+--+#endif+path :: QuasiQuoter+path = mkQ pathE++------------------------------------------------------------------------------+-- Eimination+------------------------------------------------------------------------------++-- | Convert the path to an array.+toArray :: OS_PATH_TYPE -> Array OS_WORD_TYPE+toArray (OS_PATH arr) = arr++-- | Decode the path to a stream of Unicode chars using strict CODEC_NAME decoding.+{-# INLINE toChars #-}+toChars :: Monad m => OS_PATH_TYPE -> Stream m Char+toChars (OS_PATH arr) = Common.toChars Unicode.UNICODE_DECODER arr++-- | Decode the path to a stream of Unicode chars using lax CODEC_NAME decoding.+toChars_ :: Monad m => OS_PATH_TYPE -> Stream m Char+toChars_ (OS_PATH arr) = Common.toChars Unicode.UNICODE_DECODER_LAX arr++-- XXX When showing, append a "/" to dir types?++-- | Decode the path to a Unicode string using strict CODEC_NAME decoding.+toString :: OS_PATH_TYPE -> [Char]+toString = runIdentity . Stream.toList . toChars++-- | Decode the path to a Unicode string using lax CODEC_NAME decoding.+toString_ :: OS_PATH_TYPE -> [Char]+toString_ = runIdentity . Stream.toList . toChars_++-- | Show the path as raw characters without any specific decoding.+--+-- See also: 'readArray'.+--+showArray :: OS_PATH_TYPE -> [Char]+showArray (OS_PATH arr) = show arr++#ifndef IS_WINDOWS+#ifdef IS_PORTABLE+-- | Parse a raw array of bytes as a path type.+--+-- >>> readArray = fromJust . Path.fromArray . read+--+-- >>> arr = Path.encodeString "hello"+-- >>> Path.showArray $ (Path.readArray $ show arr :: Path.Path)+-- "fromList [104,101,108,108,111]"+--+-- See also: 'showArray'.+#endif+readArray :: [Char] -> OS_PATH_TYPE+readArray = fromJust . fromArray . read+#endif++-- We cannot show decoded path in the Show instance as it may not always+-- succeed and it depends on the encoding which we may not even know. The+-- encoding may depend on the OS and the file system. Also we need Show and+-- Read to be inverses. The best we can provide is to show the bytes as+-- Hex or decimal values.+{-+instance Show OS_PATH where+ show (OS_PATH x) = show x+-}++#ifndef IS_WINDOWS+-- | Use the path as a pinned CString. Useful for using a PosixPath in+-- system calls on Posix.+{-# INLINE AS_OS_CSTRING #-}+AS_OS_CSTRING :: OS_PATH_TYPE -> (OS_CSTRING_TYPE -> IO a) -> IO a+AS_OS_CSTRING p = Array.asCStringUnsafe (toArray p)+#else+-- | Use the path as a pinned CWString. Useful for using a WindowsPath in+-- system calls on Windows.+{-# INLINE AS_OS_CSTRING #-}+AS_OS_CSTRING :: OS_PATH_TYPE -> (OS_CSTRING_TYPE -> IO a) -> IO a+AS_OS_CSTRING p = Array.asCWString (toArray p)+#endif++------------------------------------------------------------------------------+-- Operations on Path+------------------------------------------------------------------------------++#ifndef IS_WINDOWS+-- | A path that is attached to a root e.g. "\/x" or ".\/x" are rooted paths.+-- "\/" is considered an absolute root and "." as a dynamic root. ".." is not+-- considered a root, "..\/x" or "x\/y" are not rooted paths.+--+-- >>> isRooted = Path.isRooted . Path.fromString_+--+-- >>> isRooted "/"+-- True+-- >>> isRooted "/x"+-- True+-- >>> isRooted "."+-- True+-- >>> isRooted "./x"+-- True+--+isRooted :: OS_PATH_TYPE -> Bool+isRooted (OS_PATH arr) = Common.isRooted Common.OS_NAME arr+#endif++-- | A path that is not attached to a root e.g. @a\/b\/c@ or @..\/b\/c@.+--+-- >>> isUnrooted = not . Path.isRooted+--+-- >>> isUnrooted = Path.isUnrooted . Path.fromString_+--+-- >>> isUnrooted "x"+-- True+-- >>> isUnrooted "x/y"+-- True+-- >>> isUnrooted ".."+-- True+-- >>> isUnrooted "../x"+-- True+--+isUnrooted :: OS_PATH_TYPE -> Bool+isUnrooted = not . isRooted++#ifndef IS_WINDOWS+-- | Like 'join' but does not check if the second path is rooted.+--+-- >>> f a b = Path.toString $ Path.unsafeJoin (Path.fromString_ a) (Path.fromString_ b)+--+-- >>> f "x" "y"+-- "x/y"+-- >>> f "x/" "y"+-- "x/y"+-- >>> f "x" "/y"+-- "x/y"+-- >>> f "x/" "/y"+-- "x/y"+--+{-# INLINE unsafeJoin #-}+unsafeJoin :: OS_PATH_TYPE -> OS_PATH_TYPE -> OS_PATH_TYPE+unsafeJoin (OS_PATH a) (OS_PATH b) =+ OS_PATH+ $ Common.unsafeAppend+ Common.OS_NAME (Common.toString Unicode.UNICODE_DECODER) a b++-- If you want to avoid runtime failure use the typesafe+-- Streamly.FileSystem.OS_PATH_TYPE.Seg module.++-- | Append a separator followed by another path to a OS_PATH_TYPE. Fails if+-- the second path is a rooted path. Use 'unsafeJoin' to avoid failure if you+-- know it is ok to append the rooted path.+--+-- >>> f a b = Path.toString $ Path.join a b+--+-- >>> f [path|/usr|] [path|bin|]+-- "/usr/bin"+-- >>> f [path|/usr/|] [path|bin|]+-- "/usr/bin"+-- >>> fails (f [path|/usr|] [path|/bin|])+-- True+--+join :: OS_PATH_TYPE -> OS_PATH_TYPE -> OS_PATH_TYPE+join (OS_PATH a) (OS_PATH b) =+ OS_PATH+ $ Common.append+ Common.OS_NAME (Common.toString Unicode.UNICODE_DECODER) a b++-- | A stricter version of 'join' which requires the first path to be a+-- directory like path i.e. having a trailing separator.+--+-- >>> f a b = Path.toString $ Path.joinDir a b+--+-- >>> fails $ f [path|/usr|] [path|bin|]+-- True+--+joinDir ::+ OS_PATH_TYPE -> OS_PATH_TYPE -> OS_PATH_TYPE+joinDir+ (OS_PATH a) (OS_PATH b) =+ OS_PATH+ $ Common.append'+ Common.OS_NAME (Common.toString Unicode.UNICODE_DECODER) a b+#endif++-- XXX This can be pure, like append.+-- XXX add appendCWString for Windows?++#ifndef IS_WINDOWS+-- | Append a separator and a CString to the Array. This is like 'unsafeJoin'+-- but always inserts a separator between the two paths even if the first path+-- has a trailing separator or second path has a leading separator.+--+joinCStr :: OS_PATH_TYPE -> CString -> IO OS_PATH_TYPE+joinCStr (OS_PATH a) str =+ fmap OS_PATH+ $ Common.appendCString+ Common.OS_NAME a str++-- | Like 'joinCStr' but creates a pinned path.+--+joinCStr' ::+ OS_PATH_TYPE -> CString -> IO OS_PATH_TYPE+joinCStr'+ (OS_PATH a) str =+ fmap OS_PATH+ $ Common.appendCString'+ Common.OS_NAME a str+#endif++-- See unsafeJoinPaths in the Common path module, we need to avoid MonadIo from+-- that to implement this.++-- | Join paths by path separator. Does not check if the paths being appended+-- are rooted or branches. Note that splitting and joining may not give exactly+-- the original path but an equivalent path.+--+-- /Unimplemented/+unsafeJoinPaths :: [OS_PATH_TYPE] -> OS_PATH_TYPE+unsafeJoinPaths = undefined++------------------------------------------------------------------------------+-- Splitting path+------------------------------------------------------------------------------++#ifndef IS_WINDOWS+-- | If a path is rooted then separate the root and the remaining path,+-- otherwise return 'Nothing'. The non-root+-- part is guaranteed to NOT start with a separator.+--+-- Some filepath package equivalent idioms:+--+-- >>> splitDrive = Path.splitRoot+-- >>> joinDrive = Path.unsafeJoin+-- >>> takeDrive = fmap fst . Path.splitRoot+-- >>> dropDrive x = Path.splitRoot x >>= snd+-- >>> hasDrive = isJust . Path.splitRoot+-- >>> isDrive = isNothing . dropDrive+--+-- >>> toList (a,b) = (Path.toString a, fmap Path.toString b)+-- >>> split = fmap toList . Path.splitRoot . Path.fromString_+--+-- >>> split "/"+-- Just ("/",Nothing)+--+-- >>> split "."+-- Just (".",Nothing)+--+-- >>> split "./"+-- Just ("./",Nothing)+--+-- >>> split "/home"+-- Just ("/",Just "home")+--+-- >>> split "//"+-- Just ("//",Nothing)+--+-- >>> split "./home"+-- Just ("./",Just "home")+--+-- >>> split "home"+-- Nothing+--+splitRoot :: OS_PATH_TYPE -> Maybe (OS_PATH_TYPE, Maybe OS_PATH_TYPE)+splitRoot (OS_PATH x) =+ let (a,b) = Common.splitRoot Common.OS_NAME x+ in if Array.null a+ then Nothing+ else if Array.null b+ then Just (OS_PATH a, Nothing)+ else Just (OS_PATH a, Just (OS_PATH b))++-- | Split the path components keeping separators between path components+-- attached to the dir part. Redundant separators are removed, only the first+-- one is kept. Separators are not added either e.g. "." and ".." may not have+-- trailing separators if the original path does not.+--+-- >>> split = Stream.toList . fmap Path.toString . Path.splitPath . Path.fromString_+--+-- >>> split "."+-- ["."]+--+-- >>> split "././"+-- ["./"]+--+-- >>> split "./a/b/."+-- ["./","a/","b/"]+--+-- >>> split ".."+-- [".."]+--+-- >>> split "../"+-- ["../"]+--+-- >>> split "a/.."+-- ["a/",".."]+--+-- >>> split "/"+-- ["/"]+--+-- >>> split "//"+-- ["/"]+--+-- >>> split "/x"+-- ["/","x"]+--+-- >>> split "/./x/"+-- ["/","x/"]+--+-- >>> split "/x/./y"+-- ["/","x/","y"]+--+-- >>> split "/x/../y"+-- ["/","x/","../","y"]+--+-- >>> split "/x///y"+-- ["/","x/","y"]+--+-- >>> split "/x/\\y"+-- ["/","x/","\\y"]+--+{-# INLINE splitPath #-}+splitPath :: Monad m => OS_PATH_TYPE -> Stream m OS_PATH_TYPE+splitPath (OS_PATH a) = fmap OS_PATH $ Common.splitPath Common.OS_NAME a++-- | Split a path into components separated by the path separator. "."+-- components in the path are ignored except when in the leading position.+-- Trailing separators in non-root components are dropped.+--+-- >>> split = Stream.toList . fmap Path.toString . Path.splitPath_ . Path.fromString_+--+-- >>> split "."+-- ["."]+--+-- >>> split "././"+-- ["."]+--+-- >>> split ".//"+-- ["."]+--+-- >>> split "//"+-- ["/"]+--+-- >>> split "//x/y/"+-- ["/","x","y"]+--+-- >>> split "./a"+-- [".","a"]+--+-- >>> split "a/."+-- ["a"]+--+-- >>> split "/"+-- ["/"]+--+-- >>> split "/x"+-- ["/","x"]+--+-- >>> split "/./x/"+-- ["/","x"]+--+-- >>> split "/x/./y"+-- ["/","x","y"]+--+-- >>> split "/x/../y"+-- ["/","x","..","y"]+--+-- >>> split "/x///y"+-- ["/","x","y"]+--+-- >>> split "/x/\\y"+-- ["/","x","\\y"]+--+{-# INLINE splitPath_ #-}+splitPath_ :: Monad m => OS_PATH_TYPE -> Stream m OS_PATH_TYPE+splitPath_ (OS_PATH a) = fmap OS_PATH $ Common.splitPath_ Common.OS_NAME a+#endif++-- | If the path does not look like a directory then return @Just (Maybe dir,+-- file)@ otherwise return 'Nothing'. The path is not a directory if:+--+-- * the path does not end with a separator+-- * the path does not end with a . or .. component+--+-- Splits a single component path into @Just (Nothing, path)@ if it does not+-- look like a dir.+--+-- >>> toList (a,b) = (fmap Path.toString a, Path.toString b)+-- >>> split = fmap toList . Path.splitFile . Path.fromString_+--+-- >>> split "/"+-- Nothing+--+-- >>> split "."+-- Nothing+--+-- >>> split "/."+-- Nothing+--+-- >>> split ".."+-- Nothing+--+-- >> split "//" -- Posix+-- Nothing+--+-- >>> split "/home"+-- Just (Just "/","home")+--+-- >>> split "./home"+-- Just (Just "./","home")+--+-- >>> split "home"+-- Just (Nothing,"home")+--+-- >>> split "x/"+-- Nothing+--+-- >>> split "x/y"+-- Just (Just "x/","y")+--+-- >>> split "x//y"+-- Just (Just "x//","y")+--+-- >>> split "x/./y"+-- Just (Just "x/./","y")+splitFile :: OS_PATH_TYPE -> Maybe (Maybe OS_PATH_TYPE, OS_PATH_TYPE)+splitFile (OS_PATH a) =+ fmap (bimap (fmap OS_PATH) OS_PATH) $ Common.splitFile Common.OS_NAME a++-- | Split the path into the first component and rest of the path. Treats the+-- entire root or share name, if present, as the first component.+--+-- /Unimplemented/+splitFirst :: OS_PATH_TYPE -> (OS_PATH_TYPE, Maybe OS_PATH_TYPE)+splitFirst (OS_PATH a) =+ bimap OS_PATH (fmap OS_PATH) $ Common.splitHead Common.OS_NAME a++-- | Split the path into the last component and rest of the path. Treats the+-- entire root or share name, if present, as the first component.+--+-- >>> basename = snd . Path.splitLast -- Posix basename+-- >>> dirname = fst . Path.splitLast -- Posix dirname+--+-- /Unimplemented/+splitLast :: OS_PATH_TYPE -> (Maybe OS_PATH_TYPE, OS_PATH_TYPE)+splitLast (OS_PATH a) =+ bimap (fmap OS_PATH) OS_PATH $ Common.splitTail Common.OS_NAME a++#ifndef IS_WINDOWS+-- Note: In the cases of "x.y." and "x.y.." we return no extension rather+-- than ".y." or ".y.." as extensions. That is they considered to have no+-- extension.++-- | Returns @Just(filename, extension)@ if an extension is present otherwise+-- returns 'Nothing'.+--+-- A file name is considered to have an extension if the file name can be+-- split into a non-empty filename followed by the extension separator "."+-- followed by a non-empty extension with at least one character in addition to+-- the extension separator.+-- The shortest suffix obtained by this rule, starting with the+-- extension separator, is returned as the extension and the remaining prefix+-- part as the filename.+--+-- A directory name does not have an extension.+--+-- If you want a @splitExtensions@, you can use splitExtension until the+-- extension returned is Nothing. @dropExtensions@, @isExtensionOf@ can be+-- implemented similarly.+--+-- >>> toList (a,b) = (Path.toString a, Path.toString b)+-- >>> split = fmap toList . Path.splitExtension . Path.fromString_+--+-- >>> split "/"+-- Nothing+--+-- >>> split "."+-- Nothing+--+-- >>> split ".."+-- Nothing+--+-- >>> split "x"+-- Nothing+--+-- >>> split "/x"+-- Nothing+--+-- >>> split "x/"+-- Nothing+--+-- >>> split "./x"+-- Nothing+--+-- >>> split "x/."+-- Nothing+--+-- >>> split "x/y."+-- Nothing+--+-- >>> split "/x.y"+-- Just ("/x",".y")+--+-- >>> split "/x.y."+-- Nothing+--+-- >>> split "/x.y.."+-- Nothing+--+-- >>> split "x/.y"+-- Nothing+--+-- >>> split ".x"+-- Nothing+--+-- >>> split "x."+-- Nothing+--+-- >>> split ".x.y"+-- Just (".x",".y")+--+-- >>> split "x/y.z"+-- Just ("x/y",".z")+--+-- >>> split "x.y.z"+-- Just ("x.y",".z")+--+-- >>> split "x..y"+-- Just ("x.",".y")+--+-- >>> split "..."+-- Nothing+--+-- >>> split "..x"+-- Just (".",".x")+--+-- >>> split "...x"+-- Just ("..",".x")+--+-- >>> split "x/y.z/"+-- Nothing+--+-- >>> split "x/y"+-- Nothing+--+splitExtension :: OS_PATH_TYPE -> Maybe (OS_PATH_TYPE, OS_PATH_TYPE)+splitExtension (OS_PATH a) =+ fmap (bimap OS_PATH OS_PATH) $ Common.splitExtension Common.OS_NAME a+#endif++-- | Take the extension of a file if it has one.+--+-- >>> takeExtension = fmap snd . Path.splitExtension+-- >>> hasExtension = isJust . Path.splitExtension+--+-- >>> fmap Path.toString $ Path.takeExtension [path|/home/user/file.txt|]+-- Just ".txt"+--+-- See 'splitExtension' for more examples.+takeExtension :: OS_PATH_TYPE -> Maybe OS_PATH_TYPE+takeExtension = fmap snd . splitExtension++-- | Drop the extension of a file if it has one.+--+-- >>> dropExtension p = maybe p fst $ Path.splitExtension p+--+-- >>> Path.toString $ Path.dropExtension [path|/home/user/file.txt|]+-- "/home/user/file"+--+dropExtension :: OS_PATH_TYPE -> OS_PATH_TYPE+dropExtension orig@(OS_PATH a) =+ maybe orig (OS_PATH . fst) $ Common.splitExtension Common.OS_NAME a++-- | Add an extension to a file path. If a non-empty extension does not start+-- with a leading dot then a dot is inserted, otherwise the extension is+-- concatenated with the path.+--+-- It is an error to add an extension to a path with a trailing separator.+--+-- /Unimplemented/+addExtension :: OS_PATH_TYPE -> OS_PATH_TYPE -> OS_PATH_TYPE+addExtension (OS_PATH _a) = undefined++-- /Unimplemented/+replaceExtension :: OS_PATH_TYPE -> OS_PATH_TYPE -> OS_PATH_TYPE+replaceExtension (OS_PATH _a) = undefined++------------------------------------------------------------------------------+-- Path View+------------------------------------------------------------------------------++-- | Extracts the file name component (with extension) from a OS_PATH_TYPE, if+-- present.+--+-- >>> takeFileName = fmap snd . Path.splitFile+-- >>> replaceDirectory p x = fmap (flip Path.join x) (takeFileName p)+--+-- >>> fmap Path.toString $ Path.takeFileName [path|/home/user/file.txt|]+-- Just "file.txt"+-- >>> fmap Path.toString $ Path.takeFileName [path|/home/user/|]+-- Nothing+--+-- See 'splitFile' for more examples.+--+takeFileName :: OS_PATH_TYPE -> Maybe OS_PATH_TYPE+takeFileName = fmap snd . splitFile++-- | Extracts the file name dropping the extension, if any, from a+-- OS_PATH_TYPE.+--+-- >>> takeFileBase = fmap Path.dropExtension . Path.takeFileName+--+-- >>> fmap Path.toString $ Path.takeFileBase [path|/home/user/file.txt|]+-- Just "file"+-- >>> fmap Path.toString $ Path.takeFileBase [path|/home/user/file|]+-- Just "file"+-- >>> fmap Path.toString $ Path.takeFileBase [path|/home/user/.txt|]+-- Just ".txt"+-- >>> fmap Path.toString $ Path.takeFileBase [path|/home/user/|]+-- Nothing+--+-- See 'splitFile' for more examples.+--+takeFileBase :: OS_PATH_TYPE -> Maybe OS_PATH_TYPE+takeFileBase = fmap dropExtension . takeFileName++-- | Returns the parent directory of the given OS_PATH_TYPE, if any.+--+-- >>> takeDirectory x = Path.splitFile x >>= fst+-- >>> replaceFileName p x = fmap (flip Path.join x) (takeDirectory p)+--+-- To get an equivalent to takeDirectory from filepath use+-- 'dropTrailingSeparators' on the result.+--+-- >>> fmap Path.toString $ Path.takeDirectory [path|/home/user/file.txt|]+-- Just "/home/user/"+-- >>> fmap Path.toString $ Path.takeDirectory [path|file.txt|]+-- Nothing+--+takeDirectory :: OS_PATH_TYPE -> Maybe OS_PATH_TYPE+takeDirectory x = splitFile x >>= fst++------------------------------------------------------------------------------+-- Path equality+------------------------------------------------------------------------------++#ifndef IS_WINDOWS+-- | Default equality check configuration.+--+-- >>> :{+-- eqCfg =+-- Path.ignoreTrailingSeparators False+-- . Path.ignoreCase False+-- . Path.allowRelativeEquality False+-- :}+--+#else+-- | Default equality check configuration.+--+-- >>> :{+-- eqCfg =+-- Path.ignoreTrailingSeparators False+-- . Path.ignoreCase True+-- . Path.allowRelativeEquality False+-- :}+--+#endif+eqCfg :: EqCfg+eqCfg = Common.EqCfg+ { _ignoreTrailingSeparators = False+ , _allowRelativeEquality = False+#ifndef IS_WINDOWS+ , _ignoreCase = False+#else+ , _ignoreCase = True+#endif+ }++-- | When set to 'False' (default):+--+-- >>> cfg = Path.ignoreTrailingSeparators False+-- >>> eq a b = Path.eqPath cfg (Path.fromString_ a) (Path.fromString_ b)+--+-- >>> eq "x/" "x"+-- False+--+-- When set to 'True':+--+-- >>> cfg = Path.ignoreTrailingSeparators True+-- >>> eq a b = Path.eqPath cfg (Path.fromString_ a) (Path.fromString_ b)+--+-- >>> eq "x/" "x"+-- True+--+-- /Default/: False+ignoreTrailingSeparators :: Bool -> EqCfg -> EqCfg+ignoreTrailingSeparators val conf = conf { _ignoreTrailingSeparators = val }++-- | When set to 'False', comparison is case sensitive.+--+-- /Posix Default/: False+--+-- /Windows Default/: True+ignoreCase :: Bool -> EqCfg -> EqCfg+ignoreCase val conf = conf { _ignoreCase = val }++-- Note: ignoreLeadingDot or similar names are not good because we want to+-- convey that when it is False "./x" and "./x" are not strictly equal.+-- Similarly, "treatDotRootsEqual" has a problem with the "./x" and "x"+-- comparison, there is not dor root in the second path.++-- | Allow relative paths to be treated as equal. When this is 'False' relative+-- paths will never match even if they are literally equal e.g. "./x" will not+-- match "./x" because the meaning of "." in both cases could be different+-- depending on what the user meant by current directory in each case.+--+-- When set to 'False' (default):+--+-- >>> cfg = Path.allowRelativeEquality False+-- >>> eq a b = Path.eqPath cfg (Path.fromString_ a) (Path.fromString_ b)+-- >>> eq "." "."+-- False+-- >>> eq "./x" "./x"+-- False+-- >>> eq "./x" "x"+-- False+--+-- When set to 'False' (default):+--+-- >>> cfg = Path.allowRelativeEquality True+-- >>> eq a b = Path.eqPath cfg (Path.fromString_ a) (Path.fromString_ b)+-- >>> eq "." "."+-- True+-- >>> eq "./x" "./x"+-- True+-- >>> eq "./x" "x"+-- True+--+-- >>> eq "./x" "././x"+-- True+--+-- /Default/: False+allowRelativeEquality :: Bool -> EqCfg -> EqCfg+allowRelativeEquality val conf = conf { _allowRelativeEquality = val }++#ifndef IS_WINDOWS+-- | Checks whether two paths are logically equal. This function takes a+-- configuration modifier to customize the notion of equality. For using the+-- default configuration pass 'id' as the modifier. For details about the+-- defaults, see 'EqCfg'.+--+-- eqPath performs some normalizations on the paths before comparing them,+-- specifically it drops redundant path separators between path segments and+-- redundant "\/.\/" components between segments.+--+-- Default config options use strict equality, for strict equality both the+-- paths must be absolute or both must be path segments without a leading root+-- component (e.g. x\/y). Also, both must be files or both must be directories.+--+-- In addition to the default config options, the following equality semantics+-- are used:+--+-- * An absolute path and a path relative to "." may be equal depending on the+-- meaning of ".", however this routine treats them as unequal, it does not+-- resolve the "." to a concrete path.+--+-- * Two paths having ".." components may be equal after processing the ".."+-- components even if we determined them to be unequal. However, if we+-- determined them to be equal then they must be equal.+--+-- Using default config with case sensitive comparision, if eqPath returns+-- equal then the paths are definitely equal, if it returns unequal then the+-- paths may still be equal under some relaxed equality criterion.+--+-- >>> :{+-- eq a b = Path.eqPath id (Path.fromString_ a) (Path.fromString_ b)+-- :}+--+-- >>> eq "x" "x"+-- True+-- >>> eq ".." ".."+-- True+--+-- Non-trailing separators and non-leading "." segments are ignored:+--+-- >>> eq "/x" "//x"+-- True+-- >>> eq "x//y" "x/y"+-- True+-- >>> eq "x/./y" "x/y"+-- True+-- >>> eq "x/y/." "x/y"+-- True+--+-- Leading dot, relative paths are not equal by default:+--+-- >>> eq "." "."+-- False+-- >>> eq "./x" "./x"+-- False+-- >>> eq "./x" "x"+-- False+--+-- Trailing separators are significant by default:+--+-- >>> eq "x/" "x"+-- False+--+-- Match is case sensitive by default:+--+-- >>> eq "x" "X"+-- False+--+eqPath :: (EqCfg -> EqCfg) -> OS_PATH_TYPE -> OS_PATH_TYPE -> Bool+eqPath cfg (OS_PATH a) (OS_PATH b) =+ Common.eqPath Unicode.UNICODE_DECODER+ Common.OS_NAME (cfg eqCfg) a b+#endif++-- | Check two paths for byte level equality. This is the most strict path+-- equality check.+--+-- >>> eqPath a b = Path.eqPathBytes (Path.fromString_ a) (Path.fromString_ b)+--+-- >>> eqPath "x//y" "x//y"+-- True+--+-- >>> eqPath "x//y" "x/y"+-- False+--+-- >>> eqPath "x/./y" "x/y"+-- False+--+-- >>> eqPath "x\\y" "x/y"+-- False+--+-- >>> eqPath "./file" "file"+-- False+--+-- >>> eqPath "file/" "file"+-- False+--+eqPathBytes :: OS_PATH_TYPE -> OS_PATH_TYPE -> Bool+eqPathBytes (OS_PATH a) (OS_PATH b) = Common.eqPathBytes a b++-- | Convert the path to an equivalent but standard format for reliable+-- comparison. This can be implemented if required. Usually, the equality+-- operations should be enough and this may not be needed.+--+-- /Unimplemented/+normalize :: EqCfg -> OS_PATH_TYPE -> OS_PATH_TYPE+normalize _cfg (OS_PATH _a) = undefined
+ src/Streamly/Internal/FileSystem/PosixPath/Node.hs view
@@ -0,0 +1,162 @@+{-# LANGUAGE TemplateHaskell #-}+-- For constraints on "append"+{-# OPTIONS_GHC -Wno-redundant-constraints #-}++#if defined(IS_WINDOWS)+#define OS_NAME Windows+#define OS_PATH WindowsPath+#else+#define OS_NAME Posix+#define OS_PATH PosixPath+#endif++-- |+-- Module : Streamly.Internal.FileSystem.OS_PATH.Node+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--+-- This module provides a type safe path append operation by distinguishing+-- paths between files and directories. Files are represented by the @File+-- OS_PATH@ type and directories are represented by the @Dir OS_PATH@ type.+--+-- This distinction provides safety against appending a path to a file. Append+-- operation allows appending to only 'Dir' types.+--+module Streamly.Internal.FileSystem.OS_PATH.Node+ (+ -- * Types+ File (..)+ , Dir (..)+ , IsNode++ -- * Statically Verified Path Literals+ -- | Quasiquoters.+ , dir+ , file++ -- * Statically Verified Path Strings+ -- | Template Haskell expression splices.+ , dirE+ , fileE++ -- * Operations+ , join+ )+where++import Control.Monad ((>=>))+import Language.Haskell.TH (Q, Exp)+import Language.Haskell.TH.Syntax (lift)+import Language.Haskell.TH.Quote (QuasiQuoter)+import Streamly.Internal.Data.Path (IsPath(..))+import Streamly.Internal.FileSystem.Path.Common (OS(..), mkQ)+import Streamly.Internal.FileSystem.OS_PATH (OS_PATH(..))++import qualified Streamly.Internal.FileSystem.Path.Common as Common+import qualified Streamly.Internal.FileSystem.OS_PATH as OsPath++{- $setup+>>> :m+>>> :set -XQuasiQuotes++For APIs that have not been released yet.++>>> import Streamly.Internal.FileSystem.PosixPath (PosixPath)+>>> import Streamly.Internal.FileSystem.PosixPath.Node (File, Dir, file, dir)+>>> import qualified Streamly.Internal.FileSystem.PosixPath as Path+>>> import qualified Streamly.Internal.FileSystem.PosixPath.Node as Node+-}++newtype File a = File a+newtype Dir a = Dir a++-- | Constraint to check if a type uses 'File' or 'Dir' as the outermost+-- constructor.+class IsNode a++instance IsNode (File a)+instance IsNode (Dir a)++instance IsPath OS_PATH (File OS_PATH) where+ unsafeFromPath = File++ fromPath p@(OS_PATH arr) = do+ !_ <- Common.validateFile OS_NAME arr+ pure $ File p++ toPath (File p) = p++instance IsPath OS_PATH (Dir OS_PATH) where+ unsafeFromPath = Dir+ fromPath p = pure (Dir p)+ toPath (Dir p) = p++------------------------------------------------------------------------------+-- Statically Verified Strings+------------------------------------------------------------------------------++-- XXX We can lift the array directly, ByteArray has a lift instance. Does that+-- work better?++liftDir :: Dir OS_PATH -> Q Exp+liftDir (Dir p) =+ [| unsafeFromPath (OsPath.unsafeFromString $(lift $ OsPath.toString $ toPath p)) :: Dir OS_PATH |]++liftFile :: File OS_PATH -> Q Exp+liftFile (File p) =+ [| unsafeFromPath (OsPath.unsafeFromString $(lift $ OsPath.toString $ toPath p)) :: File OS_PATH |]++-- | Generates a Haskell expression of type @Dir OS_PATH@.+--+dirE :: String -> Q Exp+dirE = either (error . show) liftDir . (OsPath.fromString >=> fromPath)++-- | Generates a Haskell expression of type @File OS_PATH@.+--+fileE :: String -> Q Exp+fileE = either (error . show) liftFile . (OsPath.fromString >=> fromPath)++------------------------------------------------------------------------------+-- Statically Verified Literals+------------------------------------------------------------------------------++-- XXX Define folds or parsers to parse the paths.+-- XXX Build these on top of the str quasiquoter so that we get interpolation+-- for free. Interpolated vars if any have to be of appropriate type depending+-- on the context so that we can splice them safely.++-- | Generates a @Dir OS_PATH@ type from a quoted literal.+--+-- >>> Path.toString (Path.toPath ([dir|usr|] :: Dir PosixPath))+-- "usr"+--+dir :: QuasiQuoter+dir = mkQ dirE++-- | Generates a @File OS_PATH@ type from a quoted literal.+--+-- >>> Path.toString (Path.toPath ([file|usr|] :: File PosixPath))+-- "usr"+--+file :: QuasiQuoter+file = mkQ fileE++-- The only safety we need for paths is: (1) The first path can only be a Dir+-- type path, and (2) second path can only be a Seg path.++-- | Append a 'Dir' or 'File' path to a 'Dir' path.+--+-- >>> Path.toString (Path.toPath (Node.join [dir|/usr|] [dir|bin|] :: Dir PosixPath))+-- "/usr/bin"+-- >>> Path.toString (Path.toPath (Node.join [dir|/usr|] [file|bin|] :: File PosixPath))+-- "/usr/bin"+--+-- Fails if the second path is a specific location and not a path segment.+--+{-# INLINE join #-}+join :: (IsPath OS_PATH (a OS_PATH), IsNode (a OS_PATH)) =>+ Dir OS_PATH -> a OS_PATH -> a OS_PATH+join (Dir a) b =+ unsafeFromPath $ OsPath.unsafeJoin (toPath a) (toPath b)
+ src/Streamly/Internal/FileSystem/PosixPath/Seg.hs view
@@ -0,0 +1,172 @@+{-# LANGUAGE TemplateHaskell #-}+-- For constraints on "append"+{-# OPTIONS_GHC -Wno-redundant-constraints #-}++#if defined(IS_WINDOWS)+#define OS_NAME Windows+#define OS_PATH WindowsPath+#else+#define OS_NAME Posix+#define OS_PATH PosixPath+#endif++-- |+-- Module : Streamly.Internal.FileSystem.OS_PATH.Seg+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--+-- This module provides a type safe path append operation by distinguishing+-- paths between rooted paths and branches. Rooted paths are represented by the+-- @Rooted OS_PATH@ type and branches are represented by the @Unrooted OS_PATH@+-- type. Rooted paths are paths that are attached to specific roots in the file+-- system. Rooted paths could be absolute or relative e.g. @\/usr\/bin@,+-- @.\/local\/bin@, or @.@. Unrootedes are a paths that are not attached to a+-- specific root e.g. @usr\/bin@, @local\/bin@, or @../bin@ are branches.+--+-- This distinction provides a safe path append operation which cannot fail.+-- These types do not allow appending a rooted path to any other path. Only+-- branches can be appended.+--+module Streamly.Internal.FileSystem.OS_PATH.Seg+ (+ -- * Types+ Rooted (..)+ , Unrooted (..)+ , IsSeg++ -- * Statically Verified Path Literals+ -- | Quasiquoters.+ , rt+ , ur++ -- * Statically Verified Path Strings+ -- | Template Haskell expression splices.+ , rtE+ , urE++ -- * Operations+ , join+ )+where++import Control.Monad ((>=>))+import Control.Monad.Catch (MonadThrow(..))+import Language.Haskell.TH (Q, Exp)+import Language.Haskell.TH.Syntax (lift)+import Language.Haskell.TH.Quote (QuasiQuoter)+import Streamly.Internal.Data.Path (IsPath(..), PathException(..))+import Streamly.Internal.FileSystem.Path.Common (mkQ)+import Streamly.Internal.FileSystem.OS_PATH (OS_PATH(..))++import qualified Streamly.Internal.FileSystem.OS_PATH as OsPath++{- $setup+>>> :m+>>> :set -XQuasiQuotes++For APIs that have not been released yet.++>>> import Streamly.Internal.FileSystem.PosixPath (PosixPath)+>>> import Streamly.Internal.FileSystem.PosixPath.Seg (Rooted, Unrooted, rt, ur)+>>> import qualified Streamly.Internal.FileSystem.PosixPath as Path+>>> import qualified Streamly.Internal.FileSystem.PosixPath.Seg as Seg+-}++newtype Rooted a = Rooted a+newtype Unrooted a = Unrooted a++instance IsPath OS_PATH (Rooted OS_PATH) where+ unsafeFromPath = Rooted+ fromPath p =+ if OsPath.isRooted p+ then pure (Rooted p)+ -- XXX Add more detailed error msg with all valid examples.+ else throwM $ InvalidPath+ $ "Must be a specific location, not a path segment: "+ ++ OsPath.toString p+ toPath (Rooted p) = p++instance IsPath OS_PATH (Unrooted OS_PATH) where+ unsafeFromPath = Unrooted+ fromPath p =+ if OsPath.isUnrooted p+ then pure (Unrooted p)+ -- XXX Add more detailed error msg with all valid examples.+ else throwM $ InvalidPath+ $ "Must be a path segment, not a specific location: "+ ++ OsPath.toString p+ toPath (Unrooted p) = p++-- | Constraint to check if a type has Rooted or Unrooted annotations.+class IsSeg a++instance IsSeg (Rooted a)+instance IsSeg (Unrooted a)++------------------------------------------------------------------------------+-- Statically Verified Strings+------------------------------------------------------------------------------++liftRooted :: Rooted OS_PATH -> Q Exp+liftRooted (Rooted p) =+ [| unsafeFromPath (OsPath.unsafeFromString $(lift $ OsPath.toString $ toPath p)) :: Rooted OS_PATH |]++liftUnrooted :: Unrooted OS_PATH -> Q Exp+liftUnrooted (Unrooted p) =+ [| unsafeFromPath (OsPath.unsafeFromString $(lift $ OsPath.toString $ toPath p)) :: Unrooted OS_PATH |]++-- | Generates a Haskell expression of type @Rooted OS_PATH@.+--+rtE :: String -> Q Exp+rtE = either (error . show) liftRooted . (OsPath.fromString >=> fromPath)++-- | Generates a Haskell expression of type @Unrooted OS_PATH@.+--+urE :: String -> Q Exp+urE = either (error . show) liftUnrooted . (OsPath.fromString >=> fromPath)++------------------------------------------------------------------------------+-- Statically Verified Literals+------------------------------------------------------------------------------++-- XXX Define folds or parsers to parse the paths.+-- XXX Build these on top of the str quasiquoter so that we get interpolation+-- for free. Interpolated vars if any have to be of appropriate type depending+-- on the context so that we can splice them safely.++-- | Generates a @Rooted Path@ type from a quoted literal.+--+-- >>> Path.toString (Path.toPath ([rt|/usr|] :: Rooted PosixPath))+-- "/usr"+--+rt :: QuasiQuoter+rt = mkQ rtE++-- | Generates a @Unrooted Path@ type from a quoted literal.+--+-- >>> Path.toString (Path.toPath ([ur|usr|] :: Unrooted PosixPath))+-- "usr"+--+ur :: QuasiQuoter+ur = mkQ urE++-- The only safety we need for paths is: (1) The first path can only be a Dir+-- type path, and (2) second path can only be a Unrooted path.++-- | Append a 'Unrooted' type path to a 'Rooted' path or 'Unrooted' path.+--+-- >>> Path.toString (Path.toPath (Seg.join [rt|/usr|] [ur|bin|] :: Rooted PosixPath))+-- "/usr/bin"+-- >>> Path.toString (Path.toPath (Seg.join [ur|usr|] [ur|bin|] :: Unrooted PosixPath))+-- "usr/bin"+--+{-# INLINE join #-}+join ::+ (+ IsSeg (a OS_PATH)+ , IsPath OS_PATH (a OS_PATH)+ ) => a OS_PATH -> Unrooted OS_PATH -> a OS_PATH+join a (Unrooted c) =+ unsafeFromPath $ OsPath.unsafeJoin (toPath a) (toPath c)
+ src/Streamly/Internal/FileSystem/PosixPath/SegNode.hs view
@@ -0,0 +1,311 @@+{-# LANGUAGE TemplateHaskell #-}+-- For constraints on "join"+{-# OPTIONS_GHC -Wno-redundant-constraints #-}+{-# OPTIONS_GHC -Wno-orphans #-}++#if defined(IS_WINDOWS)+#define OS_NAME Windows+#define OS_PATH WindowsPath+#else+#define OS_NAME Posix+#define OS_PATH PosixPath+#endif++-- |+-- Module : Streamly.Internal.FileSystem.OS_PATH.SegNode+-- Copyright : (c) 2023 Composewell Technologies+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Portability : GHC+--+-- When @Rooted/Unrooted@ and @File/Dir@ both are present, @Rooted/Unrooted@ must be+-- outermost constructors and @File/Dir@ as inner. Thus the types File (Rooted+-- a) or Dir (Rooted a) are not allowed but Rooted (Dir a) and Rooted (File a) are+-- allowed.++module Streamly.Internal.FileSystem.OS_PATH.SegNode+ (+ -- * Statically Verified Path Literals+ -- | Quasiquoters.+ rtdir+ , urdir+ , rtfile+ , urfile++ -- * Statically Verified Path Strings+ -- | Template Haskell expression splices.+ , rtdirE+ , urdirE+ , rtfileE+ , urfileE++ -- * Operations+ , join+ )+where++import Control.Monad ((>=>))+import Language.Haskell.TH.Syntax (lift)+import Streamly.Internal.FileSystem.Path.Common (mkQ)+import Streamly.Internal.FileSystem.OS_PATH (OS_PATH(..))+import Streamly.Internal.FileSystem.OS_PATH.Seg (Rooted(..), Unrooted(..))+import Streamly.Internal.FileSystem.OS_PATH.Node (File(..), Dir(..))++import qualified Streamly.Internal.FileSystem.OS_PATH as OsPath++import Language.Haskell.TH+import Language.Haskell.TH.Quote+import Streamly.Internal.Data.Path++{- $setup+>>> :m+>>> :set -XQuasiQuotes++For APIs that have not been released yet.++>>> import Streamly.Internal.FileSystem.PosixPath (PosixPath)+>>> import Streamly.Internal.FileSystem.PosixPath.Node (Dir, File, dir, file)+>>> import Streamly.Internal.FileSystem.PosixPath.Seg (Rooted, Unrooted, rt, ur)+>>> import Streamly.Internal.FileSystem.PosixPath.SegNode (rtdir, urdir, rtfile, urfile)+>>> import qualified Streamly.Internal.FileSystem.PosixPath as Path+>>> import qualified Streamly.Internal.FileSystem.PosixPath.SegNode as SegNode+-}++-- Note that (Rooted a) may also be a directory if "a" is (Dir b), but it can also+-- be a file if "a" is (File b). Therefore, the constraints are put on a more+-- specific type e.g. (Rooted OS_PATH) may be a dir.++{-+-- | Constraint to check if a type represents a directory.+class HasDir a++instance HasDir (Dir a)+instance HasDir (Rooted (Dir a))+instance HasDir (Unrooted (Dir a))+-}++-- Design notes:+--+-- There are two ways in which we can lift or upgrade a lower level path to a+-- higher level one. Lift each type directly from the base path e.g. Rooted (Dir+-- PosixPath) can be created directly from PosixPath. This allows us to do dir+-- checks and loc checks at the same time in a monolithic manner. But this also+-- makes us do the Dir checks again if we are lifting from Dir to Rooted. This+-- leads to less complicated constraints, more convenient type conversions.+--+-- Another alternative is to lift one segment at a time, so we lift PosixPath+-- to Dir and then Dir to Rooted. This way the checks are serialized, we perform+-- the dir checks first and then Rooted checks, we cannot combine them together.+-- The advantage is that when lifting from Dir to Rooted we do not need to do the+-- Dir checks. The disadvantage is less convenient conversion because of+-- stronger typing, we will need two steps - fromPath . fromPath and toPath .+-- toPath to upgrade or downgrade instead of just adapt.+--+{-+instance IsPath (File OS_PATH) (Rooted (File OS_PATH)) where+ unsafeFromPath = Rooted+ fromPath (File p) = do+ _ :: Rooted OS_PATH <- fromPath p+ pure $ Rooted (File p)+ toPath (Rooted p) = p++instance IsPath (Rooted OS_PATH) (Rooted (File OS_PATH)) where+ unsafeFromPath = Rooted+ fromPath (File p) = do+ _ :: File OS_PATH <- fromPath p+ pure $ Rooted (File p)+ toPath (Rooted p) = p+-}++-- Assuming that lifting from Dir/File to Rooted/Unrooted is not common and even if it+-- is then the combined cost of doing Dir/Rooted checks would be almost the same+-- as individual checks, we take the first approach.++instance IsPath OS_PATH (Rooted (File OS_PATH)) where+ unsafeFromPath p = Rooted (File p)+ fromPath p = do+ _ :: File OS_PATH <- fromPath p+ _ :: Rooted OS_PATH <- fromPath p+ pure $ Rooted (File p)+ toPath (Rooted (File p)) = p++instance IsPath OS_PATH (Rooted (Dir OS_PATH)) where+ unsafeFromPath p = Rooted (Dir p)+ fromPath p = do+ _ :: Dir OS_PATH <- fromPath p+ _ :: Rooted OS_PATH <- fromPath p+ pure $ Rooted (Dir p)+ toPath (Rooted (Dir p)) = p++instance IsPath OS_PATH (Unrooted (File OS_PATH)) where+ unsafeFromPath p = Unrooted (File p)+ fromPath p = do+ _ :: File OS_PATH <- fromPath p+ _ :: Unrooted OS_PATH <- fromPath p+ pure $ Unrooted (File p)+ toPath (Unrooted (File p)) = p++instance IsPath OS_PATH (Unrooted (Dir OS_PATH)) where+ unsafeFromPath p = Unrooted (Dir p)+ fromPath p = do+ _ :: Dir OS_PATH <- fromPath p+ _ :: Unrooted OS_PATH <- fromPath p+ pure $ Unrooted (Dir p)+ toPath (Unrooted (Dir p)) = p++------------------------------------------------------------------------------+-- Statically Verified Strings+------------------------------------------------------------------------------++-- XXX We can lift the array directly, ByteArray has a lift instance. Does that+-- work better?++liftRootedDir :: Rooted (Dir OS_PATH) -> Q Exp+liftRootedDir (Rooted (Dir p)) =+ [| unsafeFromPath (OsPath.unsafeFromString $(lift $ OsPath.toString $ toPath p)) :: Rooted (Dir OS_PATH)|]++liftUnrootedDir :: Unrooted (Dir OS_PATH) -> Q Exp+liftUnrootedDir (Unrooted (Dir p)) =+ [| unsafeFromPath (OsPath.unsafeFromString $(lift $ OsPath.toString $ toPath p)) :: Unrooted (Dir OS_PATH) |]++liftRootedFile :: Rooted (File OS_PATH) -> Q Exp+liftRootedFile (Rooted (File p)) =+ [| unsafeFromPath (OsPath.unsafeFromString $(lift $ OsPath.toString $ toPath p)) :: Rooted (File OS_PATH)|]++liftUnrootedFile :: Unrooted (File OS_PATH) -> Q Exp+liftUnrootedFile (Unrooted (File p)) =+ [| unsafeFromPath (OsPath.unsafeFromString $(lift $ OsPath.toString $ toPath p)) :: Unrooted (File OS_PATH)|]++-- | Generates a Haskell expression of type @Rooted (Dir OS_PATH)@.+--+rtdirE :: String -> Q Exp+rtdirE = either (error . show) liftRootedDir . (OsPath.fromString >=> fromPath)++-- | Generates a Haskell expression of type @Unrooted (Dir OS_PATH)@.+--+urdirE :: String -> Q Exp+urdirE = either (error . show) liftUnrootedDir . (OsPath.fromString >=> fromPath)++-- | Generates a Haskell expression of type @Rooted (File OS_PATH)@.+--+rtfileE :: String -> Q Exp+rtfileE = either (error . show) liftRootedFile . (OsPath.fromString >=> fromPath)++-- | Generates a Haskell expression of type @Unrooted (File OS_PATH)@.+--+urfileE :: String -> Q Exp+urfileE = either (error . show) liftUnrootedFile . (OsPath.fromString >=> fromPath)++------------------------------------------------------------------------------+-- Statically Verified Literals+------------------------------------------------------------------------------++-- XXX Define folds or parsers to parse the paths.+-- XXX Build these on top of the str quasiquoter so that we get interpolation+-- for free. Interpolated vars if any have to be of appropriate type depending+-- on the context so that we can splice them safely.++-- | Generates a @Rooted (Dir OS_PATH)@ type from a quoted literal.+--+-- >>> Path.toString (Path.toPath ([rtdir|/usr|] :: Rooted (Dir PosixPath)))+-- "/usr"+--+rtdir :: QuasiQuoter+rtdir = mkQ rtdirE++-- | Generates a @Unrooted (Dir OS_PATH)@ type from a quoted literal.+--+-- >>> Path.toString (Path.toPath ([urdir|usr|] :: Unrooted (Dir PosixPath)))+-- "usr"+--+urdir :: QuasiQuoter+urdir = mkQ urdirE++-- | Generates a @Rooted (File OS_PATH)@ type from a quoted literal.+--+-- >>> Path.toString (Path.toPath ([rtfile|/x.txt|] :: Rooted (File PosixPath)))+-- "/x.txt"+--+rtfile :: QuasiQuoter+rtfile = mkQ rtfileE++-- | Generates a @Unrooted (File OS_PATH)@ type from a quoted literal.+--+-- >>> Path.toString (Path.toPath ([urfile|x.txt|] :: Unrooted (File PosixPath)))+-- "x.txt"+--+urfile :: QuasiQuoter+urfile = mkQ urfileE++-- The only safety we need for paths is: (1) The first path can only be a Dir+-- type path, and (2) second path can only be a Unrooted path.++{-+-- If the first path is 'Rooted' then the return type is also 'Rooted'.+--+-- If the second path does not have 'File' or 'Dir' information then the return+-- type too cannot have it.+--+-- >> Path.toString (Path.toPath (SegNode.join [rtdir|/usr|] [br|bin|] :: Rooted PosixPath))+-- "/usr/bin"+-- >> Path.toString (Path.toPath (SegNode.join [urdir|usr|] [br|bin|] :: Unrooted PosixPath))+-- "usr/bin"+--+-- >> Path.toString (Path.toPath (SegNode.join [rt|/usr|] [br|bin|] :: Rooted PosixPath))+-- "/usr/bin"+-- >> Path.toString (Path.toPath (SegNode.join [br|usr|] [br|bin|] :: Unrooted PosixPath))+-- "usr/bin"+--+-- If the second path has 'File' or 'Dir' information then the return type+-- also has it.+--+-- >> Path.toString (Path.toPath (SegNode.join [rt|/usr|] [urdir|bin|] :: Rooted (Dir PosixPath)))+-- "/usr/bin"+-- >> Path.toString (Path.toPath (SegNode.join [rt|/usr|] [urfile|bin|] :: Rooted (File PosixPath)))+-- "/usr/bin"+-- >> Path.toString (Path.toPath (SegNode.join [br|usr|] [urdir|bin|] :: Unrooted (Dir PosixPath)))+-- "usr/bin"+-- >> Path.toString (Path.toPath (SegNode.join [br|usr|] [urfile|bin|] :: Unrooted (File PosixPath)))+-- "usr/bin"+--+-- Type error cases:+--+-- >> SegNode.join [dir|/usr|] [br|bin|] -- first arg must be Rooted/Unrooted+-- >> SegNode.join [file|/usr|] [br|bin|] -- first arg must be Rooted/Unrooted+-- >> SegNode.join [rtfile|/usr|] [br|bin|] -- first arg must be a dir+-- >> SegNode.join [rt|/usr|] [rt|/bin|] -- second arg must be seg+-- >> SegNode.join [rt|/usr|] [dir|bin|] -- second arg must be seg+-- >> SegNode.join [rt|/usr|] [file|bin|] -- second arg must be seg+--+{-# INLINE join #-}+join ::+ (+ IsSeg (a b)+ , HasDir (a b)+ , IsPath OS_PATH (a b)+ , IsPath OS_PATH c+ , IsPath OS_PATH (a c)+ ) => a b -> Unrooted c -> a c+join a (Unrooted c) = unsafeFromPath $ OS_NAME.unsafeJoin (toPath a) (toPath c)+-}++-- | Append a branch type path to a directory.+--+-- >>> Path.toString (Path.toPath (SegNode.join [rtdir|/usr|] [urdir|bin|] :: Rooted (Dir PosixPath)))+-- "/usr/bin"+-- >>> Path.toString (Path.toPath (SegNode.join [rtdir|/usr|] [urfile|bin|] :: Rooted (File PosixPath)))+-- "/usr/bin"+-- >>> Path.toString (Path.toPath (SegNode.join [urdir|usr|] [urdir|bin|] :: Unrooted (Dir PosixPath)))+-- "usr/bin"+-- >>> Path.toString (Path.toPath (SegNode.join [urdir|usr|] [urfile|bin|] :: Unrooted (File PosixPath)))+-- "usr/bin"+--+{-# INLINE join #-}+join ::+ (+ IsPath OS_PATH (a (Dir OS_PATH))+ , IsPath OS_PATH (b OS_PATH)+ , IsPath OS_PATH (a (b OS_PATH))+ ) => a (Dir OS_PATH) -> Unrooted (b OS_PATH) -> a (b OS_PATH)+join p1 (Unrooted p2) =+ unsafeFromPath $ OsPath.unsafeJoin (toPath p1) (toPath p2)
+ src/Streamly/Internal/FileSystem/Windows/File.hsc view
@@ -0,0 +1,202 @@+-- XXX When introducing platform specifc API, see Posix/File.hsc and design in+-- the same consistent way.+module Streamly.Internal.FileSystem.Windows.File+ (+#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+ -- * Handle based+ openFile+ , withFile+ , openBinaryFile+ , withBinaryFile+#endif+ ) where++#if defined(mingw32_HOST_OS) || defined(__MINGW32__)++-------------------------------------------------------------------------------+-- Imports+-------------------------------------------------------------------------------++import Control.Concurrent (threadDelay)+import Control.Exception (onException)+import Control.Monad (when, void)+import Streamly.Internal.FileSystem.WindowsPath (WindowsPath)+import System.IO (IOMode(..), Handle)++#if defined(__IO_MANAGER_WINIO__)+import GHC.IO.SubSystem+#else+import GHC.IO.Handle.FD (fdToHandle')+#include <fcntl.h>+#endif++import qualified Streamly.Internal.FileSystem.File.Common as File+import qualified Streamly.Internal.FileSystem.WindowsPath as Path++import Data.Bits+import Foreign.Ptr+import System.Win32 as Win32 hiding (createFile, failIfWithRetry)++#include <windows.h>++-------------------------------------------------------------------------------+-- Low level (fd returning) file opening APIs+-------------------------------------------------------------------------------++-- XXX Note for i386, stdcall is needed instead of ccall, see Win32+-- package/windows_cconv.h. We support only x86_64 for now.+foreign import ccall unsafe "windows.h CreateFileW"+ c_CreateFile :: LPCTSTR -> AccessMode -> ShareMode -> LPSECURITY_ATTRIBUTES -> CreateMode -> FileAttributeOrFlag -> HANDLE -> IO HANDLE++-- | like failIf, but retried on sharing violations. This is necessary for many+-- file operations; see+-- https://www.betaarchive.com/wiki/index.php/Microsoft_KB_Archive/316609+--+failIfWithRetry :: (a -> Bool) -> String -> IO a -> IO a+failIfWithRetry needRetry msg action = retryOrFail retries++ where++ delay = 100 * 1000 -- 100 ms++ -- KB article recommends 250/5+ retries = 20 :: Int++ -- retryOrFail :: Int -> IO a+ retryOrFail times+ | times <= 0 = errorWin msg+ | otherwise = do+ ret <- action+ if not (needRetry ret)+ then return ret+ else do+ err_code <- getLastError+ if err_code == 32+ then do+ threadDelay delay+ retryOrFail (times - 1)+ else errorWin msg++createFile ::+ WindowsPath+ -> AccessMode+ -> ShareMode+ -> Maybe LPSECURITY_ATTRIBUTES+ -> CreateMode+ -> FileAttributeOrFlag+ -> Maybe Win32.HANDLE+ -> IO Win32.HANDLE+createFile name access share mb_attr mode flag mb_h =+ Path.asCWString name $ \c_name ->+ failIfWithRetry+ (== iNVALID_HANDLE_VALUE)+ (unwords ["CreateFile", Path.toString name])+ $ c_CreateFile+ c_name access share (maybePtr mb_attr) mode flag (maybePtr mb_h)++win2HsHandle :: WindowsPath -> IOMode -> Win32.HANDLE -> IO Handle+win2HsHandle _fp _iomode h = do+#if defined(__IO_MANAGER_WINIO__)+ Win32.hANDLEToHandle h+#else+ fd <- _open_osfhandle (fromIntegral (ptrToIntPtr h)) (#const _O_BINARY)+ fdToHandle' fd Nothing False (Path.toString _fp) _iomode True+#endif++fdToHandle :: WindowsPath -> IOMode -> Win32.HANDLE -> IO Handle+fdToHandle fp iomode h =+ win2HsHandle fp iomode h `onException` Win32.closeHandle h++openFileFd :: Bool -> WindowsPath -> IOMode -> IO Win32.HANDLE+openFileFd existing fp iomode = do+ h <- createFile+ fp+ accessMode+ shareMode+ Nothing+ (if existing then createModeExisting else createMode)+ fileAttr+ Nothing+ when (iomode == AppendMode )+ $ void $ Win32.setFilePointerEx h 0 Win32.fILE_END+ return h++ where++ accessMode =+ case iomode of+ ReadMode -> Win32.gENERIC_READ+ WriteMode -> Win32.gENERIC_WRITE+ AppendMode -> Win32.gENERIC_WRITE .|. Win32.fILE_APPEND_DATA+ ReadWriteMode -> Win32.gENERIC_READ .|. Win32.gENERIC_WRITE++ writeShareMode :: ShareMode+ writeShareMode =+ Win32.fILE_SHARE_DELETE+ .|. Win32.fILE_SHARE_READ++ maxShareMode :: ShareMode+ maxShareMode =+ Win32.fILE_SHARE_DELETE+ .|. Win32.fILE_SHARE_READ+ .|. Win32.fILE_SHARE_WRITE++ shareMode =+ case iomode of+ ReadMode -> Win32.fILE_SHARE_READ+ WriteMode -> writeShareMode+ AppendMode -> writeShareMode+ ReadWriteMode -> maxShareMode++ createMode =+ case iomode of+ ReadMode -> Win32.oPEN_EXISTING+ WriteMode -> Win32.cREATE_ALWAYS+ AppendMode -> Win32.oPEN_ALWAYS+ ReadWriteMode -> Win32.oPEN_ALWAYS++ createModeExisting =+ case iomode of+ ReadMode -> Win32.oPEN_EXISTING+ WriteMode -> Win32.tRUNCATE_EXISTING+ AppendMode -> Win32.oPEN_EXISTING+ ReadWriteMode -> Win32.oPEN_EXISTING++ fileAttr =+#if defined(__IO_MANAGER_WINIO__)+ (case ioSubSystem of+ IoPOSIX -> Win32.fILE_ATTRIBUTE_NORMAL+ IoNative -> Win32.fILE_ATTRIBUTE_NORMAL .|. Win32.fILE_FLAG_OVERLAPPED+ )+#else+ Win32.fILE_ATTRIBUTE_NORMAL+#endif++-------------------------------------------------------------------------------+-- base openFile compatible, Handle returning, APIs+-------------------------------------------------------------------------------++-- | Open a regular file, return a Handle. The file is locked, the Handle is+-- NOT set up to close the file on garbage collection.+{-# INLINE openFileHandle #-}+openFileHandle :: WindowsPath -> IOMode -> IO Handle+openFileHandle p x = openFileFd False p x >>= fdToHandle p x++-- | Like withFile in base package but using Path instead of FilePath.+-- Use hSetBinaryMode on the handle if you want to use binary mode.+withFile :: WindowsPath -> IOMode -> (Handle -> IO r) -> IO r+withFile = File.withFile False openFileHandle++-- | Like openFile in base package but using Path instead of FilePath.+-- Use hSetBinaryMode on the handle if you want to use binary mode.+openFile :: WindowsPath -> IOMode -> IO Handle+openFile = File.openFile False openFileHandle++-- | Like withBinaryFile in base package but using Path instead of FilePath.+withBinaryFile :: WindowsPath -> IOMode -> (Handle -> IO r) -> IO r+withBinaryFile = File.withFile True openFileHandle++-- | Like openBinaryFile in base package but using Path instead of FilePath.+openBinaryFile :: WindowsPath -> IOMode -> IO Handle+openBinaryFile = File.openFile True openFileHandle+#endif
+ src/Streamly/Internal/FileSystem/Windows/ReadDir.hsc view
@@ -0,0 +1,273 @@+-- |+-- Module : Streamly.Internal.FileSystem.Windows.ReadDir+-- Copyright : (c) 2024 Composewell Technologies+--+-- License : BSD3+-- Maintainer : streamly@composewell.com+-- Portability : GHC++module Streamly.Internal.FileSystem.Windows.ReadDir+ (+#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+ DirStream+ , openDirStream+ , closeDirStream+ , readDirStreamEither+ , eitherReader+ , reader+#endif+ )+where++#if defined(mingw32_HOST_OS) || defined(__MINGW32__)++import Control.Exception (throwIO)+import Control.Monad (void)+import Control.Monad.Catch (MonadCatch)+import Control.Monad.IO.Class (MonadIO(..))+import Data.Char (ord, isSpace)+import Data.IORef (IORef, newIORef, readIORef, writeIORef)+import Foreign.C (CInt(..), CWchar(..), Errno(..), errnoToIOError, peekCWString)+import Numeric (showHex)+import Streamly.Internal.Data.Unfold.Type (Unfold(..))+import Streamly.Internal.Data.Stream (Step(..))+import Streamly.Internal.FileSystem.Path (Path)+import Streamly.Internal.FileSystem.WindowsPath (WindowsPath(..))+import System.IO.Error (ioeSetErrorString)++import qualified Streamly.Internal.Data.Array as Array+import qualified Streamly.Internal.Data.Unfold as UF (bracketIO)+import qualified Streamly.Internal.FileSystem.WindowsPath as Path+import qualified System.Win32 as Win32 (failWith)++import Streamly.Internal.FileSystem.DirOptions+import Foreign hiding (void)++#include <windows.h>++-- Note on A vs W suffix in APIs.+-- CreateFile vs. CreateFileW: CreateFile is a macro that expands to+-- CreateFileA or CreateFileW depending on whether Unicode support (UNICODE and+-- _UNICODE preprocessor macros) is enabled in your project. To ensure+-- consistent Unicode support, explicitly use CreateFileW.++------------------------------------------------------------------------------+-- Types+------------------------------------------------------------------------------++type BOOL = Bool+type DWORD = Word32++type UINT_PTR = Word+type ErrCode = DWORD+type LPCTSTR = Ptr CWchar+type WIN32_FIND_DATA = ()+type HANDLE = Ptr ()++------------------------------------------------------------------------------+-- Windows C APIs+------------------------------------------------------------------------------++-- XXX Note for i386, stdcall is needed instead of ccall, see Win32+-- package/windows_cconv.h. We support only x86_64 for now.+foreign import ccall unsafe "windows.h FindFirstFileW"+ c_FindFirstFileW :: LPCTSTR -> Ptr WIN32_FIND_DATA -> IO HANDLE++foreign import ccall unsafe "windows.h FindNextFileW"+ c_FindNextFileW :: HANDLE -> Ptr WIN32_FIND_DATA -> IO BOOL++foreign import ccall unsafe "windows.h FindClose"+ c_FindClose :: HANDLE -> IO BOOL++foreign import ccall unsafe "windows.h GetLastError"+ getLastError :: IO ErrCode++foreign import ccall unsafe "windows.h LocalFree"+ localFree :: Ptr a -> IO (Ptr a)++------------------------------------------------------------------------------+-- Haskell C APIs+------------------------------------------------------------------------------++foreign import ccall unsafe "maperrno_func" -- in base/cbits/Win32Utils.c+ c_maperrno_func :: ErrCode -> IO Errno++------------------------------------------------------------------------------+-- Error Handling+------------------------------------------------------------------------------++-- XXX getErrorMessage and castUINTPtrToPtr require c code, so left out for+-- now. Once we replace these we can remove dependency on Win32. We can+-- possibly implement these in Haskell by directly calling the Windows API.++foreign import ccall unsafe "getErrorMessage"+ getErrorMessage :: DWORD -> IO (Ptr CWchar)++foreign import ccall unsafe "castUINTPtrToPtr"+ castUINTPtrToPtr :: UINT_PTR -> Ptr a++failWith :: String -> ErrCode -> IO a+failWith fn_name err_code = do+ c_msg <- getErrorMessage err_code+ msg <- if c_msg == nullPtr+ then return $ "Error 0x" ++ Numeric.showHex err_code ""+ else do+ msg <- peekCWString c_msg+ -- We ignore failure of freeing c_msg, given we're already failing+ _ <- localFree c_msg+ return msg+ errno <- c_maperrno_func err_code+ let msg' = reverse $ dropWhile isSpace $ reverse msg -- drop trailing \n+ ioerror = errnoToIOError fn_name errno Nothing Nothing+ `ioeSetErrorString` msg'+ throwIO ioerror++errorWin :: String -> IO a+errorWin fn_name = do+ err_code <- getLastError+ failWith fn_name err_code++failIf :: (a -> Bool) -> String -> IO a -> IO a+failIf p wh act = do+ v <- act+ if p v then errorWin wh else return v++iNVALID_HANDLE_VALUE :: HANDLE+iNVALID_HANDLE_VALUE = castUINTPtrToPtr maxBound++------------------------------------------------------------------------------+-- Dir stream implementation+------------------------------------------------------------------------------++-- XXX Define this as data and unpack three fields?+newtype DirStream =+ DirStream (HANDLE, IORef Bool, ForeignPtr WIN32_FIND_DATA)++openDirStream :: WindowsPath -> IO DirStream+openDirStream p = do+ let path = Path.unsafeJoin p $ Path.unsafeFromString "*"+ fp_finddata <- mallocForeignPtrBytes (# const sizeof(WIN32_FIND_DATAW) )+ withForeignPtr fp_finddata $ \dataPtr -> do+ handle <-+ Array.asCStringUnsafe (Path.toArray path) $ \pathPtr -> do+ -- XXX Use getLastError to distinguish the case when no+ -- matching file is found. See the doc of FindFirstFileW.+ failIf+ (== iNVALID_HANDLE_VALUE)+ ("FindFirstFileW: " ++ Path.toString path)+ $ c_FindFirstFileW (castPtr pathPtr) dataPtr+ ref <- newIORef True+ return $ DirStream (handle, ref, fp_finddata)++closeDirStream :: DirStream -> IO ()+closeDirStream (DirStream (h, _, _)) = void (c_FindClose h)++-- XXX Keep this in sync with the isMetaDir function in Posix readdir module.+isMetaDir :: Ptr CWchar -> IO Bool+isMetaDir dname = do+ -- XXX Assuming UTF16LE encoding+ c1 <- peek dname+ if (c1 /= fromIntegral (ord '.'))+ then return False+ else do+ c2 :: Word8 <- peekByteOff dname 1+ if (c2 == 0)+ then return True+ else if (c2 /= fromIntegral (ord '.'))+ then return False+ else do+ c3 :: Word8 <- peekByteOff dname 2+ if (c3 == 0)+ then return True+ else return False++readDirStreamEither ::+ (ReadOptions -> ReadOptions) ->+ DirStream -> IO (Maybe (Either WindowsPath WindowsPath))+readDirStreamEither _ (DirStream (h, ref, fdata)) =+ withForeignPtr fdata $ \ptr -> do+ firstTime <- readIORef ref+ if firstTime+ then do+ writeIORef ref False+ processEntry ptr+ else findNext ptr++ where++ -- XXX: for a symlink the attribute may have a FILE_ATTRIBUTE_DIRECTORY if+ -- the symlink was created as a directory symlink, but it might have+ -- changed later. To find the real type of the symlink when we have+ -- followSymlinks option on we need to check if it is a+ -- FILE_ATTRIBUTE_REPARSE_POINT, we need to open the reparse point and find+ -- the type.++ processEntry ptr = do+ let dname = #{ptr WIN32_FIND_DATAW, cFileName} ptr+ dattrs :: #{type DWORD} <-+ #{peek WIN32_FIND_DATAW, dwFileAttributes} ptr+ name <- Array.fromW16CString dname+ if (dattrs .&. (#const FILE_ATTRIBUTE_DIRECTORY) /= 0)+ then do+ isMeta <- isMetaDir dname+ if isMeta+ then findNext ptr+ else return (Just (Left (Path.unsafeFromArray name)))+ else return (Just (Right (Path.unsafeFromArray name)))++ findNext ptr = do+ retval <- liftIO $ c_FindNextFileW h ptr+ if (retval)+ then processEntry ptr+ else do+ err <- getLastError+ if err == (# const ERROR_NO_MORE_FILES )+ then return Nothing+ -- XXX Print the path in the error message+ else Win32.failWith "findNextFile" err++{-# INLINE streamEitherReader #-}+streamEitherReader :: MonadIO m =>+ (ReadOptions -> ReadOptions) ->+ Unfold m DirStream (Either Path Path)+streamEitherReader f = Unfold step return+ where++ step strm = do+ r <- liftIO $ readDirStreamEither f strm+ case r of+ Nothing -> return Stop+ Just x -> return $ Yield x strm++{-# INLINE streamReader #-}+streamReader :: MonadIO m => Unfold m DirStream Path+streamReader = fmap (either id id) (streamEitherReader id)++-- | Read a directory emitting a stream with names of the children. Filter out+-- "." and ".." entries.+--+-- /Internal/++{-# INLINE reader #-}+reader :: (MonadIO m, MonadCatch m) => Unfold m Path Path+reader =+-- XXX Instead of using bracketIO for each iteration of the loop we should+-- instead yield a buffer of dir entries in each iteration and then use an+-- unfold and concat to flatten those entries. That should improve the+-- performance.+ UF.bracketIO openDirStream closeDirStream streamReader++-- | Read directories as Left and files as Right. Filter out "." and ".."+-- entries.+--+-- /Internal/+--+{-# INLINE eitherReader #-}+eitherReader :: (MonadIO m, MonadCatch m) =>+ (ReadOptions -> ReadOptions) -> Unfold m Path (Either Path Path)+eitherReader f =+ -- XXX The measured overhead of bracketIO is not noticeable, if it turns+ -- out to be a problem for small filenames we can use getdents64 to use+ -- chunked read to avoid the overhead.+ UF.bracketIO openDirStream closeDirStream (streamEitherReader f)+#endif
+ src/Streamly/Internal/FileSystem/WindowsPath.hs view
@@ -0,0 +1,366 @@+{-# LANGUAGE CPP #-}+#define IS_WINDOWS+#include "Streamly/Internal/FileSystem/PosixPath.hs"++-- XXX Move these functions to PosixPath.hs and use CPP conditionals for+-- documentation differences, definitions are identical.++-- Note: We can use powershell for testing path validity.+-- "//share/x" works in powershell.+-- But mixed forward and backward slashes do not work, it is treated as a path+-- relative to current drive e.g. "\\/share/x" is treated as "C:/share/x".+--+-- XXX Note: Windows may have case sensitive behavior depending on the file+-- system being used. Does it impact any of the case insensitive validations+-- below?+--+-- XXX ADS - alternate data stream syntax - file.txt:stream .++-- | Like 'validatePath' but more strict. The path must refer to a file system+-- object. For example, a share root itself is not a valid file system object.+-- it must be followed by a non-empty path.+--+-- >>> isValid = isJust . Path.validatePath' . Path.encodeString+--+-- >>> isValid "\\\\"+-- False+-- >>> isValid "\\\\server\\"+-- False+-- >>> isValid "\\\\server\\x"+-- True+-- >>> isValid "\\\\?\\UNC\\server"+-- False+--+validatePath' ::+ MonadThrow m => Array OS_WORD_TYPE -> m ()+validatePath' = Common.validatePath' Common.Windows++-- | Like 'isValidPath' but more strict.+--+-- >>> isValidPath' = isJust . Path.validatePath'+--+isValidPath' ::+ Array OS_WORD_TYPE -> Bool+isValidPath' = isJust . validatePath'++-- | Read a raw array of OS_WORD_TYPE as a path type.+--+-- >>> readArray = fromJust . Path.fromArray . read+--+-- >>> arr :: Array Word16 = Path.encodeString "hello"+-- >>> Path.showArray $ (Path.readArray $ show arr :: Path.WindowsPath)+-- "fromList [104,101,108,108,111]"+--+-- See also: 'showArray'.+readArray :: [Char] -> OS_PATH_TYPE+readArray = fromJust . fromArray . read++-- | A path that is attached to a root. "C:\\" is considered an absolute root+-- and "." as a dynamic root. ".." is not considered a root, "..\/x" or "x\/y"+-- are not rooted paths.+--+-- Absolute locations:+--+-- * @C:\\@ local drive+-- * @\\\\share\\@ UNC share+-- * @\\\\?\\C:\\@ Long UNC local drive+-- * @\\\\?\\UNC\\@ Long UNC remote server+-- * @\\\\.\\@ DOS local device namespace+-- * @\\\\??\\@ DOS global namespace+--+-- Relative locations:+--+-- * @\\@ relative to current drive root+-- * @.\\@ relative to current directory+-- * @C:@ current directory in drive+-- * @C:file@ relative to current directory in drive+--+-- >>> isRooted = Path.isRooted . fromJust . Path.fromString+--+-- Common to Windows and Posix:+--+-- >>> isRooted "/"+-- True+-- >>> isRooted "/x"+-- True+-- >>> isRooted "."+-- True+-- >>> isRooted "./x"+-- True+--+-- Windows specific:+--+-- >>> isRooted "c:"+-- True+-- >>> isRooted "c:x"+-- True+-- >>> isRooted "c:/"+-- True+-- >>> isRooted "//x/y"+-- True+--+isRooted :: OS_PATH_TYPE -> Bool+isRooted (OS_PATH arr) = Common.isRooted Common.OS_NAME arr++-- | Like 'join' but does not check if any of the path is empty or if the+-- second path is rooted.+--+-- >>> f a b = Path.toString $ Path.unsafeJoin (Path.fromString_ a) (Path.fromString_ b)+--+-- >>> f "x" "y"+-- "x\\y"+-- >>> f "x/" "y"+-- "x/y"+-- >>> f "x" "/y"+-- "x/y"+-- >>> f "x/" "/y"+-- "x/y"+--+-- Note "c:" and "/x" are both rooted paths, therefore, 'join' cannot be used+-- to join them. Similarly for joining "//x/" and "/y". For these cases use+-- 'unsafeJoin'. 'unsafeJoin' can be used as a replacement for the+-- joinDrive function from the filepath package.+--+-- >>> f "c:" "/x"+-- "c:/x"+-- >>> f "//x/" "/y"+-- "//x/y"+--+{-# INLINE unsafeJoin #-}+unsafeJoin :: OS_PATH_TYPE -> OS_PATH_TYPE -> OS_PATH_TYPE+unsafeJoin (OS_PATH a) (OS_PATH b) =+ OS_PATH+ $ Common.unsafeAppend+ Common.OS_NAME (Common.toString Unicode.UNICODE_DECODER) a b++-- | Append a OS_PATH_TYPE to another. Fails if the second path refers to a rooted+-- path. If you want to avoid runtime failure use the typesafe+-- Streamly.FileSystem.OS_PATH_TYPE.Seg module. Use 'unsafeJoin' to avoid failure+-- if you know it is ok to append the path.+--+-- Usually, append joins two paths using a separator between the paths. On+-- Windows, joining a drive "c:" with path "x" does not add a separator between+-- the two because "c:x" is different from "c:/x".+--+-- Note "c:" and "/x" are both rooted paths, therefore, 'join' cannot be used+-- to join them. Similarly for joining "//x/" and "/y". For these cases use+-- 'unsafeJoin'.+--+-- >>> f a b = Path.toString $ Path.join a b+--+-- >>> f [path|x|] [path|y|]+-- "x\\y"+-- >>> f [path|x/|] [path|y|]+-- "x/y"+-- >>> f [path|c:|] [path|x|]+-- "c:x"+-- >>> f [path|c:/|] [path|x|]+-- "c:/x"+-- >>> f [path|//x/|] [path|y|]+-- "//x/y"+--+-- >>> fails $ f [path|c:|] [path|/|]+-- True+-- >>> fails $ f [path|c:|] [path|/x|]+-- True+-- >>> fails $ f [path|c:/|] [path|/x|]+-- True+-- >>> fails $ f [path|//x/|] [path|/y|]+-- True+join :: OS_PATH_TYPE -> OS_PATH_TYPE -> OS_PATH_TYPE+join (OS_PATH a) (OS_PATH b) =+ OS_PATH+ $ Common.append+ Common.OS_NAME (Common.toString Unicode.UNICODE_DECODER) a b++-- | A stricter version of 'join' which requires the first path to be a+-- directory like path i.e. with a trailing separator.+--+-- >>> f a b = Path.toString $ Path.joinDir a b+--+-- >>> fails $ f [path|x|] [path|y|]+-- True+--+joinDir ::+ OS_PATH_TYPE -> OS_PATH_TYPE -> OS_PATH_TYPE+joinDir+ (OS_PATH a) (OS_PATH b) =+ OS_PATH+ $ Common.append'+ Common.OS_NAME (Common.toString Unicode.UNICODE_DECODER) a b++-- | See the eqPath documentation in the+-- "Streamly.Internal.FileSystem.PosixPath" module for details.+--+-- On Windows, the following is different:+--+-- * paths are normalized by replacing forward slash path separators by+-- backslashes.+-- * default configuration uses case-insensitive comparison.+--+-- >>> :{+-- eq a b = Path.eqPath id (Path.fromString_ a) (Path.fromString_ b)+-- :}+--+-- The cases that are different from Posix:+--+-- >>> eq "x\\y" "x/y"+-- True+--+-- >>> eq "x" "X"+-- True+--+-- >>> eq "c:" "C:"+-- False+--+-- >>> eq "c:" "c:"+-- False+--+-- >>> eq "c:x" "c:x"+-- False+--+-- >>> :{+-- cfg = Path.ignoreTrailingSeparators True+-- . Path.ignoreCase True+-- . Path.allowRelativeEquality True+-- eq a b = Path.eqPath cfg (Path.fromString_ a) (Path.fromString_ b)+-- :}+--+-- >>> eq "./x" "x"+-- True+--+-- >>> eq "X/" "x"+-- True+--+-- >>> eq "C:x" "c:X"+-- True+--+-- >>> eq ".\\x" "./X"+-- True+--+-- >>> eq "x//y" "x/y"+-- True+--+-- >>> eq "x/./y" "x/y"+-- True+--+-- >>> eq "x" "x"+-- True+--+eqPath :: (EqCfg -> EqCfg) -> OS_PATH_TYPE -> OS_PATH_TYPE -> Bool+eqPath cfg (OS_PATH a) (OS_PATH b) =+ Common.eqPath Unicode.UNICODE_DECODER+ Common.OS_NAME (cfg eqCfg) a b++-- | If a path is rooted then separate the root and the remaining path,+-- otherwise root is returned as empty. If the path is rooted then the non-root+-- part is guaranteed to not start with a separator.+--+-- See "Streamly.Internal.FileSystem.PosixPath" module for common examples. We+-- provide some Windows specific examples here.+--+-- >>> toList (a,b) = (Path.toString a, fmap Path.toString b)+-- >>> split = fmap toList . Path.splitRoot . Path.fromString_+--+-- >>> split "c:"+-- Just ("c:",Nothing)+--+-- >>> split "c:/"+-- Just ("c:/",Nothing)+--+-- >>> split "//x/"+-- Just ("//x/",Nothing)+--+-- >>> split "//x/y"+-- Just ("//x/",Just "y")+--+splitRoot :: OS_PATH_TYPE -> Maybe (OS_PATH_TYPE, Maybe OS_PATH_TYPE)+splitRoot (OS_PATH x) =+ let (a,b) = Common.splitRoot Common.OS_NAME x+ in if Array.null a+ then Nothing+ else if Array.null b+ then Just (OS_PATH a, Nothing)+ else Just (OS_PATH a, Just (OS_PATH b))++-- | Split a path into components separated by the path separator. "."+-- components in the path are ignored except when in the leading position.+-- Trailing separators in non-root components are dropped.+--+-- >>> split = Stream.toList . fmap Path.toString . Path.splitPath_ . Path.fromString_+--+-- >>> split "c:x"+-- ["c:","x"]+--+-- >>> split "c:/" -- Note, c:/ is not the same as c:+-- ["c:/"]+--+-- >>> split "c:/x"+-- ["c:/","x"]+--+-- >>> split "//x/y/"+-- ["//x","y"]+--+-- >>> split "./a"+-- [".","a"]+--+-- >>> split "c:./a"+-- ["c:","a"]+--+-- >>> split "a/."+-- ["a"]+--+-- >>> split "/x"+-- ["/","x"]+--+-- >>> split "/x/\\y"+-- ["/","x","y"]+--+-- >>> split "\\x/\\y"+-- ["\\","x","y"]+--+{-# INLINE splitPath_ #-}+splitPath_ :: Monad m => OS_PATH_TYPE -> Stream m OS_PATH_TYPE+splitPath_ (OS_PATH a) = fmap OS_PATH $ Common.splitPath_ Common.OS_NAME a++-- | Split the path components keeping separators between path components+-- attached to the dir part. Redundant separators are removed, only the first+-- one is kept, but separators are not changed to the default on Windows.+-- Separators are not added either e.g. "." and ".." may not have trailing+-- separators if the original path does not.+--+-- >>> split = Stream.toList . fmap Path.toString . Path.splitPath . Path.fromString_+--+-- >>> split "/x"+-- ["/","x"]+--+-- >>> split "/x/\\y"+-- ["/","x/","y"]+--+-- >>> split "\\x/\\y" -- this is not valid, multiple seps after share?+-- ["\\","x/","y"]+--+{-# INLINE splitPath #-}+splitPath :: Monad m => OS_PATH_TYPE -> Stream m OS_PATH_TYPE+splitPath (OS_PATH a) = fmap OS_PATH $ Common.splitPath Common.OS_NAME a++-- | See "Streamly.Internal.FileSystem.PosixPath" module for detailed+-- documentation and examples. We provide some Windows specific examples here.+--+-- Note: On Windows we cannot create a file named "prn." or "prn..". Thus it+-- considers anything starting with and including the first "." as the+-- extension and the part before it as the filename. Our definition considers+-- "prn." as a filename without an extension.+--+-- >>> toList (a,b) = (Path.toString a, Path.toString b)+-- >>> split = fmap toList . Path.splitExtension . Path.fromString_+--+-- >>> split "x:y"+-- Nothing+--+-- >>> split "x:.y"+-- Nothing+--+splitExtension :: OS_PATH_TYPE -> Maybe (OS_PATH_TYPE, OS_PATH_TYPE)+splitExtension (OS_PATH a) =+ fmap (bimap OS_PATH OS_PATH) $ Common.splitExtension Common.OS_NAME a
+ src/Streamly/Internal/FileSystem/WindowsPath/Node.hs view
@@ -0,0 +1,2 @@+#define IS_WINDOWS+#include "Streamly/Internal/FileSystem/PosixPath/Node.hs"
+ src/Streamly/Internal/FileSystem/WindowsPath/Seg.hs view
@@ -0,0 +1,2 @@+#define IS_WINDOWS+#include "Streamly/Internal/FileSystem/PosixPath/Seg.hs"
+ src/Streamly/Internal/FileSystem/WindowsPath/SegNode.hs view
@@ -0,0 +1,2 @@+#define IS_WINDOWS+#include "Streamly/Internal/FileSystem/PosixPath/SegNode.hs"
− src/Streamly/Internal/Serialize/FromBytes.hs
@@ -1,394 +0,0 @@--- |--- Module : Streamly.Internal.Serialize.FromBytes--- Copyright : (c) 2020 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : pre-release--- Portability : GHC------ Decode Haskell data types from byte streams.--module Streamly.Internal.Serialize.FromBytes- (- -- * Type class- FromBytes (..)-- -- * Decoders- , unit- , bool- , ordering- , eqWord8 -- XXX rename to word8Eq- , word8- , word16be- , word16le- , word32be- , word32le- , word64be- , word64le- , word64host- , int8- , int16be- , int16le- , int32be- , int32le- , int64be- , int64le- , float32be- , float32le- , double64be- , double64le- , charLatin1- )-where--import Control.Monad.IO.Class (MonadIO)-import Data.Bits ((.|.), unsafeShiftL)-import Data.Char (chr)-import Data.Int (Int8, Int16, Int32, Int64)-import GHC.Float (castWord32ToFloat, castWord64ToDouble)-import Data.Word (Word8, Word16, Word32, Word64)-import Streamly.Internal.Data.Parser (Parser)-import Streamly.Internal.Data.Maybe.Strict (Maybe'(..))-import Streamly.Internal.Data.Tuple.Strict (Tuple' (..))-import qualified Streamly.Data.Array as A-import qualified Streamly.Internal.Data.Array as A- (unsafeIndex, castUnsafe)-import qualified Streamly.Internal.Data.Parser as PR- (fromPure, either, satisfy, takeEQ)-import qualified Streamly.Internal.Data.Parser.ParserD as PRD- (Parser(..), Initial(..), Step(..))---- Note: The () type does not need to have an on-disk representation in theory.--- But we use a concrete representation for it so that we count how many ()--- types we have. Or when we have an array of units the array a concrete--- length.---- | A value of type '()' is encoded as @0@ in binary encoding.------ @--- 0 ==> ()--- @------ /Pre-release/----{-# INLINE unit #-}-unit :: Monad m => Parser Word8 m ()-unit = eqWord8 0 *> PR.fromPure ()--{-# INLINE word8ToBool #-}-word8ToBool :: Word8 -> Either String Bool-word8ToBool 0 = Right False-word8ToBool 1 = Right True-word8ToBool w = Left ("Invalid Bool encoding " ++ Prelude.show w)---- | A value of type 'Bool' is encoded as follows in binary encoding.------ @--- 0 ==> False--- 1 ==> True--- @------ /Pre-release/----{-# INLINE bool #-}-bool :: Monad m => Parser Word8 m Bool-bool = PR.either word8ToBool--{-# INLINE word8ToOrdering #-}-word8ToOrdering :: Word8 -> Either String Ordering-word8ToOrdering 0 = Right LT-word8ToOrdering 1 = Right EQ-word8ToOrdering 2 = Right GT-word8ToOrdering w = Left ("Invalid Ordering encoding " ++ Prelude.show w)---- | A value of type 'Ordering' is encoded as follows in binary encoding.------ @--- 0 ==> LT--- 1 ==> EQ--- 2 ==> GT--- @------ /Pre-release/----{-# INLINE ordering #-}-ordering :: Monad m => Parser Word8 m Ordering-ordering = PR.either word8ToOrdering---- XXX should go in a Word8 parser module?--- | Accept the input byte only if it is equal to the specified value.------ /Pre-release/----{-# INLINE eqWord8 #-}-eqWord8 :: Monad m => Word8 -> Parser Word8 m Word8-eqWord8 b = PR.satisfy (== b)---- | Accept any byte.------ /Pre-release/----{-# INLINE word8 #-}-word8 :: Monad m => Parser Word8 m Word8-word8 = PR.satisfy (const True)---- | Big endian (MSB first) Word16-{-# INLINE word16beD #-}-word16beD :: Monad m => PRD.Parser Word8 m Word16-word16beD = PRD.Parser step initial extract-- where-- initial = return $ PRD.IPartial Nothing'-- step Nothing' a =- -- XXX We can use a non-failing parser or a fold so that we do not- -- have to buffer for backtracking which is inefficient.- return $ PRD.Continue 0 (Just' (fromIntegral a `unsafeShiftL` 8))- step (Just' w) a =- return $ PRD.Done 0 (w .|. fromIntegral a)-- extract _ = return $ PRD.Error "word16be: end of input"---- | Parse two bytes as a 'Word16', the first byte is the MSB of the Word16 and--- second byte is the LSB (big endian representation).------ /Pre-release/----{-# INLINE word16be #-}-word16be :: Monad m => Parser Word8 m Word16-word16be = word16beD---- | Little endian (LSB first) Word16-{-# INLINE word16leD #-}-word16leD :: Monad m => PRD.Parser Word8 m Word16-word16leD = PRD.Parser step initial extract-- where-- initial = return $ PRD.IPartial Nothing'-- step Nothing' a =- return $ PRD.Continue 0 (Just' (fromIntegral a))- step (Just' w) a =- return $ PRD.Done 0 (w .|. fromIntegral a `unsafeShiftL` 8)-- extract _ = return $ PRD.Error "word16le: end of input"---- | Parse two bytes as a 'Word16', the first byte is the LSB of the Word16 and--- second byte is the MSB (little endian representation).------ /Pre-release/----{-# INLINE word16le #-}-word16le :: Monad m => Parser Word8 m Word16-word16le = word16leD---- | Big endian (MSB first) Word32-{-# INLINE word32beD #-}-word32beD :: Monad m => PRD.Parser Word8 m Word32-word32beD = PRD.Parser step initial extract-- where-- initial = return $ PRD.IPartial $ Tuple' 0 24-- step (Tuple' w sh) a = return $- if sh /= 0- then- let w1 = w .|. (fromIntegral a `unsafeShiftL` sh)- in PRD.Continue 0 (Tuple' w1 (sh - 8))- else PRD.Done 0 (w .|. fromIntegral a)-- extract _ = return $ PRD.Error "word32beD: end of input"---- | Parse four bytes as a 'Word32', the first byte is the MSB of the Word32--- and last byte is the LSB (big endian representation).------ /Pre-release/----{-# INLINE word32be #-}-word32be :: Monad m => Parser Word8 m Word32-word32be = word32beD---- | Little endian (LSB first) Word32-{-# INLINE word32leD #-}-word32leD :: Monad m => PRD.Parser Word8 m Word32-word32leD = PRD.Parser step initial extract-- where-- initial = return $ PRD.IPartial $ Tuple' 0 0-- step (Tuple' w sh) a = return $- let w1 = w .|. (fromIntegral a `unsafeShiftL` sh)- in if sh /= 24- then PRD.Continue 0 (Tuple' w1 (sh + 8))- else PRD.Done 0 w1-- extract _ = return $ PRD.Error "word32leD: end of input"---- | Parse four bytes as a 'Word32', the first byte is the MSB of the Word32--- and last byte is the LSB (big endian representation).------ /Pre-release/----{-# INLINE word32le #-}-word32le :: Monad m => Parser Word8 m Word32-word32le = word32leD---- | Big endian (MSB first) Word64-{-# INLINE word64beD #-}-word64beD :: Monad m => PRD.Parser Word8 m Word64-word64beD = PRD.Parser step initial extract-- where-- initial = return $ PRD.IPartial $ Tuple' 0 56-- step (Tuple' w sh) a = return $- if sh /= 0- then- let w1 = w .|. (fromIntegral a `unsafeShiftL` sh)- in PRD.Continue 0 (Tuple' w1 (sh - 8))- else PRD.Done 0 (w .|. fromIntegral a)-- extract _ = return $ PRD.Error "word64beD: end of input"---- | Parse eight bytes as a 'Word64', the first byte is the MSB of the Word64--- and last byte is the LSB (big endian representation).------ /Pre-release/----{-# INLINE word64be #-}-word64be :: Monad m => Parser Word8 m Word64-word64be = word64beD---- | Little endian (LSB first) Word64-{-# INLINE word64leD #-}-word64leD :: Monad m => PRD.Parser Word8 m Word64-word64leD = PRD.Parser step initial extract-- where-- initial = return $ PRD.IPartial $ Tuple' 0 0-- step (Tuple' w sh) a = return $- let w1 = w .|. (fromIntegral a `unsafeShiftL` sh)- in if sh /= 56- then PRD.Continue 0 (Tuple' w1 (sh + 8))- else PRD.Done 0 w1-- extract _ = return $ PRD.Error "word64leD: end of input"---- | Parse eight bytes as a 'Word64', the first byte is the MSB of the Word64--- and last byte is the LSB (big endian representation).------ /Pre-release/----{-# INLINE word64le #-}-word64le :: Monad m => Parser Word8 m Word64-word64le = word64leD--{-# INLINE int8 #-}-int8 :: Monad m => Parser Word8 m Int8-int8 = fromIntegral <$> word8---- | Parse two bytes as a 'Int16', the first byte is the MSB of the Int16 and--- second byte is the LSB (big endian representation).------ /Pre-release/----{-# INLINE int16be #-}-int16be :: Monad m => Parser Word8 m Int16-int16be = fromIntegral <$> word16be---- | Parse two bytes as a 'Int16', the first byte is the LSB of the Int16 and--- second byte is the MSB (little endian representation).------ /Pre-release/----{-# INLINE int16le #-}-int16le :: Monad m => Parser Word8 m Int16-int16le = fromIntegral <$> word16le---- | Parse four bytes as a 'Int32', the first byte is the MSB of the Int32--- and last byte is the LSB (big endian representation).------ /Pre-release/----{-# INLINE int32be #-}-int32be :: Monad m => Parser Word8 m Int32-int32be = fromIntegral <$> word32be---- | Parse four bytes as a 'Int32', the first byte is the MSB of the Int32--- and last byte is the LSB (big endian representation).------ /Pre-release/----{-# INLINE int32le #-}-int32le :: Monad m => Parser Word8 m Int32-int32le = fromIntegral <$> word32le---- | Parse eight bytes as a 'Int64', the first byte is the MSB of the Int64--- and last byte is the LSB (big endian representation).------ /Pre-release/----{-# INLINE int64be #-}-int64be :: Monad m => Parser Word8 m Int64-int64be = fromIntegral <$> word64be---- | Parse eight bytes as a 'Int64', the first byte is the MSB of the Int64--- and last byte is the LSB (big endian representation).------ /Pre-release/----{-# INLINE int64le #-}-int64le :: Monad m => Parser Word8 m Int64-int64le = fromIntegral <$> word64le--{-# INLINE float32be #-}-float32be :: MonadIO m => Parser Word8 m Float-float32be = castWord32ToFloat <$> word32be--{-# INLINE float32le #-}-float32le :: MonadIO m => Parser Word8 m Float-float32le = castWord32ToFloat <$> word32le--{-# INLINE double64be #-}-double64be :: MonadIO m => Parser Word8 m Double-double64be = castWord64ToDouble <$> word64be--{-# INLINE double64le #-}-double64le :: MonadIO m => Parser Word8 m Double-double64le = castWord64ToDouble <$> word64le---- | Accept any byte.------ /Pre-release/----{-# INLINE charLatin1 #-}-charLatin1 :: Monad m => Parser Word8 m Char-charLatin1 = fmap (chr . fromIntegral) word8------------------------------------------------------------------------------------ Host byte order------------------------------------------------------------------------------------ | Parse eight bytes as a 'Word64' in the host byte order.------ /Pre-release/----{-# INLINE word64host #-}-word64host :: MonadIO m => Parser Word8 m Word64-word64host =- fmap (A.unsafeIndex 0 . A.castUnsafe) $ PR.takeEQ 8 (A.writeN 8)------------------------------------------------------------------------------------ Type class----------------------------------------------------------------------------------class FromBytes a where- -- | Decode a byte stream to a Haskell type.- fromBytes :: Parser Word8 m a
− src/Streamly/Internal/Serialize/ToBytes.hs
@@ -1,374 +0,0 @@--- |--- Module : Streamly.Internal.Serialize.ToBytes--- Copyright : (c) 2022 Composewell Technologies--- License : BSD-3-Clause--- Maintainer : streamly@composewell.com--- Stability : pre-release--- Portability : GHC------ Encode Haskell data types to byte streams.--module Streamly.Internal.Serialize.ToBytes- (- -- * Type class- ToBytes (..)-- -- * Encoders- , unit- , bool- , ordering- , word8- , word16be- , word16le- , word32be- , word32le- , word64be- , word64le- , word64host- , int8- , int16be- , int16le- , int32be- , int32le- , int64be- , int64le- , float32be- , float32le- , double64be- , double64le- , charLatin1- , charUtf8- )-where--#include "MachDeps.h"--import Data.Bits (shiftR)-import Data.Char (ord)-import Data.Int (Int8, Int16, Int32, Int64)-import Data.Word (Word8, Word16, Word32, Word64)-import GHC.Float (castDoubleToWord64, castFloatToWord32)-import Streamly.Internal.Data.Stream.StreamD (Stream)-import Streamly.Internal.Data.Stream.StreamD (Step(..))-import Streamly.Internal.Unicode.Stream (readCharUtf8)--import qualified Streamly.Internal.Data.Stream.StreamD as Stream-import qualified Streamly.Internal.Data.Stream.StreamD as D---- XXX Use StreamD directly?---- | A value of type '()' is encoded as @0@ in binary encoding.------ @--- 0 ==> ()--- @------ /Pre-release/----{-# INLINE unit #-}-unit :: Applicative m => Stream m Word8-unit = Stream.fromPure 0--{-# INLINE boolToWord8 #-}-boolToWord8 :: Bool -> Word8-boolToWord8 False = 0-boolToWord8 True = 1---- | A value of type 'Bool' is encoded as follows in binary encoding.------ @--- 0 ==> False--- 1 ==> True--- @------ /Pre-release/----{-# INLINE bool #-}-bool :: Applicative m => Bool -> Stream m Word8-bool = Stream.fromPure . boolToWord8--{-# INLINE orderingToWord8 #-}-orderingToWord8 :: Ordering -> Word8-orderingToWord8 LT = 0-orderingToWord8 EQ = 1-orderingToWord8 GT = 2---- | A value of type 'Ordering' is encoded as follows in binary encoding.------ @--- 0 ==> LT--- 1 ==> EQ--- 2 ==> GT--- @------ /Pre-release/----{-# INLINE ordering #-}-ordering :: Applicative m => Ordering -> Stream m Word8-ordering = Stream.fromPure . orderingToWord8---- | Stream a 'Word8'.------ /Pre-release/----{-# INLINE word8 #-}-word8 :: Applicative m => Word8 -> Stream m Word8-word8 = Stream.fromPure--data W16State = W16B1 | W16B2 | W16Done--{-# INLINE word16beD #-}-word16beD :: Applicative m => Word16 -> D.Stream m Word8-word16beD w = D.Stream step W16B1-- where-- step _ W16B1 = pure $ Yield (fromIntegral (shiftR w 8) :: Word8) W16B2- step _ W16B2 = pure $ Yield (fromIntegral w :: Word8) W16Done- step _ W16Done = pure Stop---- | Stream a 'Word16' as two bytes, the first byte is the MSB of the Word16--- and second byte is the LSB (big endian representation).------ /Pre-release/----{-# INLINE word16be #-}-word16be :: Monad m => Word16 -> Stream m Word8-word16be = word16beD---- | Little endian (LSB first) Word16-{-# INLINE word16leD #-}-word16leD :: Applicative m => Word16 -> D.Stream m Word8-word16leD w = D.Stream step W16B1-- where-- step _ W16B1 = pure $ Yield (fromIntegral w :: Word8) W16B2- step _ W16B2 = pure $ Yield (fromIntegral (shiftR w 8) :: Word8) W16Done- step _ W16Done = pure Stop---- | Stream a 'Word16' as two bytes, the first byte is the LSB of the Word16--- and second byte is the MSB (little endian representation).------ /Pre-release/----{-# INLINE word16le #-}-word16le :: Monad m => Word16 -> Stream m Word8-word16le = word16leD--data W32State = W32B1 | W32B2 | W32B3 | W32B4 | W32Done---- | Big endian (MSB first) Word32-{-# INLINE word32beD #-}-word32beD :: Applicative m => Word32 -> D.Stream m Word8-word32beD w = D.Stream step W32B1-- where-- yield n s = pure $ Yield (fromIntegral (shiftR w n) :: Word8) s-- step _ W32B1 = yield 24 W32B2- step _ W32B2 = yield 16 W32B3- step _ W32B3 = yield 8 W32B4- step _ W32B4 = pure $ Yield (fromIntegral w :: Word8) W32Done- step _ W32Done = pure Stop---- | Stream a 'Word32' as four bytes, the first byte is the MSB of the Word32--- and last byte is the LSB (big endian representation).------ /Pre-release/----{-# INLINE word32be #-}-word32be :: Monad m => Word32 -> Stream m Word8-word32be = word32beD---- | Little endian (LSB first) Word32-{-# INLINE word32leD #-}-word32leD :: Applicative m => Word32 -> D.Stream m Word8-word32leD w = D.Stream step W32B1-- where-- yield n s = pure $ Yield (fromIntegral (shiftR w n) :: Word8) s-- step _ W32B1 = pure $ Yield (fromIntegral w :: Word8) W32B2- step _ W32B2 = yield 8 W32B3- step _ W32B3 = yield 16 W32B4- step _ W32B4 = yield 24 W32Done- step _ W32Done = pure Stop---- | Stream a 'Word32' as four bytes, the first byte is the MSB of the Word32--- and last byte is the LSB (big endian representation).------ /Pre-release/----{-# INLINE word32le #-}-word32le :: Monad m => Word32 -> Stream m Word8-word32le = word32leD--data W64State =- W64B1 | W64B2 | W64B3 | W64B4 | W64B5 | W64B6 | W64B7 | W64B8 | W64Done---- | Big endian (MSB first) Word64-{-# INLINE word64beD #-}-word64beD :: Applicative m => Word64 -> D.Stream m Word8-word64beD w = D.Stream step W64B1-- where-- yield n s = pure $ Yield (fromIntegral (shiftR w n) :: Word8) s-- step _ W64B1 = yield 56 W64B2- step _ W64B2 = yield 48 W64B3- step _ W64B3 = yield 40 W64B4- step _ W64B4 = yield 32 W64B5- step _ W64B5 = yield 24 W64B6- step _ W64B6 = yield 16 W64B7- step _ W64B7 = yield 8 W64B8- step _ W64B8 = pure $ Yield (fromIntegral w :: Word8) W64Done- step _ W64Done = pure Stop---- | Stream a 'Word64' as eight bytes, the first byte is the MSB of the Word64--- and last byte is the LSB (big endian representation).------ /Pre-release/----{-# INLINE word64be #-}-word64be :: Monad m => Word64 -> Stream m Word8-word64be = word64beD---- | Little endian (LSB first) Word64-{-# INLINE word64leD #-}-word64leD :: Applicative m => Word64 -> D.Stream m Word8-word64leD w = D.Stream step W64B1-- where-- yield n s = pure $ Yield (fromIntegral (shiftR w n) :: Word8) s-- step _ W64B1 = pure $ Yield (fromIntegral w :: Word8) W64B2- step _ W64B2 = yield 8 W64B3- step _ W64B3 = yield 16 W64B4- step _ W64B4 = yield 24 W64B5- step _ W64B5 = yield 32 W64B6- step _ W64B6 = yield 40 W64B7- step _ W64B7 = yield 48 W64B8- step _ W64B8 = yield 56 W64Done- step _ W64Done = pure Stop---- | Stream a 'Word64' as eight bytes, the first byte is the MSB of the Word64--- and last byte is the LSB (big endian representation).------ /Pre-release/----{-# INLINE word64le #-}-word64le :: Monad m => Word64 -> Stream m Word8-word64le = word64leD--{-# INLINE int8 #-}-int8 :: Applicative m => Int8 -> Stream m Word8-int8 i = word8 (fromIntegral i :: Word8)---- | Stream a 'Int16' as two bytes, the first byte is the MSB of the Int16--- and second byte is the LSB (big endian representation).------ /Pre-release/----{-# INLINE int16be #-}-int16be :: Monad m => Int16 -> Stream m Word8-int16be i = word16be (fromIntegral i :: Word16)---- | Stream a 'Int16' as two bytes, the first byte is the LSB of the Int16--- and second byte is the MSB (little endian representation).------ /Pre-release/----{-# INLINE int16le #-}-int16le :: Monad m => Int16 -> Stream m Word8-int16le i = word16le (fromIntegral i :: Word16)---- | Stream a 'Int32' as four bytes, the first byte is the MSB of the Int32--- and last byte is the LSB (big endian representation).------ /Pre-release/----{-# INLINE int32be #-}-int32be :: Monad m => Int32 -> Stream m Word8-int32be i = word32be (fromIntegral i :: Word32)--{-# INLINE int32le #-}-int32le :: Monad m => Int32 -> Stream m Word8-int32le i = word32le (fromIntegral i :: Word32)---- | Stream a 'Int64' as eight bytes, the first byte is the MSB of the Int64--- and last byte is the LSB (big endian representation).------ /Pre-release/----{-# INLINE int64be #-}-int64be :: Monad m => Int64 -> Stream m Word8-int64be i = word64be (fromIntegral i :: Word64)---- | Stream a 'Int64' as eight bytes, the first byte is the LSB of the Int64--- and last byte is the MSB (little endian representation).------ /Pre-release/----{-# INLINE int64le #-}-int64le :: Monad m => Int64 -> Stream m Word8-int64le i = word64le (fromIntegral i :: Word64)---- | Big endian (MSB first) Float-{-# INLINE float32be #-}-float32be :: Monad m => Float -> Stream m Word8-float32be = word32beD . castFloatToWord32---- | Little endian (LSB first) Float-{-# INLINE float32le #-}-float32le :: Monad m => Float -> Stream m Word8-float32le = word32leD . castFloatToWord32---- | Big endian (MSB first) Double-{-# INLINE double64be #-}-double64be :: Monad m => Double -> Stream m Word8-double64be = word64beD . castDoubleToWord64---- | Little endian (LSB first) Double-{-# INLINE double64le #-}-double64le :: Monad m => Double -> Stream m Word8-double64le = word64leD . castDoubleToWord64---- | Encode a Unicode character to stream of bytes in 0-255 range.----{-# INLINE charLatin1 #-}-charLatin1 :: Applicative m => Char -> Stream m Word8-charLatin1 = Stream.fromPure . fromIntegral . ord--{-# INLINE charUtf8 #-}-charUtf8 :: Monad m => Char -> Stream m Word8-charUtf8 = Stream.unfold readCharUtf8------------------------------------------------------------------------------------ Host byte order------------------------------------------------------------------------------------ | Stream a 'Word64' as eight bytes in the host byte order.------ /Pre-release/----{-# INLINE word64host #-}-word64host :: Monad m => Word64 -> Stream m Word8-word64host =-#ifdef WORDS_BIGENDIAN- word64be-#else- word64le-#endif------------------------------------------------------------------------------------ Type class----------------------------------------------------------------------------------class ToBytes a where- -- | Convert a Haskell type to a byte stream.- toBytes :: a -> Stream m Word8
src/Streamly/Internal/Unicode/Array.hs view
@@ -14,6 +14,12 @@ -- module Streamly.Internal.Unicode.Array (+ -- * Setup+ -- | To execute the code examples provided in this module in ghci, please+ -- run the following commands first.+ --+ -- $setup+ -- * Streams of Strings lines , words@@ -49,7 +55,7 @@ -- {-# INLINE lines #-} lines :: MonadIO m => Stream m Char -> Stream m (Array Char)-lines = S.lines A.write+lines = S.lines A.create -- | Break a string up into a stream of strings, which were delimited -- by characters representing white space.@@ -61,7 +67,7 @@ -- {-# INLINE words #-} words :: MonadIO m => Stream m Char -> Stream m (Array Char)-words = S.words A.write+words = S.words A.create -- | Flattens the stream of @Array Char@, after appending a terminating -- newline to each string.
src/Streamly/Internal/Unicode/Parser.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} -- | -- Module : Streamly.Internal.Unicode.Parser -- Copyright : (c) 2021 Composewell Technologies@@ -12,6 +13,12 @@ module Streamly.Internal.Unicode.Parser (+ -- * Setup+ -- | To execute the code examples provided in this module in ghci, please+ -- run the following commands first.+ --+ -- $setup+ -- * Generic char , charIgnoreCase@@ -47,16 +54,23 @@ -- * Numeric , signed+ , number+ , doubleParser , double , decimal , hexadecimal++ -- * Utilities+ , mkDouble ) where import Control.Applicative (Alternative(..))-import Data.Bits (Bits, (.|.), shiftL)+import Data.Bits (Bits, (.|.), shiftL, (.&.)) import Data.Char (ord)-import Streamly.Internal.Data.Parser (Parser)+import Data.Ratio ((%))+import Fusion.Plugin.Types (Fuse(..))+import Streamly.Internal.Data.Parser (Parser(..), Initial(..), Step(..), Final(..)) import qualified Data.Char as Char import qualified Streamly.Data.Fold as Fold@@ -69,6 +83,8 @@ , dropWhile ) +#include "DocTestUnicodeParser.hs"+ -------------------------------------------------------------------------------- -- Character classification --------------------------------------------------------------------------------@@ -263,36 +279,349 @@ signed :: (Num a, Monad m) => Parser Char m a -> Parser Char m a signed p = (negate <$> (char '-' *> p)) <|> (char '+' *> p) <|> p --- | Parse a 'Double'.+-- XXX Change Multiplier to Sign+type Multiplier = Int++-- XXX We can use Int instead of Integer to make it twice as fast. But then we+-- will have to truncate the significant digits before overflow occurs.+type Number = Integer+type DecimalPlaces = Int+type PowerMultiplier = Int+type Power = Int++{-# ANN type ScientificParseState Fuse #-}+data ScientificParseState+ = SPInitial+ | SPSign !Multiplier+ | SPAfterSign !Multiplier !Number+ | SPDot !Multiplier !Number+ | SPAfterDot !Multiplier !Number !DecimalPlaces+ | SPExponent !Multiplier !Number !DecimalPlaces+ | SPExponentWithSign !Multiplier !Number !DecimalPlaces !PowerMultiplier+ | SPAfterExponent !Multiplier !Number !DecimalPlaces !PowerMultiplier !Power++-- XXX See https://hackage.haskell.org/package/integer-conversion for large+-- integers.++-- | A generic parser for scientific notation of numbers. Returns (mantissa,+-- exponent) tuple. The result can be mapped to 'Double' or any other number+-- representation e.g. @Scientific@. ----- This parser accepts an optional leading sign character, followed by--- at most one decimal digit. The syntax is similar to that accepted by--- the 'read' function, with the exception that a trailing @\'.\'@ is--- consumed.+-- For example, using the @scientific@ package:+-- >> parserScientific = uncurry Data.Scientific.scientific <$> 'number'+{-# INLINE number #-}+number :: Monad m => Parser Char m (Integer, Int)+number = Parser (\s a -> return $ step s a) initial (return . extract)++ where++ intToInteger :: Int -> Integer+ intToInteger = fromIntegral++ combineNum buf num = buf * 10 + num++ {-# INLINE initial #-}+ initial = pure $ IPartial SPInitial++ exitSPInitial msg =+ "number: expecting sign or decimal digit, got " ++ msg+ exitSPSign msg =+ "number: expecting decimal digit, got " ++ msg+ exitSPAfterSign multiplier num = (intToInteger multiplier * num, 0)+ exitSPAfterDot multiplier num decimalPlaces =+ ( intToInteger multiplier * num+ , -decimalPlaces+ )+ exitSPAfterExponent mult num decimalPlaces powerMult powerNum =+ let e = powerMult * powerNum - decimalPlaces+ in (intToInteger mult * num, e)++ {-# INLINE step #-}+ step SPInitial val =+ case val of+ '+' -> SContinue 1 (SPSign 1)+ '-' -> SContinue 1 (SPSign (-1))+ _ -> do+ let num = ord val - 48+ if num >= 0 && num <= 9+ then SPartial 1 $ SPAfterSign 1 (intToInteger num)+ else SError $ exitSPInitial $ show val+ step (SPSign multiplier) val =+ let num = ord val - 48+ in if num >= 0 && num <= 9+ then SPartial 1 $ SPAfterSign multiplier (intToInteger num)+ else SError $ exitSPSign $ show val+ step (SPAfterSign multiplier buf) val =+ case val of+ '.' -> SContinue 1 $ SPDot multiplier buf+ 'e' -> SContinue 1 $ SPExponent multiplier buf 0+ 'E' -> SContinue 1 $ SPExponent multiplier buf 0+ _ ->+ let num = ord val - 48+ in if num >= 0 && num <= 9+ then+ SPartial 1+ $ SPAfterSign multiplier (combineNum buf (intToInteger num))+ else SDone 0 $ exitSPAfterSign multiplier buf+ step (SPDot multiplier buf) val =+ let num = ord val - 48+ in if num >= 0 && num <= 9+ then SPartial 1 $ SPAfterDot multiplier (combineNum buf (intToInteger num)) 1+ else SDone (-1) $ exitSPAfterSign multiplier buf+ step (SPAfterDot multiplier buf decimalPlaces) val =+ case val of+ 'e' -> SContinue 1 $ SPExponent multiplier buf decimalPlaces+ 'E' -> SContinue 1 $ SPExponent multiplier buf decimalPlaces+ _ ->+ let num = ord val - 48+ in if num >= 0 && num <= 9+ then+ SPartial 1+ $ SPAfterDot+ multiplier+ (combineNum buf (intToInteger num))+ (decimalPlaces + 1)+ else SDone 0 $ exitSPAfterDot multiplier buf decimalPlaces+ step (SPExponent multiplier buf decimalPlaces) val =+ case val of+ '+' -> SContinue 1 (SPExponentWithSign multiplier buf decimalPlaces 1)+ '-' -> SContinue 1 (SPExponentWithSign multiplier buf decimalPlaces (-1))+ _ -> do+ let num = ord val - 48+ if num >= 0 && num <= 9+ then SPartial 1 $ SPAfterExponent multiplier buf decimalPlaces 1 num+ else SDone (-1) $ exitSPAfterDot multiplier buf decimalPlaces+ step (SPExponentWithSign mult buf decimalPlaces powerMult) val =+ let num = ord val - 48+ in if num >= 0 && num <= 9+ then SPartial 1 $ SPAfterExponent mult buf decimalPlaces powerMult num+ else SDone (-2) $ exitSPAfterDot mult buf decimalPlaces+ step (SPAfterExponent mult num decimalPlaces powerMult buf) val =+ let n = ord val - 48+ in if n >= 0 && n <= 9+ then+ SPartial 1+ $ SPAfterExponent+ mult num decimalPlaces powerMult (combineNum buf n)+ else+ SDone 0+ $ exitSPAfterExponent mult num decimalPlaces powerMult buf++ {-# INLINE extract #-}+ extract SPInitial = FError $ exitSPInitial "end of input"+ extract (SPSign _) = FError $ exitSPSign "end of input"+ extract (SPAfterSign mult num) = FDone 0 $ exitSPAfterSign mult num+ extract (SPDot mult num) = FDone (-1) $ exitSPAfterSign mult num+ extract (SPAfterDot mult num decimalPlaces) =+ FDone 0 $ exitSPAfterDot mult num decimalPlaces+ extract (SPExponent mult num decimalPlaces) =+ FDone (-1) $ exitSPAfterDot mult num decimalPlaces+ extract (SPExponentWithSign mult num decimalPlaces _) =+ FDone (-2) $ exitSPAfterDot mult num decimalPlaces+ extract (SPAfterExponent mult num decimalPlaces powerMult powerNum) =+ FDone 0 $ exitSPAfterExponent mult num decimalPlaces powerMult powerNum++type MantissaInt = Int+type OverflowPower = Int++{-# ANN type DoubleParseState Fuse #-}+data DoubleParseState+ = DPInitial+ | DPSign !Multiplier+ | DPAfterSign !Multiplier !MantissaInt !OverflowPower+ | DPDot !Multiplier !MantissaInt !OverflowPower+ | DPAfterDot !Multiplier !MantissaInt !OverflowPower+ | DPExponent !Multiplier !MantissaInt !OverflowPower+ | DPExponentWithSign !Multiplier !MantissaInt !OverflowPower !PowerMultiplier+ | DPAfterExponent !Multiplier !MantissaInt !OverflowPower !PowerMultiplier !Power++-- | A fast, custom parser for double precision flaoting point numbers. Returns+-- (mantissa, exponent) tuple. This is much faster than 'number' because it+-- assumes the number will fit in a 'Double' type and uses 'Int' representation+-- to store mantissa. ----- === Examples+-- Number larger than 'Double' may overflow. Int overflow is not checked in the+-- exponent. ----- Examples with behaviour identical to 'read', if you feed an empty--- continuation to the first result:+{-# INLINE doubleParser #-}+doubleParser :: Monad m => Parser Char m (Int, Int)+doubleParser = Parser (\s a -> return $ step s a) initial (return . extract)++ where++ -- XXX Assuming Int = Int64++ -- Up to 58 bits Int won't overflow+ -- ghci> (2^59-1)*10+9 :: Int+ -- 5764607523034234879+ mask :: Word+ mask = 0x7c00000000000000 -- 58 bits, ignore the sign bit++ {-# INLINE combineNum #-}+ combineNum :: Int -> Int -> Int -> (Int, Int)+ combineNum mantissa power num =+ if fromIntegral mantissa .&. mask == 0+ then (mantissa * 10 + num, power)+ else (mantissa, power + 1)++ {-# INLINE initial #-}+ initial = pure $ IPartial DPInitial++ exitDPInitial msg =+ "number: expecting sign or decimal digit, got " ++ msg+ exitDPSign msg =+ "number: expecting decimal digit, got " ++ msg+ exitDPAfterSign multiplier num opower = (fromIntegral multiplier * num, opower)+ exitDPAfterDot multiplier num opow =+ (fromIntegral multiplier * num , opow)+ exitDPAfterExponent mult num opow powerMult powerNum =+ (fromIntegral mult * num, opow + powerMult * powerNum)++ {-# INLINE step #-}+ step DPInitial val =+ case val of+ '+' -> SContinue 1 (DPSign 1)+ '-' -> SContinue 1 (DPSign (-1))+ _ -> do+ let num = ord val - 48+ if num >= 0 && num <= 9+ then SPartial 1 $ DPAfterSign 1 num 0+ else SError $ exitDPInitial $ show val+ step (DPSign multiplier) val =+ let num = ord val - 48+ in if num >= 0 && num <= 9+ then SPartial 1 $ DPAfterSign multiplier num 0+ else SError $ exitDPSign $ show val+ step (DPAfterSign multiplier buf opower) val =+ case val of+ '.' -> SContinue 1 $ DPDot multiplier buf opower+ 'e' -> SContinue 1 $ DPExponent multiplier buf opower+ 'E' -> SContinue 1 $ DPExponent multiplier buf opower+ _ ->+ let num = ord val - 48+ in if num >= 0 && num <= 9+ then+ let (buf1, power1) = combineNum buf opower num+ in SPartial 1+ $ DPAfterSign multiplier buf1 power1+ else SDone 0 $ exitDPAfterSign multiplier buf opower+ step (DPDot multiplier buf opower) val =+ let num = ord val - 48+ in if num >= 0 && num <= 9+ then+ let (buf1, power1) = combineNum buf opower num+ in SPartial 1 $ DPAfterDot multiplier buf1 (power1 - 1)+ else SDone (-1) $ exitDPAfterSign multiplier buf opower+ step (DPAfterDot multiplier buf opower) val =+ case val of+ 'e' -> SContinue 1 $ DPExponent multiplier buf opower+ 'E' -> SContinue 1 $ DPExponent multiplier buf opower+ _ ->+ let num = ord val - 48+ in if num >= 0 && num <= 9+ then+ let (buf1, power1) = combineNum buf opower num+ in SPartial 1 $ DPAfterDot multiplier buf1 (power1 - 1)+ else SDone 0 $ exitDPAfterDot multiplier buf opower+ step (DPExponent multiplier buf opower) val =+ case val of+ '+' -> SContinue 1 (DPExponentWithSign multiplier buf opower 1)+ '-' -> SContinue 1 (DPExponentWithSign multiplier buf opower (-1))+ _ -> do+ let num = ord val - 48+ if num >= 0 && num <= 9+ then SPartial 1 $ DPAfterExponent multiplier buf opower 1 num+ else SDone (-1) $ exitDPAfterDot multiplier buf opower+ step (DPExponentWithSign mult buf opower powerMult) val =+ let num = ord val - 48+ in if num >= 0 && num <= 9+ then SPartial 1 $ DPAfterExponent mult buf opower powerMult num+ else SDone (-2) $ exitDPAfterDot mult buf opower+ step (DPAfterExponent mult num opower powerMult buf) val =+ let n = ord val - 48+ in if n >= 0 && n <= 9+ then+ SPartial 1+ $ DPAfterExponent mult num opower powerMult (buf * 10 + n)+ else SDone 0 $ exitDPAfterExponent mult num opower powerMult buf++ {-# INLINE extract #-}+ extract DPInitial = FError $ exitDPInitial "end of input"+ extract (DPSign _) = FError $ exitDPSign "end of input"+ extract (DPAfterSign mult num opow) = FDone 0 $ exitDPAfterSign mult num opow+ extract (DPDot mult num opow) = FDone (-1) $ exitDPAfterSign mult num opow+ extract (DPAfterDot mult num opow) =+ FDone 0 $ exitDPAfterDot mult num opow+ extract (DPExponent mult num opow) =+ FDone (-1) $ exitDPAfterDot mult num opow+ extract (DPExponentWithSign mult num opow _) =+ FDone (-2) $ exitDPAfterDot mult num opow+ extract (DPAfterExponent mult num opow powerMult powerNum) =+ FDone 0 $ exitDPAfterExponent mult num opow powerMult powerNum++-- XXX We can have a `realFloat` parser instead to parse any RealFloat value.+-- And a integral parser to read any integral value.++-- XXX This is very expensive, takes much more time than the rest of the+-- parsing. Need to look into fromRational.++-- | @mkDouble mantissa exponent@ converts a mantissa and exponent to a+-- 'Double' value equivalent to @mantissa * 10^exponent@. It does not check for+-- overflow, powers more than 308 will overflow.+{-# INLINE mkDouble #-}+mkDouble :: Integer -> Int -> Double+mkDouble mantissa power =+ if power > 0+ then fromRational ((mantissa * 10 ^ power) % 1)+ else fromRational (mantissa % 10 ^ (-power))++-- | Parse a decimal 'Double' value. This parser accepts an optional sign (+ or+-- -) followed by at least one decimal digit. Decimal digits are optionally+-- followed by a decimal point and at least one decimal digit after the point.+-- This parser accepts the maximal valid input as long as it gives a valid+-- number. Specifcally a trailing decimal point is allowed but not consumed.+-- This function does not accept \"NaN\" or \"Infinity\" string representations+-- of double values. ----- > IS.parse double (IS.fromList "3") == 3.0--- > IS.parse double (IS.fromList "3.1") == 3.1--- > IS.parse double (IS.fromList "3e4") == 30000.0--- > IS.parse double (IS.fromList "3.1e4") == 31000.0--- > IS.parse double (IS.fromList "3e") == 30+-- Definition: ----- Examples with behaviour identical to 'read':+-- >>> double = uncurry Unicode.mkDouble <$> Unicode.number ----- > IS.parse (IS.fromList ".3") == error "Parse failed"--- > IS.parse (IS.fromList "e3") == error "Parse failed"+-- Examples: ----- Example of difference from 'read':+-- >>> p = Stream.parsePos Unicode.double . Stream.fromList ----- > IS.parse double (IS.fromList "3.foo") == 3.0+-- >>> p "-1.23e-123"+-- Right (-1.23e-123) ----- This function does not accept string representations of \"NaN\" or--- \"Infinity\".+-- Trailing input examples: ----- /Unimplemented/-double :: Parser Char m Double-double = undefined+-- >>> p "1."+-- Right 1.0+--+-- >>> p "1.2.3"+-- Right 1.2+--+-- >>> p "1e"+-- Right 1.0+--+-- >>> p "1e2.3"+-- Right 100.0+--+-- >>> p "1+2"+-- Right 1.0+--+-- Error cases:+--+-- >>> p ""+-- Left (ParseErrorPos 0 "number: expecting sign or decimal digit, got end of input")+--+-- >>> p ".1"+-- Left (ParseErrorPos 1 "number: expecting sign or decimal digit, got '.'")+--+-- >>> p "+"+-- Left (ParseErrorPos 1 "number: expecting decimal digit, got end of input")+--+{-# INLINE double #-}+double :: Monad m => Parser Char m Double+double = fmap (\(m,e) -> mkDouble (fromIntegral m) e) doubleParser
src/Streamly/Internal/Unicode/Stream.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} -- | -- Module : Streamly.Internal.Unicode.Stream -- Copyright : (c) 2018 Composewell Technologies@@ -11,10 +12,18 @@ module Streamly.Internal.Unicode.Stream (+ -- * Setup+ -- | To execute the code examples provided in this module in ghci, please+ -- run the following commands first.+ --+ -- $setup++ -- XXX Use to/from instead of encode/decode for more compact naming.+ -- * Construction (Decoding) decodeLatin1 - -- ** UTF-8 Decoding+ -- ** UTF-8 Byte Stream Decoding , CodingFailureMode(..) , writeCharUtf8' , parseCharUtf8With@@ -22,7 +31,11 @@ , decodeUtf8' , decodeUtf8_ - -- ** Resumable UTF-8 Decoding+ -- ** UTF-16 Byte Stream Decoding+ , decodeUtf16le'+ , decodeUtf16le++ -- ** Resumable UTF-8 Byte Stream Decoding , DecodeError(..) , DecodeState , CodePoint@@ -33,14 +46,15 @@ , decodeUtf8Chunks , decodeUtf8Chunks' , decodeUtf8Chunks_+ -- , fromUtf8ChunksEndByLn -- * Elimination (Encoding)- -- ** Latin1 Encoding+ -- ** Latin1 Encoding to Byte Stream , encodeLatin1 , encodeLatin1' , encodeLatin1_ - -- ** UTF-8 Encoding+ -- ** UTF-8 Encoding to Byte Stream , readCharUtf8' , readCharUtf8 , readCharUtf8_@@ -48,6 +62,21 @@ , encodeUtf8' , encodeUtf8_ , encodeStrings++ -- ** UTF-8 Encoding to Chunk Stream+ -- , toUtf8Chunks+ -- , toUtf8Chunks'+ -- , toUtf8Chunks_+ -- , toUtf8ChunksEndByLn++ -- , toPinnedUtf8Chunks+ -- , toPinnedUtf8Chunks'+ -- , toPinnedUtf8Chunks_+ -- , toPinnedUtf8ChunksEndByLn++ -- ** UTF-16 Encoding to Byte Stream+ , encodeUtf16le'+ , encodeUtf16le {- -- * Operations on character strings , strip -- (dropAround isSpace)@@ -56,10 +85,10 @@ -- * Transformation , stripHead- , lines- , words- , unlines- , unwords+ , lines -- foldLines+ , words -- foldWords+ , unlines -- unfoldLines+ , unwords -- unfoldWords -- * StreamD UTF8 Encoding / Decoding transformations. , decodeUtf8D@@ -74,6 +103,10 @@ -- * Decoding String Literals , fromStr# + -- * Word16 Utilities+ , mkEvenW8Chunks+ , swapByteOrder+ -- * Deprecations , decodeUtf8Lax , encodeLatin1Lax@@ -83,6 +116,10 @@ #include "inline.hs" +-- MachDeps.h includes ghcautoconf.h that defines WORDS_BIGENDIAN for big endian+-- systems.+#include "MachDeps.h"+ import Control.Monad (void) import Control.Monad.IO.Class (MonadIO, liftIO) import Data.Bits (shiftR, shiftL, (.|.), (.&.))@@ -90,7 +127,7 @@ #if MIN_VERSION_base(4,17,0) import Data.Char (generalCategory, GeneralCategory(Space)) #endif-import Data.Word (Word8)+import Data.Word (Word8, Word16) import Foreign.Marshal.Alloc (mallocBytes) import Foreign.Storable (Storable(..)) #ifndef __GHCJS__@@ -102,34 +139,28 @@ import GHC.Ptr (Ptr (..), plusPtr) import System.IO.Unsafe (unsafePerformIO) import Streamly.Internal.Data.Array.Type (Array(..))-import Streamly.Internal.Data.Array.Mut.Type (MutableByteArray)+import Streamly.Internal.Data.MutByteArray.Type (MutByteArray) import Streamly.Internal.Data.Fold (Fold)-import Streamly.Internal.Data.Stream.StreamD (Stream)-import Streamly.Internal.Data.Stream.StreamD (Step (..))+import Streamly.Internal.Data.Parser (Parser)+import Streamly.Internal.Data.Stream (Stream)+import Streamly.Internal.Data.Stream (Step (..)) import Streamly.Internal.Data.SVar.Type (adaptState) import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))-import Streamly.Internal.Data.Unboxed (peekWith)+import Streamly.Internal.Data.Unbox (Unbox(peekAt)) import Streamly.Internal.Data.Unfold.Type (Unfold(..)) import Streamly.Internal.System.IO (unsafeInlineIO) import qualified Streamly.Data.Fold as Fold import qualified Streamly.Data.Unfold as Unfold-import qualified Streamly.Internal.Data.Array.Type as Array+import qualified Streamly.Internal.Data.Array as Array import qualified Streamly.Internal.Data.Parser as Parser (Parser)-import qualified Streamly.Internal.Data.Parser.ParserD as ParserD-import qualified Streamly.Internal.Data.Stream.StreamD as Stream-import qualified Streamly.Internal.Data.Stream.StreamD as D+import qualified Streamly.Internal.Data.Parser as ParserD+import qualified Streamly.Internal.Data.Stream as Stream+import qualified Streamly.Internal.Data.Stream as D import Prelude hiding (lines, words, unlines, unwords) --- $setup--- >>> :m--- >>> :set -XMagicHash--- >>> import Prelude hiding (lines, words, unlines, unwords)--- >>> import qualified Streamly.Data.Stream as Stream--- >>> import qualified Streamly.Data.Fold as Fold--- >>> import qualified Streamly.Internal.Unicode.Stream as Unicode--- >>> import Streamly.Internal.Unicode.Stream+#include "DocTestUnicodeStream.hs" ------------------------------------------------------------------------------- -- Latin1 decoding@@ -462,15 +493,15 @@ handleError err souldBackTrack = case cfm of- ErrorOnCodingFailure -> ParserD.Error err+ ErrorOnCodingFailure -> ParserD.SError err TransliterateCodingFailure -> case souldBackTrack of- True -> ParserD.Done 1 replacementChar- False -> ParserD.Done 0 replacementChar+ True -> ParserD.SDone 0 replacementChar+ False -> ParserD.SDone 1 replacementChar DropOnCodingFailure -> case souldBackTrack of- True -> ParserD.Continue 1 UTF8CharDecodeInit- False -> ParserD.Continue 0 UTF8CharDecodeInit+ True -> ParserD.SContinue 0 UTF8CharDecodeInit+ False -> ParserD.SContinue 1 UTF8CharDecodeInit {-# INLINE step' #-} step' table UTF8CharDecodeInit x =@@ -480,7 +511,7 @@ -- change with the compiler versions, we need a more reliable -- "likely" primitive to control branch predication. return $ case x > 0x7f of- False -> ParserD.Done 0 $ unsafeChr $ fromIntegral x+ False -> ParserD.SDone 1 $ unsafeChr $ fromIntegral x True -> let (Tuple' sv cp) = decode0 table x in case sv of@@ -489,12 +520,12 @@ ++ "Invalid first UTF8 byte" ++ show x in handleError msg False 0 -> error $ prefix ++ "unreachable state"- _ -> ParserD.Continue 0 (UTF8CharDecoding sv cp)+ _ -> ParserD.SContinue 1 (UTF8CharDecoding sv cp) step' table (UTF8CharDecoding statePtr codepointPtr) x = return $ let (Tuple' sv cp) = decode1 table statePtr codepointPtr x in case sv of- 0 -> ParserD.Done 0 $ unsafeChr cp+ 0 -> ParserD.SDone 1 $ unsafeChr cp 12 -> let msg = prefix ++ "Invalid subsequent UTF8 byte"@@ -504,16 +535,16 @@ ++ "accumulated value" ++ show codepointPtr in handleError msg True- _ -> ParserD.Continue 0 (UTF8CharDecoding sv cp)+ _ -> ParserD.SContinue 1 (UTF8CharDecoding sv cp) {-# INLINE extract #-} extract UTF8CharDecodeInit = error $ prefix ++ "Not enough input" extract (UTF8CharDecoding _ _) = case cfm of ErrorOnCodingFailure ->- return $ ParserD.Error $ prefix ++ "Not enough input"+ return $ ParserD.FError $ prefix ++ "Not enough input" TransliterateCodingFailure ->- return (ParserD.Done 0 replacementChar)+ return (ParserD.FDone 0 replacementChar) -- XXX We shouldn't error out here. There is no way to represent an -- empty parser result unless we return a "Maybe" type. DropOnCodingFailure -> error $ prefix ++ "Not enough input"@@ -523,8 +554,8 @@ -- workflow requires backtracking 1 element. This can be revisited once "Fold" -- supports backtracking. {-# INLINE writeCharUtf8' #-}-writeCharUtf8' :: Monad m => Fold m Word8 Char-writeCharUtf8' = ParserD.toFold (parseCharUtf8WithD ErrorOnCodingFailure)+writeCharUtf8' :: Monad m => Parser Word8 m Char+writeCharUtf8' = parseCharUtf8WithD ErrorOnCodingFailure -- XXX The initial idea was to have "parseCharUtf8" and offload the error -- handling to another parser. So, say we had "parseCharUtf8'",@@ -556,7 +587,7 @@ where - prefix = "Streamly.Internal.Data.Stream.StreamD.decodeUtf8With: "+ prefix = "Streamly.Internal.Data.Stream.decodeUtf8With: " {-# INLINE handleError #-} handleError e s =@@ -676,6 +707,188 @@ decodeUtf8Lax = decodeUtf8 -------------------------------------------------------------------------------+-- Decoding Utf16+-------------------------------------------------------------------------------++data MkEvenW8ChunksState s w8 arr+ = MECSInit s+ | MECSBuffer w8 s+ | MECSYieldAndInit arr s+ | MECSYieldAndBuffer arr w8 s++-- | Ensure chunks of even length. This can be used before casting the arrays to+-- Word16. Use this API when interacting with external data.+--+-- The chunks are split and merged accordingly to create arrays of even length.+-- If the sum of length of all the arrays in the stream is odd then the trailing+-- byte of the last array is dropped.+--+{-# INLINE_NORMAL mkEvenW8Chunks #-}+mkEvenW8Chunks :: Monad m => Stream m (Array Word8) -> Stream m (Array Word8)+mkEvenW8Chunks (D.Stream step state) = D.Stream step1 (MECSInit state)++ where++ {-# INLINE_LATE step1 #-}+ step1 gst (MECSInit st) = do+ r <- step (adaptState gst) st+ return $+ case r of+ Yield arr st1 ->+ let len = Array.length arr+ in if (len .&. 1) == 1+ then let arr1 = Array.unsafeSliceOffLen 0 (len - 1) arr+ remElem = Array.unsafeGetIndex (len - 1) arr+ in Yield arr1 (MECSBuffer remElem st1)+ else Yield arr (MECSInit st1)+ Skip s -> Skip (MECSInit s)+ Stop -> Stop+ step1 gst (MECSBuffer remElem st) = do+ r <- step (adaptState gst) st+ return $+ case r of+ Yield arr st1 | Array.length arr == 0 ->+ Skip (MECSBuffer remElem st1)+ Yield arr st1 | Array.length arr == 1 ->+ let fstElem = Array.unsafeGetIndex 0 arr+ w16 = Array.fromList [remElem, fstElem]+ in Yield w16 (MECSInit st1)+ Yield arr st1 ->+ let len = Array.length arr+ in if (len .&. 1) == 1+ then let arr1 = Array.unsafeSliceOffLen 1 (len - 1) arr+ fstElem = Array.unsafeGetIndex 0 arr+ w16 = Array.fromList [remElem, fstElem]+ in Yield w16 (MECSYieldAndInit arr1 st1)+ else let arr1 = Array.unsafeSliceOffLen 1 (len - 2) arr+ fstElem = Array.unsafeGetIndex 0 arr+ lstElem = Array.unsafeGetIndex (len - 1) arr+ w16 = Array.fromList [remElem, fstElem]+ in Yield w16+ (MECSYieldAndBuffer arr1 lstElem st1)+ Skip s -> Skip (MECSBuffer remElem s)+ Stop -> Stop -- Here the last Word8 is lost+ step1 _ (MECSYieldAndInit arr st) =+ pure $ Yield arr (MECSInit st)+ step1 _ (MECSYieldAndBuffer arr lastElem st) =+ pure $ Yield arr (MECSBuffer lastElem st)++-- | Swap the byte order of Word16+--+-- > swapByteOrder 0xABCD == 0xCDAB+-- > swapByteOrder . swapByteOrder == id+{-# INLINE swapByteOrder #-}+swapByteOrder :: Word16 -> Word16+swapByteOrder w = (w `shiftL` 8) .|. (w `shiftR` 8)++data DecodeUtf16WithState w c s+ = U16NoSurrogate s+ | U16HighSurrogate w s+ | U16D+ | U16YAndC c (DecodeUtf16WithState w c s)++{-# INLINE_NORMAL decodeUtf16With #-}+decodeUtf16With ::+ Monad m+ => CodingFailureMode+ -> D.Stream m Word16+ -> D.Stream m Char+decodeUtf16With cfm (D.Stream step state) =+ D.Stream step1 (U16NoSurrogate state)++ where++ prefix = "Streamly.Internal.Unicode.Stream.decodeUtf16With: "++ {-# INLINE combineSurrogates #-}+ combineSurrogates hi lo =+ let first10 = fromIntegral (hi - utf16HighSurrogate) `shiftL` 10+ second10 = fromIntegral (lo - utf16LowSurrogate)+ in unsafeChr (0x10000 + (first10 .|. second10))++ {-# INLINE transliterateOrError #-}+ transliterateOrError e s =+ case cfm of+ ErrorOnCodingFailure -> error e+ TransliterateCodingFailure -> U16YAndC replacementChar s+ DropOnCodingFailure -> s++ {-# INLINE inputUnderflow #-}+ inputUnderflow =+ case cfm of+ ErrorOnCodingFailure -> error $ prefix ++ "Input Underflow"+ TransliterateCodingFailure -> U16YAndC replacementChar U16D+ DropOnCodingFailure -> U16D++ {-# INLINE_LATE step1 #-}+ step1 gst (U16NoSurrogate st) = do+ r <- step (adaptState gst) st+ pure $+ case r of+ Yield x st1+ | x < 0xD800 || x > 0xDFFF ->+ Yield (unsafeChr (fromIntegral x)) (U16NoSurrogate st1)+ | x >= 0xD800 && x <= 0xDBFF ->+ Skip (U16HighSurrogate x st1)+ | otherwise ->+ let msg = prefix+ ++ "Invalid first UTF16 word " ++ show x+ in Skip $+ transliterateOrError msg (U16NoSurrogate st1)+ Skip st1 -> Skip (U16NoSurrogate st1)+ Stop -> Stop+ step1 gst (U16HighSurrogate hi st) = do+ r <- step (adaptState gst) st+ pure $+ case r of+ Yield x st1+ | x >= 0xDC00 && x <= 0xDFFF ->+ Yield (combineSurrogates hi x) (U16NoSurrogate st1)+ | otherwise ->+ let msg = prefix+ ++ "Invalid subsequent UTF16 word " ++ show x+ ++ " in state " ++ show hi+ in Skip $+ transliterateOrError msg (U16NoSurrogate st1)+ Skip st1 -> Skip (U16HighSurrogate hi st1)+ Stop -> Skip inputUnderflow+ step1 _ (U16YAndC x st) = pure $ Yield x st+ step1 _ U16D = pure Stop++{-# INLINE decodeUtf16' #-}+decodeUtf16' :: Monad m => Stream m Word16 -> Stream m Char+decodeUtf16' = decodeUtf16With ErrorOnCodingFailure++{-# INLINE decodeUtf16 #-}+decodeUtf16 :: Monad m => Stream m Word16 -> Stream m Char+decodeUtf16 = decodeUtf16With TransliterateCodingFailure++-- | Similar to 'decodeUtf16le' but throws an error if an invalid codepoint is+-- encountered.+--+{-# INLINE decodeUtf16le' #-}+decodeUtf16le' :: Monad m => Stream m Word16 -> Stream m Char+decodeUtf16le' =+ decodeUtf16'+#ifdef WORDS_BIGENDIAN+ . fmap swapByteOrder+#endif++-- | Decode a UTF-16 encoded stream to a stream of Unicode characters. Any+-- invalid codepoint encountered is replaced with the unicode replacement+-- character.+--+-- The Word16s are expected to be in the little-endian byte order.+--+{-# INLINE decodeUtf16le #-}+decodeUtf16le :: Monad m => Stream m Word16 -> Stream m Char+decodeUtf16le =+ decodeUtf16+#ifdef WORDS_BIGENDIAN+ . fmap swapByteOrder+#endif++------------------------------------------------------------------------------- -- Decoding Array Streams ------------------------------------------------------------------------------- @@ -684,9 +897,9 @@ #endif data FlattenState s = OuterLoop s !(Maybe (DecodeState, CodePoint))- | InnerLoopDecodeInit s MutableByteArray !Int !Int- | InnerLoopDecodeFirst s MutableByteArray !Int !Int Word8- | InnerLoopDecoding s MutableByteArray !Int !Int+ | InnerLoopDecodeInit s MutByteArray !Int !Int+ | InnerLoopDecodeFirst s MutByteArray !Int !Int Word8+ | InnerLoopDecoding s MutByteArray !Int !Int !DecodeState !CodePoint | YAndC !Char (FlattenState s) -- These constructors can be -- encoded in the UTF8DecodeState@@ -720,7 +933,7 @@ case cfm of ErrorOnCodingFailure -> error $- show "Streamly.Internal.Data.Stream.StreamD."+ show "Streamly.Internal.Data.Stream." ++ "decodeUtf8ArraysWith: Input Underflow" TransliterateCodingFailure -> YAndC replacementChar D DropOnCodingFailure -> D@@ -745,7 +958,7 @@ | p == end = do return $ Skip $ OuterLoop st Nothing step' _ _ (InnerLoopDecodeInit st contents p end) = do- x <- liftIO $ peekWith contents p+ x <- liftIO $ peekAt p contents -- 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@@ -769,7 +982,7 @@ Skip $ transliterateOrError (- "Streamly.Internal.Data.Stream.StreamD."+ "Streamly.Internal.Data.Stream." ++ "decodeUtf8ArraysWith: Invalid UTF8" ++ " codepoint encountered" )@@ -779,7 +992,7 @@ step' _ _ (InnerLoopDecoding st _ p end sv cp) | p == end = return $ Skip $ OuterLoop st (Just (sv, cp)) step' table _ (InnerLoopDecoding st contents p end statePtr codepointPtr) = do- x <- liftIO $ peekWith contents p+ x <- liftIO $ peekAt p contents let (Tuple' sv cp) = decode1 table statePtr codepointPtr x return $ case sv of@@ -792,7 +1005,7 @@ Skip $ transliterateOrError (- "Streamly.Internal.Data.Stream.StreamD."+ "Streamly.Internal.Data.Stream." ++ "decodeUtf8ArraysWith: Invalid UTF8" ++ " codepoint encountered" )@@ -834,12 +1047,12 @@ -- Encoding Unicode (UTF-8) Characters ------------------------------------------------------------------------------- -data WList = WCons !Word8 !WList | WNil+data WList a = WCons !a !(WList a) | WNil -- UTF-8 primitives, Lifted from GHC.IO.Encoding.UTF8. {-# INLINE ord2 #-}-ord2 :: Char -> WList+ord2 :: Char -> (WList Word8) ord2 c = assert (n >= 0x80 && n <= 0x07ff) (WCons x1 (WCons x2 WNil)) where n = ord c@@ -847,7 +1060,7 @@ x2 = fromIntegral $ (n .&. 0x3F) + 0x80 {-# INLINE ord3 #-}-ord3 :: Char -> WList+ord3 :: Char -> (WList Word8) ord3 c = assert (n >= 0x0800 && n <= 0xffff) (WCons x1 (WCons x2 (WCons x3 WNil))) where n = ord c@@ -856,7 +1069,7 @@ x3 = fromIntegral $ (n .&. 0x3F) + 0x80 {-# INLINE ord4 #-}-ord4 :: Char -> WList+ord4 :: Char -> (WList Word8) ord4 c = assert (n >= 0x10000) (WCons x1 (WCons x2 (WCons x3 (WCons x4 WNil)))) where n = ord c@@ -866,7 +1079,7 @@ x4 = fromIntegral $ (n .&. 0x3F) + 0x80 {-# INLINE_NORMAL readCharUtf8With #-}-readCharUtf8With :: Monad m => WList -> Unfold m Char Word8+readCharUtf8With :: Monad m => (WList Word8) -> Unfold m Char Word8 readCharUtf8With surr = Unfold step inject where@@ -894,7 +1107,7 @@ -- paths (slow path). {-# INLINE_NORMAL encodeUtf8D' #-} encodeUtf8D' :: Monad m => D.Stream m Char -> D.Stream m Word8-encodeUtf8D' = D.unfoldMany readCharUtf8'+encodeUtf8D' = D.unfoldEach readCharUtf8' -- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. When -- any invalid character (U+D800-U+D8FF) is encountered in the input stream the@@ -913,7 +1126,7 @@ -- {-# INLINE_NORMAL encodeUtf8D #-} encodeUtf8D :: Monad m => D.Stream m Char -> D.Stream m Word8-encodeUtf8D = D.unfoldMany readCharUtf8+encodeUtf8D = D.unfoldEach readCharUtf8 -- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. Any -- Invalid characters (U+D800-U+D8FF) in the input stream are replaced by the@@ -929,7 +1142,7 @@ {-# INLINE_NORMAL encodeUtf8D_ #-} encodeUtf8D_ :: Monad m => D.Stream m Char -> D.Stream m Word8-encodeUtf8D_ = D.unfoldMany readCharUtf8_+encodeUtf8D_ = D.unfoldEach readCharUtf8_ -- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream. Any -- Invalid characters (U+D800-U+D8FF) in the input stream are dropped.@@ -946,9 +1159,84 @@ encodeUtf8Lax = encodeUtf8 -------------------------------------------------------------------------------+-- Encoding to Utf16+-------------------------------------------------------------------------------++{-# INLINE utf16LowSurrogate #-}+utf16LowSurrogate :: Word16+utf16LowSurrogate = 0xDC00++{-# INLINE utf16HighSurrogate #-}+utf16HighSurrogate :: Word16+utf16HighSurrogate = 0xD800++{-# INLINE_NORMAL readCharUtf16With #-}+readCharUtf16With :: Monad m => WList Word16 -> Unfold m Char Word16+readCharUtf16With invalidReplacement = Unfold step inject++ where++ inject c =+ return $ case ord c of+ x | x < 0xD800 -> fromIntegral x `WCons` WNil+ | x > 0xDFFF && x <= 0xFFFF -> fromIntegral x `WCons` WNil+ | x >= 0x10000 && x <= 0x10FFFF ->+ let u = x - 0x10000 -- 20 bits+ h = utf16HighSurrogate+ + fromIntegral (u `shiftR` 10) -- 10 bits+ l = utf16LowSurrogate+ + fromIntegral (u .&. 0x3FF) -- 10 bits+ in WCons h $ WCons l WNil+ | otherwise -> invalidReplacement++ {-# INLINE_LATE step #-}+ step WNil = return Stop+ step (WCons x xs) = return $ Yield x xs++{-# INLINE encodeUtf16' #-}+encodeUtf16' :: Monad m => Stream m Char -> Stream m Word16+encodeUtf16' = D.unfoldEach (readCharUtf16With errString)+ where+ errString =+ error+ $ "Streamly.Internal.Unicode.encodeUtf16': Encountered an \+ invalid character"++{-# INLINE encodeUtf16 #-}+encodeUtf16 :: Monad m => Stream m Char -> Stream m Word16+encodeUtf16 = D.unfoldEach (readCharUtf16With WNil)++-- | Similar to 'encodeUtf16le' but throws an error if any invalid character is+-- encountered.+--+{-# INLINE encodeUtf16le' #-}+encodeUtf16le' :: Monad m => Stream m Char -> Stream m Word16+encodeUtf16le' =+#ifdef WORDS_BIGENDIAN+ fmap swapByteOrder .+#endif+ encodeUtf16'++-- | Encode a stream of Unicode characters to a UTF-16 encoded stream. Any+-- invalid characters in the input stream are replaced by the Unicode+-- replacement character U+FFFD.+--+-- The resulting Word16s are encoded in little-endian byte order.+--+{-# INLINE encodeUtf16le #-}+encodeUtf16le :: Monad m => Stream m Char -> Stream m Word16+encodeUtf16le =+#ifdef WORDS_BIGENDIAN+ fmap swapByteOrder .+#endif+ encodeUtf16++------------------------------------------------------------------------------- -- Decoding string literals ------------------------------------------------------------------------------- +-- XXX decodeCString#+ -- | Read UTF-8 encoded bytes as chars from an 'Addr#' until a 0 byte is -- encountered, the 0 byte is not included in the stream. --@@ -962,7 +1250,7 @@ -- {-# INLINE fromStr# #-} fromStr# :: MonadIO m => Addr# -> Stream m Char-fromStr# addr = decodeUtf8 $ Stream.fromByteStr# addr+fromStr# addr = decodeUtf8 $ Stream.fromCString# addr ------------------------------------------------------------------------------- -- Encode streams of containers@@ -978,7 +1266,7 @@ -> Unfold m a Char -> a -> m (Array Word8)-encodeObject encode u = Stream.fold Array.write . encode . Stream.unfold u+encodeObject encode u = Stream.fold Array.create . encode . Stream.unfold u -- | Encode a stream of container objects using the supplied encoding scheme. -- Each object is encoded as an @Array Word8@.@@ -1016,7 +1304,7 @@ -- | Remove leading whitespace from a string. ----- > stripHead = Stream.dropWhile isSpace+-- >>> stripHead = Stream.dropWhile Char.isSpace -- -- /Pre-release/ {-# INLINE stripHead #-}@@ -1026,11 +1314,15 @@ -- | Fold each line of the stream using the supplied 'Fold' -- and stream the result. ----- >>> Stream.fold Fold.toList $ lines Fold.toList (Stream.fromList "lines\nthis\nstring\n\n\n")--- ["lines","this","string","",""]+-- Definition: ----- > lines = Stream.splitOnSuffix (== '\n')+-- >>> lines f = Stream.foldMany (Fold.takeEndBy_ (== '\n') f) --+-- Usage:+--+-- >>> Stream.toList $ Unicode.lines Fold.toList (Stream.fromList "line1\nline2\nline3\n\n\n")+-- ["line1","line2","line3","",""]+-- -- /Pre-release/ {-# INLINE lines #-} lines :: Monad m => Fold m Char b -> Stream m Char -> Stream m b@@ -1054,14 +1346,17 @@ where uc = fromIntegral (ord c) :: Word --- | Fold each word of the stream using the supplied 'Fold'--- and stream the result.+-- | Fold each word of the stream using the supplied 'Fold'. ----- >>> Stream.fold Fold.toList $ words Fold.toList (Stream.fromList "fold these words")--- ["fold","these","words"]+-- Definition: ----- > words = Stream.wordsBy isSpace+-- >>> words = Stream.wordsBy Char.isSpace --+-- Usage:+--+-- >>> Stream.toList $ Unicode.words Fold.toList (Stream.fromList " ab cd ef ")+-- ["ab","cd","ef"]+-- -- /Pre-release/ {-# INLINE words #-} words :: Monad m => Fold m Char b -> Stream m Char -> Stream m b@@ -1070,26 +1365,24 @@ -- | Unfold a stream to character streams using the supplied 'Unfold' -- and concat the results suffixing a newline character @\\n@ to each stream. ----- @--- unlines = Stream.interposeSuffix '\n'--- unlines = Stream.intercalateSuffix Unfold.fromList "\n"--- @+-- Definition: --+-- >>> unlines = Stream.unfoldEachEndBy '\n'+-- >>> unlines = Stream.unfoldEachEndBySeq "\n" Unfold.fromList+-- -- /Pre-release/ {-# INLINE unlines #-} unlines :: MonadIO m => Unfold m a Char -> Stream m a -> Stream m Char-unlines = Stream.interposeSuffix '\n'+unlines = Stream.unfoldEachEndBy '\n' -- | Unfold the elements of a stream to character streams using the supplied -- 'Unfold' and concat the results with a whitespace character infixed between -- the streams. ----- @--- unwords = Stream.interpose ' '--- unwords = Stream.intercalate Unfold.fromList " "--- @+-- >>> unwords = Stream.unfoldEachSepBy ' '+-- >>> unwords = Stream.unfoldEachSepBySeq " " Unfold.fromList -- -- /Pre-release/ {-# INLINE unwords #-} unwords :: MonadIO m => Unfold m a Char -> Stream m a -> Stream m Char-unwords = Stream.interpose ' '+unwords = Stream.unfoldEachSepBy ' '
src/Streamly/Internal/Unicode/String.hs view
@@ -1,4 +1,5 @@-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TemplateHaskellQuotes #-}+{-# LANGUAGE CPP #-} -- | -- Module : Streamly.Internal.Unicode.String -- Copyright : (c) 2022 Composewell Technologies@@ -31,14 +32,21 @@ -- using Haskell functions. module Streamly.Internal.Unicode.String- ( str+ (+ -- * Setup+ -- | To execute the code examples provided in this module in ghci, please+ -- run the following commands first.+ --+ -- $setup++ str ) where import Control.Applicative (Alternative(..)) import Control.Exception (displayException) import Data.Functor.Identity (runIdentity)-import Streamly.Internal.Data.Parser (Parser)+import Streamly.Internal.Data.Parser (Parser, ParseError) import Language.Haskell.TH import Language.Haskell.TH.Quote@@ -49,11 +57,8 @@ import qualified Streamly.Data.Stream as Stream (fromList, parse) import qualified Streamly.Internal.Unicode.Parser as Parser --- $setup--- >>> :m--- >>> :set -XQuasiQuotes--- >>> import Streamly.Internal.Unicode.String---+#include "DocTestUnicodeString.hs"+ -------------------------------------------------------------------------------- -- Parsing --------------------------------------------------------------------------------@@ -101,9 +106,12 @@ strExp :: [StrSegment] -> Q Exp strExp xs = appE [| concat |] $ listE $ map strSegmentExp xs +parseStr :: String -> Either ParseError [StrSegment]+parseStr = runIdentity . Stream.parse strParser . Stream.fromList+ expandVars :: String -> Q Exp-expandVars ln =- case runIdentity $ Stream.parse strParser (Stream.fromList ln) of+expandVars input =+ case parseStr input of Left e -> fail $ "str QuasiQuoter parse error: " ++ displayException e Right x ->@@ -135,9 +143,6 @@ -- world!|] -- :} -- "hello world!"------ Bugs: because of a bug in parsers, a lone # at the end of input gets--- removed. -- str :: QuasiQuoter str =
src/Streamly/Unicode/Parser.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} -- | -- Module : Streamly.Unicode.Parser -- Copyright : (c) 2021 Composewell Technologies@@ -12,9 +13,15 @@ module Streamly.Unicode.Parser (- -- * Single Chars+ -- * Setup+ -- | To execute the code examples provided in this module in ghci, please+ -- run the following commands first.+ --+ -- $setup - -- Any char+ -- * Single Chars++ -- Any char char , charIgnoreCase @@ -50,6 +57,7 @@ -- * Digit Sequences (Numbers) , decimal , hexadecimal+ , double -- * Modifiers , signed@@ -57,3 +65,5 @@ where import Streamly.Internal.Unicode.Parser++#include "DocTestUnicodeParser.hs"
src/Streamly/Unicode/Stream.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} -- | -- Module : Streamly.Unicode.Stream -- Copyright : (c) 2020 Composewell Technologies@@ -24,14 +25,11 @@ -- routines in this module and then written to IO devices or to arrays in -- memory. ----- If you have to store a 'Char' stream in memory you can convert it into a--- 'String' using 'Streamly.Data.Fold.toList' fold. The 'String' type can be--- more efficient than pinned arrays for short and short lived strings.------ For longer or long lived streams you can 'Streamly.Data.Stream.fold' the+-- If you have to store a 'Char' stream in memory you can+-- 'Streamly.Data.Stream.fold' the -- 'Char' stream as @Array Char@ using the array 'Streamly.Data.Array.write'--- fold. The 'Array' type provides a more compact representation and pinned--- memory reducing GC overhead. If space efficiency is a concern you can use+-- fold. The 'Array' type provides a more compact representation+-- reducing GC overhead. If space efficiency is a concern you can use -- 'encodeUtf8'' on the 'Char' stream before writing it to an 'Array' providing -- an even more compact representation. --@@ -69,16 +67,21 @@ -- Some experimental APIs to conveniently process text using the -- @Array Char@ represenation directly can be found in -- "Streamly.Internal.Unicode.Array".---- XXX an unpinned array representation can be useful to store short and short--- lived strings in memory. -- module Streamly.Unicode.Stream (+ -- * Setup+ -- | To execute the code examples provided in this module in ghci, please+ -- run the following commands first.+ --+ -- $setup+ -- * Construction (Decoding) decodeLatin1 , decodeUtf8 , decodeUtf8'+ , decodeUtf16le+ , decodeUtf16le' , decodeUtf8Chunks -- * Elimination (Encoding)@@ -86,6 +89,8 @@ , encodeLatin1' , encodeUtf8 , encodeUtf8'+ , encodeUtf16le+ , encodeUtf16le' , encodeStrings {- -- * Operations on character strings@@ -105,3 +110,5 @@ import Streamly.Internal.Unicode.Stream import Prelude hiding (lines, words, unlines, unwords)++#include "DocTestUnicodeStream.hs"
src/Streamly/Unicode/String.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} -- | -- Module : Streamly.Unicode.String -- Copyright : (c) 2022 Composewell Technologies@@ -7,10 +8,23 @@ -- Portability : GHC -- -- Convenient template Haskell quasiquoters to format strings.+--+-- The 'str' quasiquoter retains newlines in the string when the line is split+-- across multiple lines. The @unwords . lines@ idiom can be used on the+-- resulting string to collapse it into a single line. module Streamly.Unicode.String- ( str+ (+ -- * Setup+ -- | To execute the code examples provided in this module in ghci, please+ -- run the following commands first.+ --+ -- $setup++ str ) where import Streamly.Internal.Unicode.String++#include "DocTestUnicodeString.hs"
src/config.h.in view
@@ -1,6 +1,6 @@ /* src/config.h.in. Generated from configure.ac by autoheader. */ -/* Define to 1 if you have the `clock_gettime' function. */+/* Define to 1 if you have the 'clock_gettime' function. */ #undef HAVE_CLOCK_GETTIME /* Define to 1 if you have the <inttypes.h> header file. */@@ -51,7 +51,7 @@ /* Define to the version of this package. */ #undef PACKAGE_VERSION -/* Define to 1 if all of the C90 standard headers exist (not just the ones+/* Define to 1 if all of the C89 standard headers exist (not just the ones required in a freestanding environment). This macro is provided for backward compatibility; new code need not use it. */ #undef STDC_HEADERS
+ src/deprecation.h view
@@ -0,0 +1,9 @@+#define RENAME(_old, _new) \+{-# DEPRECATED _old "Please use _new instead." #-}; \+{-# INLINE _old #-}; \+_old = _new++#define RENAME_PRIME(_old, _new) \+{-# DEPRECATED _old "Please use _new' instead." #-}; \+{-# INLINE _old #-}; \+_old = _new'
+ src/doctest/DocTestControlException.hs view
@@ -0,0 +1,13 @@+{- $setup++>>> :m+>>> import Control.Monad (when)+>>> import Control.Concurrent (threadDelay)+>>> import Data.Function ((&))+>>> import System.IO (hClose, IOMode(..), openFile)++>>> import Streamly.Data.Stream (Stream)+>>> import qualified Streamly.Data.Fold as Fold+>>> import qualified Streamly.Data.Stream as Stream+>>> import qualified Streamly.Control.Exception as Exception+-}
+ src/doctest/DocTestDataArray.hs view
@@ -0,0 +1,22 @@+{- $setup+>>> :m+>>> :set -XFlexibleContexts+>>> :set -XMagicHash+>>> import Data.Function ((&))+>>> import Data.Functor.Identity (Identity(..))+>>> import System.IO.Unsafe (unsafePerformIO)++>>> import Streamly.Data.Array (Array)+>>> import Streamly.Data.Stream (Stream)++>>> import qualified Streamly.Data.Array as Array+>>> import qualified Streamly.Data.Fold as Fold+>>> import qualified Streamly.Data.ParserK as ParserK+>>> import qualified Streamly.Data.Stream as Stream+>>> import qualified Streamly.Data.StreamK as StreamK++For APIs that have not been released yet.++>>> import qualified Streamly.Internal.Data.Array as Array+>>> import qualified Streamly.Internal.Data.Stream as Stream+-}
+ src/doctest/DocTestDataFold.hs view
@@ -0,0 +1,33 @@+{- $setup+>>> :m+>>> :set -XFlexibleContexts+>>> import Control.Monad (void)+>>> import qualified Data.Foldable as Foldable+>>> import Data.Bifunctor(bimap)+>>> import Data.Function ((&))+>>> import Data.Functor.Identity (Identity, runIdentity)+>>> import Data.IORef (newIORef, readIORef, writeIORef)+>>> import Data.Maybe (fromJust, isJust)+>>> import Data.Monoid (Endo(..), Last(..), Sum(..))++>>> import Streamly.Data.Array (Array)+>>> import Streamly.Data.Fold (Fold, Tee(..))+>>> import Streamly.Data.Stream (Stream)++>>> import qualified Data.Map as Map+>>> import qualified Data.Set as Set+>>> import qualified Data.IntSet as IntSet+>>> import qualified Streamly.Data.Array as Array+>>> import qualified Streamly.Data.Fold as Fold+>>> import qualified Streamly.Data.MutArray as MutArray+>>> import qualified Streamly.Data.Parser as Parser+>>> import qualified Streamly.Data.Stream as Stream+>>> import qualified Streamly.Data.StreamK as StreamK+>>> import qualified Streamly.Data.Unfold as Unfold++For APIs that have not been released yet.++>>> import qualified Streamly.Internal.Data.Fold as Fold+>>> import qualified Streamly.Internal.Data.Scanl as Scanl+>>> import qualified Streamly.Internal.Data.Stream as Stream+-}
+ src/doctest/DocTestDataMutArray.hs view
@@ -0,0 +1,11 @@+{- $setup+>>> :m+>>> import qualified Streamly.Data.Fold as Fold+>>> import qualified Streamly.Data.MutArray as MutArray+>>> import qualified Streamly.Data.Stream as Stream++For APIs that have not been released yet.++>>> import qualified Streamly.Internal.Data.Fold as Fold+>>> import qualified Streamly.Internal.Data.MutArray as MutArray+-}
+ src/doctest/DocTestDataMutArrayGeneric.hs view
@@ -0,0 +1,10 @@+{- $setup+>>> :m+>>> import qualified Streamly.Data.Fold as Fold+>>> import qualified Streamly.Data.MutArray.Generic as MutArray+>>> import qualified Streamly.Data.Stream as Stream++For APIs that have not been released yet.++>>> import Streamly.Internal.Data.MutArray.Generic as MutArray+-}
+ src/doctest/DocTestDataParser.hs view
@@ -0,0 +1,20 @@+{- $setup+>>> :m+>>> import Control.Applicative ((<|>))+>>> import Data.Bifunctor (second)+>>> import Data.Char (isSpace)+>>> import qualified Data.Foldable as Foldable+>>> import qualified Data.Maybe as Maybe++>>> import Streamly.Data.Fold (Fold)+>>> import Streamly.Data.Parser (Parser)++>>> import qualified Streamly.Data.Fold as Fold+>>> import qualified Streamly.Data.Parser as Parser+>>> import qualified Streamly.Data.Stream as Stream++For APIs that have not been released yet.++>>> import qualified Streamly.Internal.Data.Fold as Fold+>>> import qualified Streamly.Internal.Data.Parser as Parser+-}
+ src/doctest/DocTestDataParserK.hs view
@@ -0,0 +1,18 @@+{- $setup+>>> :m+>>> import Control.Applicative ((<|>))+>>> import Data.Char (isDigit, isAlpha)++>>> import Streamly.Data.Parser (Parser)+>>> import Streamly.Data.ParserK (ParserK)++>>> import qualified Streamly.Data.Parser as Parser+>>> import qualified Streamly.Data.ParserK as ParserK+>>> import qualified Streamly.Data.Stream as Stream+>>> import qualified Streamly.Data.StreamK as StreamK+>>> import qualified Streamly.Unicode.Parser as Parser++For APIs that have not been released yet.++>>> import qualified Streamly.Internal.Data.ParserK as ParserK+-}
+ src/doctest/DocTestDataScanl.hs view
@@ -0,0 +1,36 @@+{- $setup+>>> :m+>>> :set -XFlexibleContexts+>>> import Control.Monad (void)+>>> import qualified Data.Foldable as Foldable+>>> import Data.Bifunctor(bimap)+>>> import Data.Function ((&))+>>> import Data.Functor.Identity (Identity, runIdentity)+>>> import Data.IORef (newIORef, readIORef, writeIORef)+>>> import Data.Maybe (fromJust, isJust)+>>> import Data.Monoid (Endo(..), Last(..), Sum(..))+>>> import Prelude hiding (length, sum, minimum, maximum)++>>> import Streamly.Data.Array (Array)+>>> import Streamly.Data.Fold (Fold, Tee(..))+>>> import Streamly.Data.Stream (Stream)++>>> import qualified Data.Map as Map+>>> import qualified Data.Set as Set+>>> import qualified Data.IntSet as IntSet+>>> import qualified Streamly.Data.Array as Array+>>> import qualified Streamly.Data.Fold as Fold+>>> import qualified Streamly.Data.MutArray as MutArray+>>> import qualified Streamly.Data.Parser as Parser+>>> import qualified Streamly.Data.Scanl as Scanl+>>> import qualified Streamly.Data.Stream as Stream+>>> import qualified Streamly.Data.StreamK as StreamK+>>> import qualified Streamly.Data.Unfold as Unfold++For APIs that have not been released yet.++>>> import qualified Streamly.Internal.Data.Fold as Fold+>>> import qualified Streamly.Internal.Data.RingArray as RingArray+>>> import qualified Streamly.Internal.Data.Scanl as Scanl+>>> import qualified Streamly.Internal.Data.Stream as Stream+-}
+ src/doctest/DocTestDataStream.hs view
@@ -0,0 +1,45 @@+{- $setup++>>> :m+>>> import Control.Concurrent (threadDelay)+>>> import Control.Monad (void, when)+>>> import Control.Monad.IO.Class (MonadIO (liftIO))+>>> import Control.Monad.Trans.Class (lift)+>>> import Control.Monad.Trans.Identity (runIdentityT)+>>> import Data.Char (isSpace)+>>> import Data.Either (fromLeft, fromRight, isLeft, isRight, either)+>>> import Data.Maybe (fromJust, isJust)+>>> import Data.Function (fix, (&))+>>> import Data.Functor.Identity (runIdentity)+>>> import Data.IORef+>>> import Data.Semigroup (cycle1)+>>> import Data.Word (Word8, Word16)+>>> import GHC.Exts (Ptr (Ptr))+>>> import System.IO (stdout, hClose, hSetBuffering, openFile, BufferMode(LineBuffering), IOMode(..))++>>> hSetBuffering stdout LineBuffering+>>> effect n = print n >> return n++>>> import Streamly.Data.Stream (Stream)+>>> import qualified Streamly.Data.Array as Array+>>> import qualified Streamly.Data.Fold as Fold+>>> import qualified Streamly.Data.Scanl as Scanl+>>> import qualified Streamly.Data.Stream as Stream+>>> import qualified Streamly.Data.StreamK as StreamK+>>> import qualified Streamly.Data.Unfold as Unfold+>>> import qualified Streamly.Data.Parser as Parser+>>> import qualified Streamly.FileSystem.DirIO as Dir++For APIs that have not been released yet.++>>> import qualified Streamly.Internal.Control.Exception as Exception+>>> import qualified Streamly.Internal.FileSystem.Path as Path+>>> import qualified Streamly.Internal.Data.Scanr as Scanr+>>> import qualified Streamly.Internal.Data.Scanl as Scanl+>>> import qualified Streamly.Internal.Data.Fold as Fold+>>> import qualified Streamly.Internal.Data.Parser as Parser+>>> import qualified Streamly.Internal.Data.Stream as Stream+>>> import qualified Streamly.Internal.Data.StreamK as StreamK+>>> import qualified Streamly.Internal.Data.Unfold as Unfold+>>> import qualified Streamly.Internal.FileSystem.DirIO as Dir+-}
+ src/doctest/DocTestDataStreamK.hs view
@@ -0,0 +1,24 @@+{- $setup++>>> :m+>>> import Control.Concurrent (threadDelay)+>>> import Data.Function (fix, (&))+>>> import Data.Semigroup (cycle1)++>>> import Streamly.Data.StreamK (StreamK)+>>> import qualified Streamly.Data.Fold as Fold+>>> import qualified Streamly.Data.Parser as Parser+>>> import qualified Streamly.Data.Stream as Stream+>>> import qualified Streamly.Data.StreamK as StreamK+>>> import qualified Streamly.FileSystem.DirIO as Dir++>>> mk = StreamK.fromStream . Stream.fromList+>>> un = Stream.toList . StreamK.toStream+>>> effect n = print n >> return n++For APIs that have not been released yet.++>>> import qualified Streamly.Internal.FileSystem.Path as Path+>>> import qualified Streamly.Internal.Data.StreamK as StreamK+>>> import qualified Streamly.Internal.FileSystem.DirIO as Dir+-}
+ src/doctest/DocTestDataUnfold.hs view
@@ -0,0 +1,13 @@+{- $setup++>>> :m+>>> import Streamly.Data.Unfold (Unfold)+>>> import qualified Streamly.Data.Fold as Fold+>>> import qualified Streamly.Data.Scanl as Scanl+>>> import qualified Streamly.Data.Stream as Stream+>>> import qualified Streamly.Data.Unfold as Unfold++For APIs that have not been released yet.++>>> import qualified Streamly.Internal.Data.Unfold as Unfold+-}
+ src/doctest/DocTestFileSystemHandle.hs view
@@ -0,0 +1,15 @@+{- $setup+>>> :m+>>> import qualified Streamly.Data.Array as Array+>>> import qualified Streamly.FileSystem.Handle as Handle hiding (readChunks)+>>> import qualified Streamly.Data.Fold as Fold+>>> import qualified Streamly.Data.Stream as Stream+>>> import qualified Streamly.Data.Unfold as Unfold++For APIs that have not been released yet.++>>> import qualified Streamly.Internal.Data.Array as Array (unsafeCreateOf)+>>> import qualified Streamly.Internal.Data.Unfold as Unfold (first)+>>> import qualified Streamly.Internal.FileSystem.Handle as Handle+>>> import qualified Streamly.Internal.System.IO as IO (defaultChunkSize)+-}
+ src/doctest/DocTestFileSystemPath.hs view
@@ -0,0 +1,20 @@+{- $setup+>>> :m+>>> :set -XQuasiQuotes+>>> import Control.Exception (SomeException, evaluate, try)+>>> import Data.Either (Either, isLeft)+>>> import Data.Maybe (fromJust, isJust, isNothing)+>>> import Streamly.FileSystem.Path (Path, path)+>>> import qualified Streamly.Data.Array as Array+>>> import qualified Streamly.Data.Stream as Stream+>>> import qualified Streamly.FileSystem.Path as Path+>>> import qualified Streamly.Unicode.Stream as Unicode++For APIs that have not been released yet.++>>> import qualified Streamly.Internal.FileSystem.Path as Path++Utilities:++>>> fails x = isLeft <$> (try (evaluate x) :: IO (Either SomeException String))+-}
+ src/doctest/DocTestFileSystemPosixPath.hs view
@@ -0,0 +1,19 @@+{- $setup+>>> :m+>>> :set -XQuasiQuotes+>>> import Control.Exception (SomeException, evaluate, try)+>>> import Data.Either (Either, isLeft)+>>> import Data.Maybe (isNothing, isJust)+>>> import qualified Streamly.Data.Array as Array+>>> import qualified Streamly.Data.Stream as Stream+>>> import qualified Streamly.Unicode.Stream as Unicode++For APIs that have not been released yet.++>>> import Streamly.Internal.FileSystem.PosixPath (PosixPath, path)+>>> import qualified Streamly.Internal.FileSystem.PosixPath as Path++Utilities:++>>> fails x = isLeft <$> (try (evaluate x) :: IO (Either SomeException String))+-}
+ src/doctest/DocTestFileSystemWindowsPath.hs view
@@ -0,0 +1,21 @@+{- $setup+>>> :m+>>> :set -XQuasiQuotes+>>> import Control.Exception (SomeException, evaluate, try)+>>> import Data.Either (Either, isLeft)+>>> import Data.Maybe (fromJust, isNothing, isJust)+>>> import Data.Word (Word16)+>>> import Streamly.Data.Array (Array)+>>> import qualified Streamly.Data.Array as Array+>>> import qualified Streamly.Data.Stream as Stream+>>> import qualified Streamly.Unicode.Stream as Unicode++For APIs that have not been released yet.++>>> import Streamly.Internal.FileSystem.WindowsPath (WindowsPath, path)+>>> import qualified Streamly.Internal.FileSystem.WindowsPath as Path++Utilities:++>>> fails x = isLeft <$> (try (evaluate x) :: IO (Either SomeException String))+-}
+ src/doctest/DocTestUnicodeParser.hs view
@@ -0,0 +1,10 @@+{- $setup+>>> :m+>>> import qualified Streamly.Data.Stream as Stream+>>> import qualified Streamly.Unicode.Parser as Unicode++For APIs that have not been released yet.++>>> import qualified Streamly.Internal.Data.Stream as Stream (parsePos)+>>> import qualified Streamly.Internal.Unicode.Parser as Unicode (number, mkDouble)+-}
+ src/doctest/DocTestUnicodeStream.hs view
@@ -0,0 +1,12 @@+{- $setup+>>> :m++>>> import qualified Streamly.Data.Fold as Fold+>>> import qualified Streamly.Data.Stream as Stream+>>> import qualified Streamly.Unicode.Stream as Unicode++For APIs that have not been released yet.++>>> :set -XMagicHash+>>> import qualified Streamly.Internal.Unicode.Stream as Unicode+-}
+ src/doctest/DocTestUnicodeString.hs view
@@ -0,0 +1,5 @@+{- $setup+>>> :m+>>> :set -XQuasiQuotes+>>> import Streamly.Internal.Unicode.String+-}
streamly-core.cabal view
@@ -1,23 +1,52 @@-cabal-version: 2.2+cabal-version: 2.4 name: streamly-core-version: 0.1.0-synopsis: Streaming, parsers, arrays and more+version: 0.3.1+synopsis: Streaming, parsers, arrays, serialization and more description:- Streamly consists of two packages: "streamly-core" and "streamly".- <https://hackage.haskell.org/package/streamly-core streamly-core>- provides basic features, and depends only on GHC boot libraries (see- note below), while- <https://hackage.haskell.org/package/streamly streamly> provides- higher-level features like concurrency, time, lifted exceptions,- and networking. For documentation, visit the- <https://streamly.composewell.com Streamly website>.+ For upgrading to streamly-0.9.0+ please read the+ <https://github.com/composewell/streamly/blob/streamly-0.10.0/docs/User/Project/Upgrading-0.8-to-0.9.md Streamly-0.9.0 upgrade guide>. .- This package provides streams, arrays, parsers, unicode text, file- IO, and console IO functionality.+ Streamly is a high-performance, beginner-friendly standard library+ for Haskell. It unifies streaming with list transformers and logic+ programming; unifies streaming with concurrency and reactive+ programming; unifies arrays with ring arrays, text, bytestring+ and vector use cases; unifies arrays with builders and binary+ serialization; generalizes parsers to any input type and unifies+ attoparsec, parsec use cases with better performance; provides+ streaming fileIO — all with a clean, consistent, well-integrated and+ streaming enabled API. .- Note: The dependencies "heaps" and "monad-control" are included in- the package solely for backward compatibility, and will be removed in- future versions.+ Streams are designed to have a list like interface — no steep+ learning curve, no complex types. Streamly is designed to build+ general purpose applications in a truly functional manner, from+ simple hello-world to advanced high-performance systems. The design+ emphasizes simplicity, modularity, and code reuse with minimal+ building blocks. Performance is on par with C, tuning is easy, and+ it’s hard to get it wrong.+ .+ Streamly is serial by default, with seamless declarative concurrency+ that scales automatically when needed. It provides prompt and safe+ resource management, works well with other streaming libraries as well+ as core libraries like bytestring and text, and is backed by solid+ documentation.+ .+ @streamly-core@ is a Haskell standard library built on top of @base@+ and GHC boot libraries only. Stream processing abstractions include+ streams, scans, folds, parsers; and console I/O, file I/O; text+ processing. Array abstractions include pinned, unpinned, mutable,+ immutable, boxed and unboxed arrays, and ring arrays. Builders,+ binary serialization, and deserialization are built-in features of+ arrays.+ .+ This package provides a high-performance, unified and ergonomic+ alternative to many disparate packages, such as @streaming, pipes,+ conduit, list-t, logict, foldl, attoparsec, array, primitive,+ vector, vector-algorithms, binary, cereal, store, bytestring, text,+ stringsearch, interpolate, filepath, and path@.+ .+ Performant. Unified. Modular. Powerful. Simple.+ .+ Learn more at <https://streamly.composewell.com the streamly website>. homepage: https://streamly.composewell.com bug-reports: https://github.com/composewell/streamly/issues@@ -27,8 +56,12 @@ , GHC==8.8.4 , GHC==8.10.7 , GHC==9.0.2- , GHC==9.2.7- , GHC==9.4.4+ , GHC==9.2.8+ , GHC==9.4.7+ , GHC==9.6.3+ , GHC==9.8.1+ , GHC==9.10.1+ , GHC==9.12.1 author: Composewell Technologies maintainer: streamly@composewell.com copyright: 2017 Composewell Technologies@@ -43,19 +76,14 @@ configure.ac -- doctest include files- src/DocTestDataArray.hs- src/DocTestDataFold.hs- src/DocTestDataMutArray.hs- src/DocTestDataMutArrayGeneric.hs- src/DocTestDataParser.hs- src/DocTestDataStream.hs- src/DocTestDataStreamK.hs- src/DocTestDataUnfold.hs+ src/doctest/*.hs -- This is duplicated src/Streamly/Internal/Data/Array/ArrayMacros.h+ src/Streamly/Internal/Data/ParserDrivers.h src/assert.hs src/inline.hs+ src/deprecation.h src/Streamly/Internal/Data/Time/Clock/config-clock.h src/config.h.in@@ -69,7 +97,7 @@ extra-doc-files: Changelog.md docs/*.md- docs/ApiChangelogs/0.1.0.txt+ docs/ApiChangelogs/*.txt source-repository head type: git@@ -80,8 +108,8 @@ manual: True default: False -flag dev- description: Development build+flag internal-dev+ description: DO NOT USE, ONLY FOR INTERNAL USE. manual: True default: False @@ -90,16 +118,6 @@ manual: True default: False -flag no-fusion- description: Disable rewrite rules for stream fusion- manual: True- default: False--flag use-c-malloc- description: Use C malloc instead of GHC malloc- manual: True- default: False- flag opt description: off=GHC default, on=-O2 manual: True@@ -110,8 +128,8 @@ manual: True default: False -flag use-unliftio- description: Use unliftio-core instead of monad-control+flag internal-use-unliftio+ description: DO NOT USE, ONLY FOR INTERNAL USE. manual: True default: False @@ -125,17 +143,20 @@ manual: True default: False +flag force-lstat-readdir+ description: Use lstat instead of checking for dtype in ReadDir+ manual: True+ default: False+ ------------------------------------------------------------------------------- -- Common stanzas ------------------------------------------------------------------------------- common compile-options- default-language: Haskell2010-- if flag(no-fusion)- cpp-options: -DDISABLE_FUSION+ if flag(force-lstat-readdir)+ cpp-options: -DFORCE_LSTAT_READDIR - if flag(dev)+ if flag(internal-dev) cpp-options: -DDEVBUILD if flag(use-unfolds)@@ -144,9 +165,6 @@ if flag(use-folds) cpp-options: -DUSE_FOLDS_EVERYWHERE - if flag(use-c-malloc)- cpp-options: -DUSE_C_MALLOC- ghc-options: -Weverything -Wno-implicit-prelude -Wno-missing-deriving-strategies@@ -167,24 +185,48 @@ -Wno-redundant-bang-patterns -Wno-operator-whitespace + if impl(ghc >= 9.8)+ ghc-options:+ -Wno-missing-role-annotations++ if impl(ghc >= 9.10)+ ghc-options:+ -Wno-missing-poly-kind-signatures+ if flag(has-llvm) ghc-options: -fllvm - if flag(dev)+ if flag(internal-dev) ghc-options: -Wmissed-specialisations -Wall-missed-specialisations if flag(limit-build-mem) ghc-options: +RTS -M1000M -RTS - if flag(use-unliftio)+ if flag(internal-use-unliftio) cpp-options: -DUSE_UNLIFTIO common default-extensions+ default-language: Haskell2010++ -- GHC2024 may include more extensions than we are actually using, see the+ -- full list below. We enable this to ensure that we are able to compile+ -- with this i.e. there is no interference by other extensions.++ -- Don't enforce GHC2024 and GHC2021 but We can support the build with them.++ -- if impl(ghc >= 9.10)+ -- default-language: GHC2024++ -- if impl(ghc >= 9.2) && impl(ghc < 9.10)+ -- default-language: GHC2021++ if impl(ghc >= 8.10)+ default-extensions: StandaloneKindSignatures++ -- In GHC 2024 default-extensions: BangPatterns- CApiFFI- CPP ConstraintKinds DeriveDataTypeable DeriveGeneric@@ -196,27 +238,42 @@ InstanceSigs KindSignatures LambdaCase- MagicHash MultiParamTypeClasses- PatternSynonyms RankNTypes- RecordWildCards ScopedTypeVariables StandaloneDeriving TupleSections TypeApplications- TypeFamilies TypeOperators- ViewPatterns - -- MonoLocalBinds, enabled by TypeFamilies, causes performance- -- regressions. Disable it. This must come after TypeFamilies,+ -- Not in GHC2024+ CApiFFI+ CPP+ DefaultSignatures+ MagicHash+ RecordWildCards++ -- TypeFamilies is required by IsList, IsMap type classes and+ -- Unbox generic deriving code.+ -- TypeFamilies++ -- MonoLocalBinds, enabled by TypeFamilies and GHC2024, was+ -- once found to cause runtime performance regressions which+ -- does not seem to be the case anymore, but need more testing+ -- to confirm. It is confirmed that it requires more memory+ -- for compilation at least in some cases (Data.Fold.Window+ -- benchmark on GHC-9.10.1 macOS). It also causes some+ -- code to not compile, so has been disabled in specific+ -- modules. Disabling this must come after TypeFamilies, -- otherwise TypeFamilies will enable it again.- NoMonoLocalBinds+ -- NoMonoLocalBinds -- UndecidableInstances -- Does not show any perf impact -- UnboxedTuples -- interferes with (#.) + if impl(ghc >= 8.6)+ default-extensions: QuantifiedConstraints+ common optimization-options if flag(opt) ghc-options: -O2@@ -225,7 +282,8 @@ -fmax-worker-args=16 -- For this to be effective it must come after the -O2 option- if flag(dev) || flag(debug) || !flag(opt)+ if flag(internal-dev) || flag(debug) || !flag(opt)+ cpp-options: -DDEBUG ghc-options: -fno-ignore-asserts common threading-options@@ -248,16 +306,19 @@ library import: lib-options - if impl(ghc >= 8.6)- default-extensions: QuantifiedConstraints- js-sources: jsbits/clock.js include-dirs: src+ , src/doctest+ , src/Streamly/Internal/Data , src/Streamly/Internal/Data/Array , src/Streamly/Internal/Data/Stream + c-sources: src/Streamly/Internal/Data/MutArray/Lib.c++ -- Prefer OS conditionals inside the source files rather than here,+ -- conditionals here do not work well with cabal2nix. if os(windows) c-sources: src/Streamly/Internal/Data/Time/Clock/Windows.c @@ -289,81 +350,49 @@ -- streamly-time , Streamly.Internal.Data.Time.TimeSpec , Streamly.Internal.Data.Time.Units- , Streamly.Internal.Data.Time.Clock.Type , Streamly.Internal.Data.Time.Clock+ , Streamly.Internal.Data.Path -- streamly-core-stream-types , Streamly.Internal.Data.SVar.Type- , Streamly.Internal.Data.Stream.StreamK.Type- , Streamly.Internal.Data.Fold.Step , Streamly.Internal.Data.Refold.Type- , Streamly.Internal.Data.Fold.Type- , Streamly.Internal.Data.Stream.StreamD.Step- , Streamly.Internal.Data.Stream.StreamD.Type- , Streamly.Internal.Data.Unfold.Type- , Streamly.Internal.Data.Producer.Type , Streamly.Internal.Data.Producer- , Streamly.Internal.Data.Producer.Source- , Streamly.Internal.Data.Parser.ParserK.Type- , Streamly.Internal.Data.Parser.ParserD.Type- , Streamly.Internal.Data.Pipe.Type -- streamly-core-array-types- , Streamly.Internal.Data.Unboxed- -- Unboxed IORef- , Streamly.Internal.Data.IORef.Unboxed+ , Streamly.Internal.Data.MutByteArray+ , Streamly.Internal.Data.CString++ -- streaming and parsing Haskell types to/from bytes+ , Streamly.Internal.Data.Binary.Parser+ , Streamly.Internal.Data.Binary.Stream+ -- May depend on streamly-core-stream- , Streamly.Internal.Data.Array.Mut.Type- , Streamly.Internal.Data.Array.Mut- , Streamly.Internal.Data.Array.Type- , Streamly.Internal.Data.Array.Generic.Mut.Type+ , Streamly.Internal.Data.MutArray+ , Streamly.Internal.Data.MutArray.Generic -- streamly-core-streams- , Streamly.Internal.Data.Stream.StreamK+ , Streamly.Internal.Data.StreamK -- StreamD depends on streamly-array-types- , Streamly.Internal.Data.Stream.StreamD.Generate- , Streamly.Internal.Data.Stream.StreamD.Eliminate- , Streamly.Internal.Data.Stream.StreamD.Nesting- , Streamly.Internal.Data.Stream.StreamD.Transform- , Streamly.Internal.Data.Stream.StreamD.Exception- , Streamly.Internal.Data.Stream.StreamD.Lift- , Streamly.Internal.Data.Stream.StreamD.Top- , Streamly.Internal.Data.Stream.StreamD- , Streamly.Internal.Data.Stream.Common , Streamly.Internal.Data.Stream - , Streamly.Internal.Data.Parser.ParserD.Tee- , Streamly.Internal.Data.Parser.ParserD- -- streamly-core-data , Streamly.Internal.Data.Builder , Streamly.Internal.Data.Unfold- , Streamly.Internal.Data.Unfold.Enumeration- , Streamly.Internal.Data.Fold.Tee- , Streamly.Internal.Data.Fold- , Streamly.Internal.Data.Fold.Chunked- , Streamly.Internal.Data.Fold.Window , Streamly.Internal.Data.Parser+ , Streamly.Internal.Data.ParserK , Streamly.Internal.Data.Pipe-- -- streamly-transformers (non-base)- , Streamly.Internal.Data.Stream.StreamD.Transformer- , Streamly.Internal.Data.Stream.StreamK.Transformer+ , Streamly.Internal.Data.Scanr -- streamly-containers (non-base)- , Streamly.Internal.Data.Stream.StreamD.Container- , Streamly.Internal.Data.Fold.Container-- , Streamly.Internal.Data.Stream.Chunked+ , Streamly.Internal.Data.Fold+ , Streamly.Internal.Data.Scanl -- streamly-core-data-arrays , Streamly.Internal.Data.Array.Generic , Streamly.Internal.Data.Array- , Streamly.Internal.Data.Array.Mut.Stream - -- streamly-serde- , Streamly.Internal.Serialize.FromBytes- , Streamly.Internal.Serialize.ToBytes+ -- Unboxed IORef+ , Streamly.Internal.Data.IORef -- streamly-unicode-core , Streamly.Internal.Unicode.Stream@@ -372,34 +401,47 @@ , Streamly.Internal.Unicode.Array -- Filesystem/IO++ , Streamly.Internal.FileSystem.Path+ , Streamly.Internal.FileSystem.Path.Seg+ , Streamly.Internal.FileSystem.Path.Node+ , Streamly.Internal.FileSystem.Path.SegNode++ , Streamly.Internal.FileSystem.PosixPath+ , Streamly.Internal.FileSystem.PosixPath.Seg+ , Streamly.Internal.FileSystem.PosixPath.Node+ , Streamly.Internal.FileSystem.PosixPath.SegNode++ , Streamly.Internal.FileSystem.WindowsPath+ , Streamly.Internal.FileSystem.WindowsPath.Seg+ , Streamly.Internal.FileSystem.WindowsPath.Node+ , Streamly.Internal.FileSystem.WindowsPath.SegNode+ , Streamly.Internal.FileSystem.Handle- , Streamly.Internal.FileSystem.File- , Streamly.Internal.FileSystem.Dir+ , Streamly.Internal.FileSystem.File.Common+ , Streamly.Internal.FileSystem.Posix.Errno+ , Streamly.Internal.FileSystem.Posix.File+ , Streamly.Internal.FileSystem.Posix.ReadDir+ , Streamly.Internal.FileSystem.Windows.ReadDir+ , Streamly.Internal.FileSystem.Windows.File+ , Streamly.Internal.FileSystem.FileIO+ , Streamly.Internal.FileSystem.DirIO - -- Ring Arrays- , Streamly.Internal.Data.Ring.Unboxed- , Streamly.Internal.Data.Ring+ -- RingArray Arrays+ , Streamly.Internal.Data.RingArray+ , Streamly.Internal.Data.RingArray.Generic -- streamly-console , Streamly.Internal.Console.Stdio -- To be implemented -- , Streamly.Data.Refold- -- , Streamly.Data.Binary.Encode -- Stream types -- Pre-release modules- -- , Streamly.Data.Fold.Window -- , Streamly.Data.Pipe- -- , Streamly.Data.Array.Stream- -- , Streamly.Data.Array.Fold- -- , Streamly.Data.Array.Mut.Stream- -- , Streamly.Data.Ring- -- , Streamly.Data.Ring.Unboxed- -- , Streamly.Data.IORef.Unboxed+ -- , Streamly.Data.RingArray.Generic+ -- , Streamly.Data.IORef -- , Streamly.Data.List- -- , Streamly.Data.Binary.Decode- -- , Streamly.FileSystem.File- -- , Streamly.FileSystem.Dir -- , Streamly.Data.Time.Units -- , Streamly.Data.Time.Clock -- , Streamly.Data.Tuple.Strict@@ -407,45 +449,112 @@ -- , Streamly.Data.Either.Strict -- streamly-core released modules in alphabetic order- -- NOTE: these must be added to streamly.cabal as well , Streamly.Console.Stdio+ , Streamly.Control.Exception , Streamly.Data.Array , Streamly.Data.Array.Generic+ , Streamly.Data.Fold , Streamly.Data.MutArray , Streamly.Data.MutArray.Generic- , Streamly.Data.Fold+ , Streamly.Data.MutByteArray , Streamly.Data.Parser , Streamly.Data.ParserK+ , Streamly.Data.RingArray+ , Streamly.Data.Scanl , Streamly.Data.Stream , Streamly.Data.StreamK , Streamly.Data.Unfold- , Streamly.FileSystem.Dir- , Streamly.FileSystem.File+ , Streamly.FileSystem.DirIO+ , Streamly.FileSystem.FileIO , Streamly.FileSystem.Handle+ , Streamly.FileSystem.Path , Streamly.Unicode.Parser , Streamly.Unicode.Stream , Streamly.Unicode.String - if flag(dev)+ -- Deprecated in 0.3.0+ , Streamly.Internal.FileSystem.File+ , Streamly.Internal.FileSystem.Dir+ , Streamly.FileSystem.Dir+ , Streamly.FileSystem.File++ -- Deprecated in 0.2.0+ , Streamly.Internal.Data.MutArray.Stream+ , Streamly.Internal.Data.Array.Stream+ , Streamly.Internal.Data.Stream.StreamD+ , Streamly.Internal.Data.Fold.Chunked++ -- Only those modules should be here which are fully re-exported via some+ -- other module.+ other-modules:+ Streamly.FileSystem.Path.Seg+ , Streamly.FileSystem.Path.Node+ , Streamly.FileSystem.Path.SegNode++ , Streamly.Internal.Data.Fold.Step+ , Streamly.Internal.Data.Fold.Type+ , Streamly.Internal.Data.Fold.Combinators+ , Streamly.Internal.Data.Fold.Container+ , Streamly.Internal.Data.Fold.Exception+ , Streamly.Internal.Data.Fold.Tee+ , Streamly.Internal.Data.Fold.Window++ , Streamly.Internal.Data.Scanl.Type+ , Streamly.Internal.Data.Scanl.Window+ , Streamly.Internal.Data.Scanl.Combinators+ , Streamly.Internal.Data.Scanl.Container++ , Streamly.Internal.Data.Parser.Type+ , Streamly.Internal.Data.Parser.Tee+ , Streamly.Internal.Data.ParserK.Type+ , Streamly.Internal.Data.ParserDrivers++ , Streamly.Internal.Data.Stream.Container+ , Streamly.Internal.Data.Stream.Eliminate+ , Streamly.Internal.Data.Stream.Exception+ , Streamly.Internal.Data.Stream.Generate+ , Streamly.Internal.Data.Stream.Lift+ , Streamly.Internal.Data.Stream.Nesting+ , Streamly.Internal.Data.Stream.Step+ , Streamly.Internal.Data.Stream.Top+ , Streamly.Internal.Data.Stream.Transform+ , Streamly.Internal.Data.Stream.Transformer+ , Streamly.Internal.Data.Stream.Type++ , Streamly.Internal.Data.StreamK.Type+ , Streamly.Internal.Data.StreamK.Transformer++ , Streamly.Internal.Data.Pipe.Type++ , Streamly.Internal.Data.Unfold.Type+ , Streamly.Internal.Data.Unfold.Enumeration++ , Streamly.Internal.Data.MutArray.Type++ , Streamly.Internal.Data.Array.Type+ , Streamly.Internal.Data.Array.Generic.Type++ , Streamly.Internal.Data.MutByteArray.Type+ , Streamly.Internal.Data.Unbox+ , Streamly.Internal.Data.Unbox.TH+ , Streamly.Internal.Data.Serialize.Type+ , Streamly.Internal.Data.Serialize.TH+ , Streamly.Internal.Data.Serialize.TH.RecHeader+ , Streamly.Internal.Data.Serialize.TH.Common+ , Streamly.Internal.Data.Serialize.TH.Bottom++ , Streamly.Internal.Data.Producer.Type+ , Streamly.Internal.Data.Producer.Source++ , Streamly.Internal.Data.Time.Clock.Type+ , Streamly.Internal.FileSystem.Path.Common+ , Streamly.Internal.FileSystem.DirOptions++ if flag(internal-dev) exposed-modules:- Streamly.Internal.Data.Stream.StreamK.Alt- , Streamly.Internal.Data.Stream.Type- , Streamly.Internal.Data.Stream.Eliminate- , Streamly.Internal.Data.Stream.Enumerate- , Streamly.Internal.Data.Stream.Generate- , Streamly.Internal.Data.Stream.Transform- , Streamly.Internal.Data.Stream.Bottom- , Streamly.Internal.Data.Stream.Exception- , Streamly.Internal.Data.Stream.Expand- , Streamly.Internal.Data.Stream.Lift- , Streamly.Internal.Data.Stream.Reduce- , Streamly.Internal.Data.Stream.Transformer- , Streamly.Internal.Data.Stream.StreamDK- , Streamly.Internal.Data.Stream.Zip- , Streamly.Internal.Data.Stream.Cross- , Streamly.Internal.Data.List- , Streamly.Data.Stream.Zip- --, Streamly.Internal.Data.Parser.ParserDK+ Streamly.Internal.Data.StreamK.Alt+ -- XXX Compilation needs to be fixed+ -- , Streamly.Internal.Data.List build-depends: -- streamly-base@@ -459,21 +568,27 @@ -- packages depending on the "ghc" package (packages -- depending on doctest is a common example) can -- depend on streamly.- ghc-prim >= 0.5.3 && < 0.10+ ghc-prim >= 0.5.3 && < 0.14 , fusion-plugin-types >= 0.1 && < 0.2- , base >= 4.12 && < 4.19+ , base >= 4.12 && < 4.23 , exceptions >= 0.8.0 && < 0.11 , transformers >= 0.5.5 && < 0.7- , filepath >= 1.4.2 && < 1.5 -- streamly-unicode-core- , template-haskell >= 2.14 && < 2.20-- -- streamly-filesystem-core- , directory >= 1.3.3 && < 1.4+ , template-haskell >= 2.14 && < 2.25 -- XXX to be removed- , containers >= 0.6.0 && < 0.7+ , filepath >= 1.4.2 && < 1.6+ , containers >= 0.6.0 && < 0.9 , heaps >= 0.3 && < 0.5- if !flag(use-unliftio)++ if impl(ghc >= 9.0)+ build-depends: ghc-bignum >= 1.0 && < 2+ else+ build-depends: integer-gmp >= 1.0 && < 1.2++ if !flag(internal-use-unliftio) build-depends: monad-control >= 1.0 && < 1.1++ if os(windows)+ build-depends: Win32 >= 2.6 && < 2.15