packages feed

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 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)