diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,24 @@
+- ???
+
+    Added `nubOrd`, `nubInt`, `nubOrdOn`, `nubIntOn`.
+
+    Fix performance regression in `for`.
+    
+    Add `foldMap` and `foldMap_`.
+    
+    Fix the behaviour of `slidingWindow 1`.
+    
+    Reintroduce `readFile` and `writeFile`, using plain `System.IO`
+    instead of `ResourceT` machinery.
+
+    Add `merge`, `mergeOn`, and `mergeBy`.
+
+    Improve performance of `concat`.
+
+    Improve performance of (`*>`), getting rid of the default implementation.
+
+    Generalise type signature of `toList_`.
+
 - 0.2.1.0
 
     Adding `Semigroup` instances for GHC 8.4.
diff --git a/src/Streaming/Internal.hs b/src/Streaming/Internal.hs
--- a/src/Streaming/Internal.hs
+++ b/src/Streaming/Internal.hs
@@ -231,12 +231,8 @@
 instance (Functor f, Monad m) => Monad (Stream f m) where
   return = Return
   {-# INLINE return #-}
-  stream1 >> stream2 = loop stream1 where
-    loop stream = case stream of
-      Return _ -> stream2
-      Effect m  -> Effect (fmap loop m)
-      Step f   -> Step (fmap loop f)  
-  {-# INLINABLE (>>) #-}
+  (>>) = (*>)
+  {-# INLINE (>>) #-}
   -- (>>=) = _bind
   -- {-#INLINE (>>=) #-}
   --
@@ -281,6 +277,13 @@
   {-# INLINE pure #-}
   streamf <*> streamx = do {f <- streamf; x <- streamx; return (f x)} 
   {-# INLINE (<*>) #-}  
+  stream1 *> stream2 = loop stream1 where
+    loop stream = case stream of
+      Return _ -> stream2
+      Effect m  -> Effect (fmap loop m)
+      Step f   -> Step (fmap loop f)
+  {-# INLINABLE (*>) #-}
+
 
 {- | The 'Alternative' instance glues streams together stepwise.
 
diff --git a/src/Streaming/Prelude.hs b/src/Streaming/Prelude.hs
--- a/src/Streaming/Prelude.hs
+++ b/src/Streaming/Prelude.hs
@@ -70,6 +70,7 @@
     , stdinLn
     , readLn
     , fromHandle
+    , readFile 
     , iterate
     , iterateM
     , repeat
@@ -91,6 +92,7 @@
     , mapM_
     , print
     , toHandle
+    , writeFile 
     , effects
     , erase
     , drained
@@ -112,6 +114,10 @@
     , store
     , chain
     , sequence
+    , nubOrd
+    , nubOrdOn
+    , nubInt
+    , nubIntOn
     , filter
     , filterM
     , mapMaybeM
@@ -166,6 +172,8 @@
     , fold_
     , foldM
     , foldM_
+    , foldMap
+    , foldMap_
     , all
     , all_
     , any
@@ -221,6 +229,13 @@
     , partitionEithers
     , partition
 
+    -- * Merging streams
+    -- $merging
+
+    , merge
+    , mergeOn
+    , mergeBy
+    
     -- * Maybes
     -- $maybes
     , catMaybes
@@ -260,7 +275,7 @@
                       , print, zipWith, zip, zipWith3, zip3, unzip, seq, show, read
                       , readLn, sequence, concat, span, break, readFile, writeFile
                       , minimum, maximum, elem, notElem, all, any, head
-                      , last)
+                      , last, foldMap)
 
 import qualified GHC.IO.Exception as G
 import qualified System.IO as IO
@@ -270,6 +285,9 @@
 import Control.Concurrent (threadDelay)
 import Data.Functor.Compose
 import Data.Functor.Of
+import qualified Data.Set as Set
+import qualified Data.IntSet as IntSet
+import Data.Ord (Ordering (..), comparing)
 
 -- instance (Eq a) => Eq1 (Of a) where eq1 = (==)
 -- instance (Ord a) => Ord1 (Of a) where compare1 = compare
@@ -525,7 +543,7 @@
       return (Step (a :> loop rest))
 {-# INLINABLE chain #-}
 
-{-| Make a stream of traversable containers into a stream of their separate elements.
+{-| Make a stream of foldable containers into a stream of their separate elements.
     This is just
 
 > concat str = for str each
@@ -551,8 +569,18 @@
 -}
 
 concat :: (Monad m, Foldable.Foldable f) => Stream (Of (f a)) m r -> Stream (Of a) m r
-concat str = for str each
-{-# INLINE concat #-}
+concat = loop
+  where
+    loop str = case str of
+        Return r -> Return r
+        Effect m -> Effect (fmap loop m)
+        Step (lst :> as) ->
+          let inner [] = loop as
+              inner (x:rest) = Step (x :> inner rest)
+          in inner (Foldable.toList lst)
+{-# INLINABLE concat #-}
+-- The above hand-written loop is ~20% faster than the 'for' implementation
+-- concat str = for str each
 
 {-| The natural @cons@ for a @Stream (Of a)@.
 
@@ -1355,6 +1383,23 @@
 mappedPost = mapsMPost
 {-# INLINE mappedPost #-}
 
+{-| Map each element of the stream to a monoid, and take the monoidal sum of the results.
+
+>>> S.foldMap Sum $ S.take 2 (S.stdinLn)
+1<Enter>
+2<Enter>
+3<Enter>
+Sum {getSum = 6} :> ()
+
+ -}
+foldMap :: (Monad m, Monoid w) => (a -> w) -> Stream (Of a) m r -> m (Of w r)
+foldMap f = fold (\ !acc a -> mappend acc (f a)) mempty id
+{-# INLINE foldMap #-}
+
+foldMap_ :: (Monad m, Monoid w) => (a -> w) -> Stream (Of a) m r -> m w
+foldMap_ f = fold_ (\ !acc a -> mappend acc (f a)) mempty id
+{-# INLINE foldMap_ #-}
+
 {-| Fold streamed items into their monoidal sum
 
 >>> S.mconcat $ S.take 2 $ S.map (Data.Monoid.Last . Just) (S.stdinLn)
@@ -1365,7 +1410,7 @@
  -}
 mconcat :: (Monad m, Monoid w) => Stream (Of w) m r -> m (Of w r)
 mconcat = fold mappend mempty id
-{-#INLINE mconcat #-}
+{-# INLINE mconcat #-}
 
 data Maybe_ a = Just_ !a | Nothing_
 mconcat_ :: (Monad m, Monoid w) => Stream (Of w) m r -> m w
@@ -1375,7 +1420,7 @@
 minimum = fold (\m a -> case m of Nothing_ -> Just_ a ; Just_ a' -> Just_ (min a a'))
                Nothing_
                (\m -> case m of Nothing_ -> Nothing; Just_ r -> Just r)
-{-#INLINE minimum #-}
+{-# INLINE minimum #-}
 
 minimum_ :: (Monad m, Ord a) => Stream (Of a) m r -> m (Maybe a)
 minimum_ = fold_ (\m a -> case m of Nothing_ -> Just_ a ; Just_ a' -> Just_ (min a a'))
@@ -1454,6 +1499,51 @@
 {-#INLINABLE notElem_ #-}
 
 
+{-| Remove repeated elements from a Stream. 'nubOrd' of course accumulates a 'Data.Set.Set' of
+    elements that have already been seen and should thus be used with care.
+
+>>> S.toList_ $ S.nubOrd $ S.take 5 S.readLn :: IO ([Int])
+1<Enter>
+2<Enter>
+3<Enter>
+1<Enter>
+2<Enter>
+[1,2,3]
+
+-}
+
+nubOrd :: (Monad m, Ord a) => Stream (Of a) m r -> Stream (Of a) m r
+nubOrd = nubOrdOn id
+{-# INLINE nubOrd #-}
+
+{-|  Use 'nubOrdOn' to have a custom ordering function for your elements. -}
+nubOrdOn :: (Monad m, Ord b) => (a -> b) -> Stream (Of a) m r -> Stream (Of a) m r
+nubOrdOn f xs = loop mempty xs where
+  loop !set stream = case stream of
+    Return r         -> Return r
+    Effect m         -> Effect (liftM (loop set) m)
+    Step (a :> rest) -> let !fa = f a in
+      if Set.member fa set
+         then loop set rest
+         else Step (a :> loop (Set.insert fa set) rest)
+
+{-| More efficient versions of above when working with 'Int's that use 'Data.IntSet.IntSet'. -}
+
+nubInt :: Monad m => Stream (Of Int) m r -> Stream (Of Int) m r
+nubInt = nubIntOn id
+{-# INLINE nubInt #-}
+
+nubIntOn :: Monad m => (a -> Int) -> Stream (Of a) m r -> Stream (Of a) m r
+nubIntOn f xs = loop mempty xs where
+  loop !set stream = case stream of
+    Return r         -> Return r
+    Effect m         -> Effect (liftM (loop set) m)
+    Step (a :> rest) -> let !fa = f a in
+      if IntSet.member fa set
+         then loop set rest
+         else Step (a :> loop (IntSet.insert fa set) rest)
+
+
 {-|
 > filter p = hoist effects (partition p)
 
@@ -1948,7 +2038,7 @@
     is a leading cause of space leaks.
 
 -}
-toList_ :: Monad m => Stream (Of a) m () -> m [a]
+toList_ :: Monad m => Stream (Of a) m r -> m [a]
 toList_ = fold_ (\diff a ls -> diff (a: ls)) id (\diff -> diff [])
 {-# INLINE toList_ #-}
 
@@ -2097,7 +2187,7 @@
 yield a = Step (a :> Return ())
 {-# INLINE yield #-}
 
--- | Zip two 'Streams's
+-- | Zip two 'Stream's
 zip :: Monad m
     => (Stream (Of a) m r)
     -> (Stream (Of b) m r)
@@ -2105,7 +2195,7 @@
 zip = zipWith (,)
 {-# INLINE zip #-}
 
--- | Zip two 'Streams's using the provided combining function
+-- | Zip two 'Stream's using the provided combining function
 zipWith :: Monad m
     => (a -> b -> c)
     -> (Stream (Of a) m r)
@@ -2149,7 +2239,7 @@
 {-# INLINABLE zipWith3 #-}
 
 
--- | Zip three streams together
+-- | Zip three 'Stream's together
 zip3 :: Monad m
     => (Stream (Of a) m r)
     -> (Stream (Of b) m r)
@@ -2296,8 +2386,34 @@
            Right () -> loop rest
 {-# INLINABLE stdoutLn #-}
 
+{-| Read the lines of a file, using a function of the type: \'@'Stream' ('Of' 'String') 'IO' () -> 'IO' a@\'
+    to turn the stream into a value of type \''IO' a\'.
 
+>>> S.writeFile "lines.txt" $ S.take 2 S.stdinLn
+hello<Enter>
+world<Enter>
+>>> S.readFile "lines.txt" S.print
+"hello"
+"world"
 
+-}
+readFile :: FilePath -> (Stream (Of String) IO () -> IO a) -> IO a
+readFile f s = IO.withFile f IO.ReadMode $ \h -> s (fromHandle h)
+
+{-| Write a series of 'String's as lines to a file.
+
+>>> S.writeFile "lines.txt" $ S.take 2 S.stdinLn
+hello<Enter>
+world<Enter>
+
+>>> S.stdoutLn $ S.readFile "lines.txt"
+hello
+world
+
+-}
+writeFile :: FilePath -> Stream (Of String) IO r -> IO r
+writeFile f = IO.withFile f IO.WriteMode . flip toHandle
+
 {-| Write 'String's to 'IO.stdout' using 'putStrLn'
 
     Unlike @stdoutLn@, @stdoutLn'@ does not handle a broken output pipe. Thus it can have a polymorphic return
@@ -2481,7 +2597,7 @@
     like 'MonadResource'.  Thus I can independently filter and write to one file, but
     nub and write to another, or interact with a database and a logfile and the like:
 
->>> runResourceT $ (S.writeFile "hello2.txt" . S.nub) $ store (S.writeFile "hello.txt" . S.filter (/= "world")) $ each ["hello", "world", "goodbye", "world"]
+>>> runResourceT $ (S.writeFile "hello2.txt" . S.nubOrd) $ store (S.writeFile "hello.txt" . S.filter (/= "world")) $ each ["hello", "world", "goodbye", "world"]
 >>> :! cat hello.txt
 hello
 goodbye
@@ -2658,6 +2774,72 @@
 
 
 
+{- $merging
+   These functions combine two sorted streams of orderable elements
+   into one sorted stream. The elements of the merged stream are
+   guaranteed to be in a sorted order if the two input streams are
+   also sorted.
+
+   The merge operation is /left-biased/: when merging two elements
+   that compare as equal, the left element is chosen first.
+-}
+
+{- | Merge two streams of elements ordered with their 'Ord' instance.
+
+   The return values of both streams are returned.
+
+>>> S.print $ merge (each [1,3,5]) (each [2,4])
+1
+2
+3
+4
+5
+((), ())
+
+-}
+merge :: (Monad m, Ord a)
+  => Stream (Of a) m r
+  -> Stream (Of a) m s
+  -> Stream (Of a) m (r, s)
+merge = mergeBy compare
+{-# INLINE merge #-}
+
+{- | Merge two streams, ordering them by applying the given function to
+   each element before comparing.
+
+   The return values of both streams are returned.
+-}
+mergeOn :: (Monad m, Ord b)
+  => (a -> b)
+  -> Stream (Of a) m r
+  -> Stream (Of a) m s
+  -> Stream (Of a) m (r, s)
+mergeOn f = mergeBy (comparing f)
+{-# INLINE mergeOn #-}
+
+{- | Merge two streams, ordering the elements using the given comparison function.
+
+   The return values of both streams are returned.
+-}
+mergeBy :: Monad m
+  => (a -> a -> Ordering)
+  -> Stream (Of a) m r
+  -> Stream (Of a) m s
+  -> Stream (Of a) m (r, s)
+mergeBy cmp = loop
+  where
+    loop str0 str1 = case str0 of
+      Return r0         -> (\ r1 -> (r0, r1)) <$> str1
+      Effect m          -> Effect $ fmap (\ str -> loop str str1) m
+      Step (a :> rest0) -> case str1 of
+        Return r1         -> (\ r0 -> (r0, r1)) <$> str0
+        Effect m          -> Effect $ fmap (loop str0) m
+        Step (b :> rest1) -> case cmp a b of
+          LT -> Step (a :> loop rest0 str1)
+          EQ -> Step (a :> loop rest0 str1) -- left-biased
+          GT -> Step (b :> loop str0 rest1)
+{-# INLINABLE mergeBy #-}        
+
 {- $maybes
     These functions discard the 'Nothing's that they encounter. They are analogous
     to the functions from @Data.Maybe@ that share their names.
@@ -2716,7 +2898,7 @@
         Left r -> return r
         Right (a,rest) -> do 
           yield (sequ Seq.|> a)
-          window (Seq.drop 1 sequ Seq.|> a) rest
+          window (Seq.drop 1 $ sequ Seq.|> a) rest
     setup 0 !sequ str = do
        yield sequ 
        window (Seq.drop 1 sequ) str 
diff --git a/streaming.cabal b/streaming.cabal
--- a/streaming.cabal
+++ b/streaming.cabal
@@ -1,5 +1,5 @@
 name:                streaming
-version:             0.2.1.0
+version:             0.2.2.0
 cabal-version:       >=1.10
 build-type:          Simple
 synopsis:            an elementary streaming prelude and general stream type.
