loops (empty) → 0.1.0.0
raw patch · 12 files changed
+653/−0 lines, 12 filesdep +basedep +criteriondep +loopssetup-changed
Dependencies added: base, criterion, loops, primitive, tasty, tasty-quickcheck, transformers, vector
Files
- LICENSE +30/−0
- README.lhs +87/−0
- README.md +87/−0
- Setup.hs +2/−0
- bench/Bench.hs +23/−0
- bench/Bench/Sum.hs +27/−0
- loops.cabal +71/−0
- src/Control/Monad/Loop.hs +7/−0
- src/Control/Monad/Loop/ForEach.hs +135/−0
- src/Control/Monad/Loop/Internal.hs +156/−0
- test/Test.hs +14/−0
- test/Test/Sum.hs +14/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2014, Thomas Tuegel++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 Thomas Tuegel 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.lhs view
@@ -0,0 +1,87 @@+loops+==========++**Practical summary**++Fast, imperative-style loops. Performance is robust because there is no reliance+on fusion. `do`-notation nests loops, providing syntax cleaner than manual+recursion. A class `ForEach` is provided enabling iteration over common+container types.++**Academic Summary**++Loops have the structure of a monad. Bind (`>>=`) nests loops and `return x` is+a loop with a single iteration over a value `x`.++**Performance**++For best performance, please compile your code with `-O2`. You should also use+GHC's LLVM backend if possible; it generally produces faster executables.++A silly example+---------------++At first, the statement that "bind nests loops" may seem strange, but can be+motivated by the `Monad` instance for lists. Consider the following+`do`-notation for a list:++~~~ {.haskell}+module Example where++import Control.Monad.Loop+import Data.Foldable (toList)++-- A list of pairs (i, j) where 0 <= i <= 3 and 0 <= j <= i+nestedList :: [(Int, Int)]+nestedList = do+ i <- [0..3]+ j <- [0..i]+ return (i, j)+~~~++If you're not familiar with this use of lists, load up this file in ghci+with `ghci -isrc -pgmL markdown-unlit README.lhs`. (You need to have+[markdown-unlit](https://github.com/sol/markdown-unlit) installed first.)+Enter `nestedList` at the prompt and see:++~~~+>>> nestedList+[(0,0),(1,0),(1,1),(2,0),(2,1),(2,2),(3,0),(3,1),(3,2),(3,3)]+~~~++Now let's do something really silly: let's build the same list with a+`Loop`!++~~~ {.haskell}+nestedList' :: [(Int, Int)]+nestedList' = toList $ loop $ do -- 'loop' is just an aid to type inference+ i <- for 0 (<= 3) (+ 1)+ j <- for 0 (<= i) (+ 1)+ return (i, j)+~~~++You would never actually want to do this. This example is simply to+illustrate what "bind nests loops" means in a context most Haskellers are+familiar with.++The correspondence between the list monad and the loop monad is not a+coincidence! GHC uses stream fusion to reduce (some) uses of lists to simple+loops so that the evaluated list is never held in memory. Unfortunately,+using lists as loops is dangerous in performance-sensitive code because the+fusion rules may fail to fire, leaving you with a fully-evaluated list on+the heap! Libraries that rely on fusion require extensive use of inlining,+which increases compile time and memory usage dramatically. These are the+limitations that inspired me to write this library. A `Loop` can only+evaluate one iteration at a time, so there is no larger data structure that+needs to be fused. Consequently, performance is less fragile.++You might complain that this style of programming does not fit Haskell very+well, but I would contend just the opposite. As I mentioned above, lists are the+more general case of loops: a list can be just a plain loop (fused), or it can+be all the iterations of the loop held in memory at once. In fact, lists admit+some operations (like `reverse`) that prevent fusion, but `Loop` has a refined+type that only allows construction of fusible operations! This is exactly where+Haskell shines: the type system prevents incorrect (or in this case,+undesirable) programs from being written. I see this as part of a (relatively+recent) trend in Haskell toward using the type system to guarantee performance+in addition to correctness.
+ README.md view
@@ -0,0 +1,87 @@+loops+==========++**Practical summary**++Fast, imperative-style loops. Performance is robust because there is no reliance+on fusion. `do`-notation nests loops, providing syntax cleaner than manual+recursion. A class `ForEach` is provided enabling iteration over common+container types.++**Academic Summary**++Loops have the structure of a monad. Bind (`>>=`) nests loops and `return x` is+a loop with a single iteration over a value `x`.++**Performance**++For best performance, please compile your code with `-O2`. You should also use+GHC's LLVM backend if possible; it generally produces faster executables.++A silly example+---------------++At first, the statement that "bind nests loops" may seem strange, but can be+motivated by the `Monad` instance for lists. Consider the following+`do`-notation for a list:++~~~ {.haskell}+module Example where++import Control.Monad.Loop+import Data.Foldable (toList)++-- A list of pairs (i, j) where 0 <= i <= 3 and 0 <= j <= i+nestedList :: [(Int, Int)]+nestedList = do+ i <- [0..3]+ j <- [0..i]+ return (i, j)+~~~++If you're not familiar with this use of lists, load up this file in ghci+with `ghci -isrc -pgmL markdown-unlit README.lhs`. (You need to have+[markdown-unlit](https://github.com/sol/markdown-unlit) installed first.)+Enter `nestedList` at the prompt and see:++~~~+>>> nestedList+[(0,0),(1,0),(1,1),(2,0),(2,1),(2,2),(3,0),(3,1),(3,2),(3,3)]+~~~++Now let's do something really silly: let's build the same list with a+`Loop`!++~~~ {.haskell}+nestedList' :: [(Int, Int)]+nestedList' = toList $ loop $ do -- 'loop' is just an aid to type inference+ i <- for 0 (<= 3) (+ 1)+ j <- for 0 (<= i) (+ 1)+ return (i, j)+~~~++You would never actually want to do this. This example is simply to+illustrate what "bind nests loops" means in a context most Haskellers are+familiar with.++The correspondence between the list monad and the loop monad is not a+coincidence! GHC uses stream fusion to reduce (some) uses of lists to simple+loops so that the evaluated list is never held in memory. Unfortunately,+using lists as loops is dangerous in performance-sensitive code because the+fusion rules may fail to fire, leaving you with a fully-evaluated list on+the heap! Libraries that rely on fusion require extensive use of inlining,+which increases compile time and memory usage dramatically. These are the+limitations that inspired me to write this library. A `Loop` can only+evaluate one iteration at a time, so there is no larger data structure that+needs to be fused. Consequently, performance is less fragile.++You might complain that this style of programming does not fit Haskell very+well, but I would contend just the opposite. As I mentioned above, lists are the+more general case of loops: a list can be just a plain loop (fused), or it can+be all the iterations of the loop held in memory at once. In fact, lists admit+some operations (like `reverse`) that prevent fusion, but `Loop` has a refined+type that only allows construction of fusible operations! This is exactly where+Haskell shines: the type system prevents incorrect (or in this case,+undesirable) programs from being written. I see this as part of a (relatively+recent) trend in Haskell toward using the type system to guarantee performance+in addition to correctness.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ bench/Bench.hs view
@@ -0,0 +1,23 @@+module Main where++import Criterion.Main++import Bench.Sum++main :: IO ()+main = defaultMain+ [ bgroup "sum"+ [ bgroup "foldl"+ [ bench "[]" $ nf bench_sum_foldl_List iters+ , bench "Vector" $ nf bench_sum_foldl_Vector iters+ , bench "LoopT Identity" $ nf bench_sum_foldl_LoopT iters+ ]+ , bgroup "foldr"+ [ bench "[]" $ nf bench_sum_foldr_List iters+ , bench "Vector" $ nf bench_sum_foldr_Vector iters+ , bench "LoopT Identity" $ nf bench_sum_foldr_LoopT iters+ ]+ ]+ ]+ where+ iters = 10000000
+ bench/Bench/Sum.hs view
@@ -0,0 +1,27 @@+module Bench.Sum where++import qualified Control.Monad.Loop as LoopT+import Data.Foldable+import qualified Data.Vector.Unboxed as V+import Prelude hiding (foldr)++bench_sum_foldl_LoopT :: Int -> Int+bench_sum_foldl_LoopT n =+ foldl' (+) 0 (LoopT.for 0 (<= n) (+ 1) :: LoopT.Loop Int)++bench_sum_foldl_List :: Int -> Int+bench_sum_foldl_List n = foldl' (+) 0 [0..n]++bench_sum_foldl_Vector :: Int -> Int+bench_sum_foldl_Vector n = V.foldl' (+) 0 $ V.enumFromTo 0 n++bench_sum_foldr_List :: Int -> Int+bench_sum_foldr_List n = foldr (+) 0 [0..n]++bench_sum_foldr_Vector :: Int -> Int+bench_sum_foldr_Vector n =+ V.foldr (+) 0 $ V.enumFromTo 0 n++bench_sum_foldr_LoopT :: Int -> Int+bench_sum_foldr_LoopT n =+ foldr (+) 0 (LoopT.for 0 (<= n) (+ 1) :: LoopT.Loop Int)
+ loops.cabal view
@@ -0,0 +1,71 @@+name: loops+version: 0.1.0.0+synopsis: Fast imperative-style loops+description:+ @loops@ is a library for fast, imperative-style loops in Haskell. Performance+ is robust because there is no reliance on fusion. @do@-notation nests loops,+ providing syntax cleaner than manual recursion. A class @ForEach@ is provided+ enabling iteration over common container types.+ .+ For best performance, please compile your code with @-O2@. You should also+ use GHC's LLVM backend if possible; it generally produces faster executables.+license: BSD3+license-file: LICENSE+author: Thomas Tuegel+maintainer: ttuegel@gmail.com+copyright: (c) Thomas Tuegel 2014+category: Control+build-type: Simple+extra-source-files: README.md, README.lhs+cabal-version: >=1.10++source-repository head+ type: git+ location: https://github.com/ttuegel/loops.git++library+ exposed-modules:+ Control.Monad.Loop+ Control.Monad.Loop.ForEach+ Control.Monad.Loop.Internal+ build-depends:+ base >=4.7 && <5,+ primitive >=0.5 && <1,+ transformers >=0.3 && <1,+ vector >=0.10 && <1+ ghc-options: -Wall+ hs-source-dirs: src+ default-language: Haskell2010++test-suite tests+ type: exitcode-stdio-1.0+ hs-source-dirs:+ test+ main-is:+ Test.hs+ other-modules:+ Test.Sum+ build-depends:+ base >=4.7 && <5,+ loops,+ tasty >=0.8 && <1,+ tasty-quickcheck >=0.8 && <1+ ghc-options: -Wall+ default-language: Haskell2010++benchmark benchs+ type: exitcode-stdio-1.0+ hs-source-dirs:+ bench+ main-is:+ Bench.hs+ other-modules:+ Bench.Sum+ build-depends:+ base >=4.7 && <5,+ criterion >=0.8 && <1,+ loops,+ transformers >=0.3 && <1,+ vector >=0.10 && <1+ ghc-options: -Wall+ default-language: Haskell2010
+ src/Control/Monad/Loop.hs view
@@ -0,0 +1,7 @@+module Control.Monad.Loop+ ( module Control.Monad.Loop.Internal+ , module Control.Monad.Loop.ForEach+ ) where++import Control.Monad.Loop.Internal+import Control.Monad.Loop.ForEach
+ src/Control/Monad/Loop/ForEach.hs view
@@ -0,0 +1,135 @@+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}++module Control.Monad.Loop.ForEach where++import Control.Monad (liftM)+import Control.Monad.Primitive (PrimMonad(PrimState))+import Control.Monad.Trans.Class (lift)++-- Import the vector package qualified to write the ForEach instances+import qualified Data.Vector as V+import qualified Data.Vector.Mutable as MV+import qualified Data.Vector.Generic as G+import qualified Data.Vector.Generic.Mutable as MG+import qualified Data.Vector.Primitive as P+import qualified Data.Vector.Primitive.Mutable as MP+import qualified Data.Vector.Storable as S+import qualified Data.Vector.Storable.Mutable as MS+import qualified Data.Vector.Unboxed as U+import qualified Data.Vector.Unboxed.Mutable as MU++import Control.Monad.Loop.Internal++-- | Class of containers that can be iterated over. The class is+-- parameterized over a base monad where the values of the container can be+-- read to allow iterating over mutable structures. The associated type+-- families parameterize the value and index types of the container,+-- allowing the class to be instantiated for container types (unboxed or+-- storable vectors, for example) which do not admit all types as values.+class ForEach m c where+ type ForEachValue c+ type ForEachIx c+ -- | Iterate over the values in the container.+ forEach :: c -> m (ForEachValue c)+ -- | Iterate over the indices and the value at each index.+ iforEach :: c -> m (ForEachIx c, ForEachValue c)++instance (Monad m) => ForEach (LoopT m) [a] where+ type ForEachValue [a] = a+ type ForEachIx [a] = Int++ forEach as = liftM head $ for as (not . null) tail+ {-# INLINE forEach #-}++ iforEach = forEach . zip [0..]+ {-# INLINE iforEach #-}++instance (Monad m) => ForEach (LoopT m) (V.Vector a) where+ type ForEachValue (V.Vector a) = a+ type ForEachIx (V.Vector a) = Int+ forEach = forEachVector+ iforEach = iforEachVector+ {-# INLINE forEach #-}+ {-# INLINE iforEach #-}++instance (Monad m, U.Unbox a) => ForEach (LoopT m) (U.Vector a) where+ type ForEachValue (U.Vector a) = a+ type ForEachIx (U.Vector a) = Int+ forEach = forEachVector+ iforEach = iforEachVector+ {-# INLINE forEach #-}+ {-# INLINE iforEach #-}++instance (Monad m, P.Prim a) => ForEach (LoopT m) (P.Vector a) where+ type ForEachValue (P.Vector a) = a+ type ForEachIx (P.Vector a) = Int+ forEach = forEachVector+ iforEach = iforEachVector+ {-# INLINE forEach #-}+ {-# INLINE iforEach #-}++instance (Monad m, S.Storable a) => ForEach (LoopT m) (S.Vector a) where+ type ForEachValue (S.Vector a) = a+ type ForEachIx (S.Vector a) = Int+ forEach = forEachVector+ iforEach = iforEachVector+ {-# INLINE forEach #-}+ {-# INLINE iforEach #-}++forEachVector :: (Monad m, G.Vector v a) => v a -> LoopT m a+{-# INLINE forEachVector #-}+forEachVector = liftM snd . iforEachVector++iforEachVector :: (Monad m, G.Vector v a) => v a -> LoopT m (Int, a)+{-# INLINE iforEachVector #-}+iforEachVector v = do+ let len = G.length v+ i <- for 0 (< len) (+ 1)+ x <- G.unsafeIndexM v i+ return (i, x)++instance (PrimMonad m, PrimState m ~ s) => ForEach (LoopT m) (MV.MVector s a) where+ type ForEachValue (MV.MVector s a) = a+ type ForEachIx (MV.MVector s a) = Int+ forEach = forEachMVector+ iforEach = iforEachMVector+ {-# INLINE forEach #-}+ {-# INLINE iforEach #-}++instance (PrimMonad m, U.Unbox a, PrimState m ~ s) => ForEach (LoopT m) (MU.MVector s a) where+ type ForEachValue (MU.MVector s a) = a+ type ForEachIx (MU.MVector s a) = Int+ forEach = forEachMVector+ iforEach = iforEachMVector+ {-# INLINE forEach #-}+ {-# INLINE iforEach #-}++instance (PrimMonad m, P.Prim a, PrimState m ~ s) => ForEach (LoopT m) (MP.MVector s a) where+ type ForEachValue (MP.MVector s a) = a+ type ForEachIx (MP.MVector s a) = Int+ forEach = forEachMVector+ iforEach = iforEachMVector+ {-# INLINE forEach #-}+ {-# INLINE iforEach #-}++instance (S.Storable a, PrimMonad m, PrimState m ~ s) => ForEach (LoopT m) (MS.MVector s a) where+ type ForEachValue (MS.MVector s a) = a+ type ForEachIx (MS.MVector s a) = Int+ forEach = forEachMVector+ iforEach = iforEachMVector+ {-# INLINE forEach #-}+ {-# INLINE iforEach #-}++forEachMVector :: (PrimMonad m, MG.MVector v a) => v (PrimState m) a -> LoopT m a+{-# INLINE forEachMVector #-}+forEachMVector = liftM snd . iforEachMVector++iforEachMVector :: (PrimMonad m, MG.MVector v a) => v (PrimState m) a -> LoopT m (Int, a)+{-# INLINE iforEachMVector #-}+iforEachMVector v = do+ let len = MG.length v+ i <- for 0 (< len) (+ 1)+ x <- lift $ MG.unsafeRead v i+ return (i, x)+
+ src/Control/Monad/Loop/Internal.hs view
@@ -0,0 +1,156 @@+{-# LANGUAGE RankNTypes #-}++module Control.Monad.Loop.Internal where++import Control.Applicative (Applicative(..), (<$>), liftA2)+import Control.Category ((<<<), (>>>))+import Control.Monad (unless)+import Control.Monad.IO.Class+import Control.Monad.Trans.Class+import Data.Foldable+import Data.Functor.Identity+import Data.Maybe (fromJust, isJust)+import Data.Traversable (Traversable(..))+import Prelude hiding (foldr, iterate)++-- | @LoopT m a@ represents a loop over a base type @m@ that yields a value+-- @a@ at each iteration. It can be used as a monad transformer, but there+-- are actually no restrictions on the type @m@. However, this library only+-- provides functions to execute the loop if @m@ is at least 'Applicative'+-- (for 'exec_'). If @m@ is also 'Foldable', so is @LoopT m@. For any other+-- type, you may use 'runLoopT'.+newtype LoopT m a = LoopT+ { runLoopT+ :: forall r. (a -> m r -> m r -> m r)+ -- ^ Yield a value to the inner loop. The inner loop will call+ -- the second argument to continue and the third argument to+ -- break.+ -> m r -- ^ Continue+ -> m r -- ^ Break+ -> m r+ }++-- | @Loop@ is a pure loop, without side-effects.+type Loop = LoopT Identity++-- | @loop@ is just an aid to type inference. For loops over a base monad,+-- there are usually other constraints that fix the type, but for pure+-- loops, the compiler often has trouble inferring @Identity@.+loop :: Loop a -> Loop a+{-# INLINE loop #-}+loop = id++instance Functor (LoopT m) where+ {-# INLINE fmap #-}+ fmap f xs = LoopT $ \yield -> runLoopT xs (yield . f)++instance Applicative (LoopT m) where+ {-# INLINE pure #-}+ pure a = LoopT $ \yield -> yield a+ {-# INLINE (<*>) #-}+ fs <*> as = LoopT $ \yield next ->+ runLoopT fs (\f next' _ -> runLoopT (fmap f as) yield next' next) next++instance Monad (LoopT m) where+ {-# INLINE return #-}+ return = pure+ {-# INLINE (>>=) #-}+ as >>= f = LoopT $ \yield next ->+ runLoopT as (\a next' _ -> runLoopT (f a) yield next' next) next++instance MonadTrans LoopT where+ {-# INLINE lift #-}+ lift m = LoopT $ \yield next brk -> m >>= \a -> yield a next brk++instance MonadIO m => MonadIO (LoopT m) where+ {-# INLINE liftIO #-}+ liftIO = lift . liftIO++instance (Applicative m, Foldable m) => Foldable (LoopT m) where+ {-# INLINE foldr #-}+ foldr f r xs = foldr (<<<) id inner r+ where+ yield a next _ = (f a <<<) <$> next+ inner = runLoopT xs yield (pure id) (pure id)++ {-# INLINE foldl' #-}+ foldl' f r xs = foldl' (!>>>) id inner r+ where+ (!>>>) h g = h >>> (g $!)+ yield a next _ = (flip f a >>>) <$> next+ inner = runLoopT xs yield (pure id) (pure id)++instance (Applicative m, Foldable m) => Traversable (LoopT m) where+ {-# INLINE sequenceA #-}+ sequenceA = foldr (liftA2 cons) (pure continue_)++cons :: a -> LoopT m a -> LoopT m a+{-# INLINE cons #-}+cons a as = LoopT $ \yield next brk -> yield a (runLoopT as yield next brk) next++-- | Yield a value for this iteration of the loop and skip immediately to+-- the next iteration.+continue :: a -> LoopT m a+{-# INLINE continue #-}+continue a = LoopT $ \yield next -> yield a next++-- | Skip immediately to the next iteration of the loop without yielding+-- a value.+continue_ :: LoopT m a+{-# INLINE continue_ #-}+continue_ = LoopT $ \_ next _ -> next++-- | Skip all the remaining iterations of the immediately-enclosing loop.+break_ :: LoopT m a+{-# INLINE break_ #-}+break_ = LoopT $ \_ _ brk -> brk++-- | Execute a loop, sequencing the effects and discarding the values.+exec_ :: Applicative m => LoopT m a -> m ()+{-# INLINE exec_ #-}+exec_ xs = runLoopT xs (\_ next _ -> next) (pure ()) (pure ())++-- | Iterate forever (or until 'break' is used).+iterate+ :: a -- ^ Starting value of iterator+ -> (a -> a) -- ^ Advance the iterator+ -> LoopT m a+{-# INLINE iterate #-}+iterate a0 adv = LoopT $ \yield next _ ->+ let yield' a r = yield a r next+ go a = yield' a $ go $ adv a+ in go a0++-- | Loop forever without yielding (interesting) values.+forever :: LoopT m ()+{-# INLINE forever #-}+forever = iterate () id++-- | Standard @for@ loop.+for+ :: a -- ^ Starting value of iterator+ -> (a -> Bool) -- ^ Termination condition. The loop will terminate the+ -- first time this is false. The termination condition+ -- is checked at the /start/ of each iteration.+ -> (a -> a) -- ^ Advance the iterator+ -> LoopT m a+{-# INLINE for #-}+for a0 cond adv = iterate a0 adv >>= \a -> if cond a then return a else break_++-- | Unfold a loop from the left.+unfoldl+ :: (i -> Maybe (i, a)) -- ^ @Just (i, a)@ advances the loop, yielding an+ -- @a@. @Nothing@ terminates the loop.+ -> i -- ^ Starting value+ -> LoopT m a+{-# INLINE unfoldl #-}+unfoldl unf i0 = fromJust . fmap snd <$> for (unf i0) isJust (>>= unf . fst)++while+ :: Monad m+ => m Bool+ -> LoopT m ()+while cond = do+ forever+ p <- lift cond+ unless p break_
+ test/Test.hs view
@@ -0,0 +1,14 @@+module Main where++import Test.Tasty+import Test.Tasty.QuickCheck as QC++import Test.Sum++main :: IO ()+main = defaultMain $ testGroup "Tests"+ [ testGroup "sum"+ [ QC.testProperty "foldl" prop_sum_foldl_LoopT+ , QC.testProperty "foldr" prop_sum_foldr_LoopT+ ]+ ]
+ test/Test/Sum.hs view
@@ -0,0 +1,14 @@+module Test.Sum where++import qualified Control.Monad.Loop as LoopT+import Data.Foldable+import Prelude hiding (foldr)+import Test.Tasty.QuickCheck++prop_sum_foldl_LoopT :: [Int] -> Property+prop_sum_foldl_LoopT xs =+ foldl' (+) 0 xs === foldl' (+) 0 (LoopT.forEach xs :: LoopT.Loop Int)++prop_sum_foldr_LoopT :: [Int] -> Property+prop_sum_foldr_LoopT xs =+ foldr (+) 0 xs === foldr (+) 0 (LoopT.forEach xs :: LoopT.Loop Int)