diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/README.lhs b/README.lhs
new file mode 100644
--- /dev/null
+++ b/README.lhs
@@ -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.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -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.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/bench/Bench.hs b/bench/Bench.hs
new file mode 100644
--- /dev/null
+++ b/bench/Bench.hs
@@ -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
diff --git a/bench/Bench/Sum.hs b/bench/Bench/Sum.hs
new file mode 100644
--- /dev/null
+++ b/bench/Bench/Sum.hs
@@ -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)
diff --git a/loops.cabal b/loops.cabal
new file mode 100644
--- /dev/null
+++ b/loops.cabal
@@ -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
diff --git a/src/Control/Monad/Loop.hs b/src/Control/Monad/Loop.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Monad/Loop.hs
@@ -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
diff --git a/src/Control/Monad/Loop/ForEach.hs b/src/Control/Monad/Loop/ForEach.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Monad/Loop/ForEach.hs
@@ -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)
+
diff --git a/src/Control/Monad/Loop/Internal.hs b/src/Control/Monad/Loop/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Monad/Loop/Internal.hs
@@ -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_
diff --git a/test/Test.hs b/test/Test.hs
new file mode 100644
--- /dev/null
+++ b/test/Test.hs
@@ -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
+      ]
+  ]
diff --git a/test/Test/Sum.hs b/test/Test/Sum.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Sum.hs
@@ -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)
