streaming-nonempty (empty) → 0.1.0.0
raw patch · 7 files changed
+216/−0 lines, 7 filesdep +basedep +hspecdep +streamingsetup-changed
Dependencies added: base, hspec, streaming, streaming-nonempty
Files
- ChangeLog.md +3/−0
- LICENSE +30/−0
- README.md +27/−0
- Setup.hs +2/−0
- src/Streaming/NonEmpty.hs +74/−0
- streaming-nonempty.cabal +51/−0
- test/Spec.hs +29/−0
+ ChangeLog.md view
@@ -0,0 +1,3 @@+# Changelog for streaming-nonempty++## Unreleased changes
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Author Paolo Veronelli (c) 2021++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Author name here nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,27 @@+# streaming-nonempty++A correct _groupBy_ function is expected to produce only non-empty groups, which means we should be able to fold them up with only Semigroup constraint on the values. This is not the case for `Data.List.groupBy` as well for the Streaming lib counterpart.++This package export a _groupBy_ function that produce `f (Stream f m a)` groups which are then guaranteed to have the functorial layer.++The `NEStream (Of a) m r` newtype is supporting _sconcat_ which means we can define++```haskell++groupSemigroupBy :: (Semigroup a, Monad m) => (a -> a -> Bool) -> Stream (Of a) m r -> Stream (Of a) m r+groupSemigroupBy f = S.mapped sconcat . groupBy f++```++with expected behavior to collapse groups using semigroup composition++In contrast using the standard _groupBy_ we are stuck with ++```haskell++groupMonoidBy :: (Monoid a, Monad m) => (a -> a -> Bool) -> Stream (Of a) m r -> Stream (Of a) m r+groupMonoidBy f = S.mapped mconcat . groupBy f++```++It would be legit to use an `sconcatUnsafe` that would panic on empty streams because *we know* groups are not empty.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ src/Streaming/NonEmpty.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE NoImplicitPrelude #-}++module Streaming.NonEmpty where++import Control.Category (id, (.))+import Control.Monad (Monad (return))+import Data.Bool (Bool)+import Data.Either+import Data.Eq ((==))+import Data.Function (($))+import Data.Functor (Functor, fmap, (<$>))+import Data.List.NonEmpty hiding (groupBy)+import Data.Semigroup (Semigroup, (<>))+import qualified Data.Semigroup as Semigroup+import Streaming (Of ((:>)), Stream, first, runIdentity, wrap)+import Streaming.Internal+import qualified Streaming.Prelude as S++-- | A stream with at least one functorial layer+newtype NEStream f m a = NEStream (f (Stream f m a)) deriving (Functor)++-- | Is a stream+toStream :: (Functor f, Monad m) => NEStream f m r -> Stream f m r+toStream (NEStream f) = wrap f++-- | fold1 is safe+fold1 :: Monad m => (a -> a -> a) -> NEStream (Of a) m r -> m (Of a r)+fold1 f (NEStream (x :> s)) = S.fold f x id s++-- | sconcat as in 'Data.Semigroup', with result+sconcat :: (Semigroup a, Monad m) => NEStream (Of a) m r -> m (Of a r)+sconcat = fold1 (<>)++-- | harvest the list with the result+toNonEmpty :: Monad m => NEStream (Of a) m r -> m (Of (NonEmpty a) r)+toNonEmpty (NEStream (x :> r)) = first (x :|) <$> S.toList r++-- | harvest the list only+toNonEmpty_ :: Monad m => NEStream (Of a) m r -> m (NonEmpty a)+toNonEmpty_ = fmap S.fst' . toNonEmpty++-- | sconcat as in 'Data.Semigroup'+sconcat_ :: (Monad m, Semigroup a) => NEStream (Of a) m r -> m a+sconcat_ (NEStream (x :> s)) = S.fold_ (<>) x id s++-- | group by some equality in non empty groups+groupBy+ :: (Monad m)+ => (a -> a -> Bool)+ -> Stream (Of a) m r+ -> Stream (NEStream (Of a) m) m r+groupBy equals = loop+ where+ loop stream = Effect $ do+ e <- S.next stream+ return $ case e of+ Left r -> Return r+ Right (a, p') ->+ Step $+ fmap loop (NEStream (a :> S.span (equals a) p'))++-- | what groupBy could be+groupByPure :: (a -> a -> Bool) -> [a] -> [NonEmpty a]+groupByPure equals = runIdentity . S.toList_ . S.mapped toNonEmpty . groupBy equals . S.each++-- | collapse semigroups by some equality+groupSemigroupBy :: (Semigroup a, Monad m) => (a -> a -> Bool) -> Stream (Of a) m r -> Stream (Of a) m r+groupSemigroupBy f = S.mapped sconcat . groupBy f++-- | what should be possible to do with 'groupBy'+-- fmap sconcat . groupBy equals+groupSemigroupByPure :: Semigroup b => (b -> b -> Bool) -> [b] -> [b]+groupSemigroupByPure equals = runIdentity . S.toList_ . groupSemigroupBy equals . S.each
+ streaming-nonempty.cabal view
@@ -0,0 +1,51 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.34.4.+--+-- see: https://github.com/sol/hpack++name: streaming-nonempty+version: 0.1.0.0+synopsis: Add support for non empty streams to Streaming lib+description: Please see the README on GitHub at <https://gitlab.com/paolo.veronelli/streaming-nonempty/-/blob/master/README.md>+category: Streaming+author: Paolo Veronelli+maintainer: paolo.veronelli@gmail.com+copyright: 2021 Paolo Veronelli+license: BSD3+license-file: LICENSE+build-type: Simple+extra-source-files:+ README.md+ ChangeLog.md++source-repository head+ type: git+ location: https://gitlab.com/paolo.veronelli/streaming-nonempty++library+ exposed-modules:+ Streaming.NonEmpty+ other-modules:+ Paths_streaming_nonempty+ hs-source-dirs:+ src+ build-depends:+ base >=4.7 && <5+ , streaming+ default-language: Haskell2010++test-suite streaming-nonempty-test+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules:+ Paths_streaming_nonempty+ hs-source-dirs:+ test+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ base >=4.7 && <5+ , hspec+ , streaming+ , streaming-nonempty+ default-language: Haskell2010
+ test/Spec.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE TypeApplications #-}++import Data.Semigroup (Sum (Sum))+import Streaming.NonEmpty (groupSemigroupBy, groupSemigroupByPure)+import qualified Streaming.Prelude as S+import Test.Hspec++main :: IO ()+main = hspec $ do+ describe "groupSemigroupBy" $ do+ it "collapse no stream" $ do+ xs S.:> () <- S.toList $ groupSemigroupBy (==) $ S.each ([] @(Sum Int))+ xs `shouldBe` []+ it "collapse one elem stream" $ do+ xs S.:> () <- S.toList $ groupSemigroupBy (==) $ S.each [Sum 1]+ xs `shouldBe` [Sum 1]+ it "collapse 2 elems of different groups stream" $ do+ xs S.:> () <- S.toList $ groupSemigroupBy (==) $ S.each [Sum 1, Sum 2]+ xs `shouldBe` [Sum 1, Sum 2]+ it "collapse 2 elems of same group stream " $ do+ xs S.:> () <- S.toList $ groupSemigroupBy (==) $ S.each [Sum 1, Sum 1]+ xs `shouldBe` [Sum 2]+ it "collapse 4 elems of 2 groups stream" $ do+ xs S.:> () <- S.toList $ groupSemigroupBy (==) $ S.each [Sum 1, Sum 1, Sum 2, Sum 2]+ xs `shouldBe` [Sum 2, Sum 4]+ describe "groupSemigroupByPure" $ do+ it "collapse 4 elems of group 2 stream" $ do+ groupSemigroupByPure (==) [Sum 1, Sum 1, Sum 2, Sum 2]+ `shouldBe` [Sum 2, Sum 4]