diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+1.5.15
+
+* Add `toLines`
+* Add `Turtle.Bytes.{fromUTF8,toUTF8}`
+* Add `Turtle.Bytes.{compress,decompress}`
+* Always expose a `MonadFail` instance, relying on the `fail` package
+  where needed. Related GHC 8.8 preparedness.
+
 1.5.14
 
 * Fix `cptree` to copy symlinks instead of descending into them
diff --git a/src/Turtle/Bytes.hs b/src/Turtle/Bytes.hs
--- a/src/Turtle/Bytes.hs
+++ b/src/Turtle/Bytes.hs
@@ -16,6 +16,12 @@
     , append
     , stderr
     , strict
+    , compress
+    , decompress
+    , WindowBits(..)
+    , Zlib.defaultWindowBits
+    , fromUTF8
+    , toUTF8
 
     -- * Subprocess management
     , proc
@@ -43,7 +49,9 @@
 import Control.Monad.IO.Class (MonadIO(..))
 import Control.Monad.Managed (MonadManaged(..))
 import Data.ByteString (ByteString)
+import Data.Streaming.Zlib (Inflate, Popper, PopperRes(..), WindowBits(..))
 import Data.Text (Text)
+import Data.Text.Encoding (Decoding(..))
 import Filesystem.Path (FilePath)
 import Prelude hiding (FilePath)
 import System.Exit (ExitCode(..))
@@ -61,7 +69,10 @@
 import qualified Control.Monad
 import qualified Control.Monad.Managed         as Managed
 import qualified Data.ByteString
+import qualified Data.Streaming.Zlib           as Zlib
 import qualified Data.Text
+import qualified Data.Text.Encoding            as Encoding
+import qualified Data.Text.Encoding.Error      as Encoding.Error
 import qualified Foreign
 import qualified System.IO
 import qualified System.Process                as Process
@@ -657,3 +668,136 @@
     -> Shell (Either ByteString ByteString)
     -- ^ Chunks of either output (`Right`) or error (`Left`)
 inshellWithErr cmd = streamWithErr (Process.shell (Data.Text.unpack cmd))
+
+-- | Internal utility used by both `compress` and `decompress`
+fromPopper :: Popper -> Shell ByteString
+fromPopper popper = loop
+  where
+    loop = do
+        result <- liftIO popper
+
+        case result of
+            PRDone ->
+                empty
+            PRNext compressedByteString ->
+                return compressedByteString <|> loop
+            PRError exception ->
+                liftIO (Exception.throwIO exception)
+
+{-| Compress a stream using @zlib@
+
+    Note that this can decompress streams that are the concatenation of
+    multiple compressed streams (just like @gzip@)
+
+>>> let compressed = select [ "ABC", "DEF" ] & compress 0 defaultWindowBits
+>>> compressed & decompress defaultWindowBits & view
+"ABCDEF"
+>>> (compressed <|> compressed) & decompress defaultWindowBits & view
+"ABCDEF"
+"ABCDEF"
+-}
+compress
+    :: Int
+    -- ^ Compression level
+    -> WindowBits
+    -- ^
+    -> Shell ByteString
+    -- ^
+    -> Shell ByteString
+compress compressionLevel windowBits bytestrings = do
+    deflate <- liftIO (Zlib.initDeflate compressionLevel windowBits)
+
+    let loop = do
+            bytestring <- bytestrings
+
+            popper <- liftIO (Zlib.feedDeflate deflate bytestring)
+
+            fromPopper popper
+
+    let wrapUp = do
+            let popper = liftIO (Zlib.finishDeflate deflate)
+
+            fromPopper popper
+
+    loop <|> wrapUp
+
+data DecompressionState = Uninitialized | Decompressing Inflate
+
+-- | Decompress a stream using @zlib@ (just like the @gzip@ command)
+decompress :: WindowBits -> Shell ByteString -> Shell ByteString
+decompress windowBits (Shell k) = Shell k'
+  where
+    k' (FoldShell step begin done) = k (FoldShell step' begin' done')
+      where
+        begin' = (begin, Uninitialized)
+
+        step' (x0, Uninitialized) compressedByteString = do
+            inflate <- Zlib.initInflate windowBits
+
+            step' (x0, Decompressing inflate) compressedByteString
+        step' (x0, Decompressing inflate) compressedByteString = do
+            popper <- Zlib.feedInflate inflate compressedByteString
+
+            let loop x = do
+                    result <- popper
+
+                    case result of
+                        PRDone -> do
+                            compressedByteString' <- Zlib.getUnusedInflate inflate
+
+                            if Data.ByteString.null compressedByteString'
+                                then return (x, Decompressing inflate)
+                                else do
+                                    decompressedByteString <- Zlib.finishInflate inflate
+
+                                    x' <- step x decompressedByteString
+
+                                    step' (x', Uninitialized) compressedByteString'
+                        PRNext decompressedByteString -> do
+                            x' <- step x decompressedByteString
+
+                            loop x'
+                        PRError exception -> do
+                            Exception.throwIO exception
+
+            loop x0
+
+        done' (x0, Uninitialized) = do
+            done x0
+        done' (x0, Decompressing inflate) = do
+            decompressedByteString <- Zlib.finishInflate inflate
+
+            x0' <- step x0 decompressedByteString
+
+            done' (x0', Uninitialized)
+
+{-| Decode a stream of bytes as UTF8 `Text`
+
+    NOTE: This function will throw a pure exception (i.e. an `error`) if UTF8
+    decoding fails (mainly due to limitations in the @text@ package's stream
+    decoding API)
+-}
+toUTF8 :: Shell ByteString -> Shell Text
+toUTF8 (Shell k) = Shell k'
+  where
+    k' (FoldShell step begin done) =
+        k (FoldShell step' begin' done')
+      where
+        begin' =
+            (mempty, Encoding.streamDecodeUtf8With Encoding.Error.strictDecode, begin)
+
+        step' (prefix, decoder, x) suffix = do
+            let bytes = prefix <> suffix
+
+            let Some text prefix' decoder' = decoder bytes 
+
+            x' <- step x text
+
+            return (prefix', decoder', x')
+
+        done' (_, _, x) = do
+            done x
+
+-- | Encode a stream of bytes as UTF8 `Text`
+fromUTF8 :: Shell Text -> Shell ByteString
+fromUTF8 = fmap Encoding.encodeUtf8
diff --git a/src/Turtle/Prelude.hs b/src/Turtle/Prelude.hs
--- a/src/Turtle/Prelude.hs
+++ b/src/Turtle/Prelude.hs
@@ -206,6 +206,7 @@
     , sort
     , sortOn
     , sortBy
+    , toLines
 
     -- * Folds
     , countChars
@@ -300,7 +301,7 @@
 import Control.Foldl (Fold(..), genericLength, handles, list, premap)
 import qualified Control.Foldl
 import qualified Control.Foldl.Text
-import Control.Monad (guard, liftM, msum, when, unless, (>=>), mfilter)
+import Control.Monad (foldM, guard, liftM, msum, when, unless, (>=>), mfilter)
 import Control.Monad.IO.Class (MonadIO(..))
 import Control.Monad.Managed (MonadManaged(..), managed, managed_, runManaged)
 #ifdef mingw32_HOST_OS
@@ -308,6 +309,8 @@
 #endif
 import Data.IORef (newIORef, readIORef, writeIORef)
 import qualified Data.List as List
+import Data.List.NonEmpty (NonEmpty(..))
+import qualified Data.List.NonEmpty as NonEmpty
 import Data.Monoid ((<>))
 import Data.Ord (comparing)
 import qualified Data.Set as Set
@@ -354,7 +357,7 @@
     touchFile )
 import System.Posix.Files (createSymbolicLink)      
 #endif
-import Prelude hiding (FilePath)
+import Prelude hiding (FilePath, lines)
 
 import Turtle.Pattern (Pattern, anyChar, chars, match, selfless, sepBy)
 import Turtle.Shell
@@ -2166,3 +2169,39 @@
 -- [(1,'a'),(2,'c'),(3,'d'),(3,'e'),(4,'b'),(7,'f')]
 sortBy :: (Functor io, MonadIO io) => (a -> a -> Ordering) -> Shell a -> io [a]
 sortBy f s = List.sortBy f <$> fold s list
+
+{-| Group an arbitrary stream of `Text` into newline-delimited `Line`s
+
+>>> stdout (toLines ("ABC" <|> "DEF" <|> "GHI")
+ABCDEFGHI
+>>> stdout (toLines empty)  -- Note that this always emits at least 1 `Line`
+
+>>> stdout (toLines ("ABC\nDEF" <|> "" <|> "GHI\nJKL"))
+ABC
+DEFGHI
+JKL
+-}
+toLines :: Shell Text -> Shell Line
+toLines (Shell k) = Shell k'
+  where
+    k' (FoldShell step begin done) =
+        k (FoldShell step' begin' done')
+      where
+        step' (Pair x prefix) text = do
+            let suffix :| lines = Turtle.Line.textToLines text
+
+            let line = prefix <> suffix
+
+            let lines' = line :| lines
+
+            x' <- foldM step x (NonEmpty.init lines')
+
+            let prefix' = NonEmpty.last lines'
+
+            return (Pair x' prefix')
+
+        begin' = (Pair begin "")
+
+        done' (Pair x prefix) = do
+            x' <- step x prefix
+            done x'
diff --git a/src/Turtle/Shell.hs b/src/Turtle/Shell.hs
--- a/src/Turtle/Shell.hs
+++ b/src/Turtle/Shell.hs
@@ -81,9 +81,7 @@
 import Control.Monad.Catch (MonadThrow(..), MonadCatch(..))
 import Control.Monad.IO.Class (MonadIO(..))
 import Control.Monad.Managed (MonadManaged(..), with)
-#if MIN_VERSION_base(4,9,0)
 import qualified Control.Monad.Fail as Fail
-#endif
 import Control.Foldl (Fold(..), FoldM(..))
 import qualified Control.Foldl as Foldl
 import Data.Foldable (Foldable)
@@ -182,7 +180,9 @@
         let step1 x a = _foldShell (f a) (FoldShell step0 x return)
         _foldShell m (FoldShell step1 begin0 done0) )
 
-    fail _ = mzero
+#if!(MIN_VERSION_base(4,13,0))
+    fail = Fail.fail
+#endif
 
 instance Alternative Shell where
     empty = Shell (\(FoldShell _ begin done) -> done begin)
@@ -213,10 +213,8 @@
 instance MonadCatch Shell where
     m `catch` k = Shell (\f-> _foldShell m f `catch` (\e -> _foldShell (k e) f))
 
-#if MIN_VERSION_base(4,9,0)
 instance Fail.MonadFail Shell where
-    fail = Prelude.fail
-#endif
+    fail _ = mzero
 
 #if __GLASGOW_HASKELL__ >= 804
 instance Monoid a => Semigroup (Shell a) where
diff --git a/src/Turtle/Tutorial.hs b/src/Turtle/Tutorial.hs
--- a/src/Turtle/Tutorial.hs
+++ b/src/Turtle/Tutorial.hs
@@ -1490,10 +1490,10 @@
 
 -- $monadio
 --
--- If you are sick of having type `liftIO` everywhere, you can omit it.  This
--- is because all subroutines in @turtle@ are overloaded using the `MonadIO`
--- type class, like our original `pwd` command where we first encountered the
--- the `MonadIO` type:
+-- If you are sick of having to type `liftIO` everywhere, you can omit it.
+-- This is because all subroutines in @turtle@ are overloaded using the
+-- `MonadIO` type class, like our original `pwd` command where we first
+-- encountered the the `MonadIO` type:
 --
 -- @
 -- Prelude Turtle> :type pwd
diff --git a/test/cptree.hs b/test/cptree.hs
--- a/test/cptree.hs
+++ b/test/cptree.hs
@@ -3,10 +3,11 @@
 import Turtle
 import Filesystem.Path.CurrentOS ()
 import System.IO.Temp (withSystemTempDirectory)
+import qualified Control.Monad.Fail as Fail
 import Control.Monad (unless)
 
 check :: String -> Bool-> IO ()
-check errorMessage successs = unless successs $ fail errorMessage
+check errorMessage successs = unless successs $ Fail.fail errorMessage
 
 main :: IO ()
 main = withSystemTempDirectory "tempDir" (runTest . fromString)
diff --git a/turtle.cabal b/turtle.cabal
--- a/turtle.cabal
+++ b/turtle.cabal
@@ -1,5 +1,5 @@
 Name: turtle
-Version: 1.5.14
+Version: 1.5.15
 Cabal-Version: >=1.10
 Build-Type: Simple
 License: BSD3
@@ -53,7 +53,7 @@
         ansi-wl-pprint       >= 0.6     && < 0.7 ,
         async                >= 2.0.0.0 && < 2.3 ,
         bytestring           >= 0.9.1.8 && < 0.11,
-        clock                >= 0.4.1.2 && < 0.8 ,
+        clock                >= 0.4.1.2 && < 0.9 ,
         containers           >= 0.5.0.0 && < 0.7 ,
         directory            >= 1.0.7   && < 1.4 ,
         exceptions           >= 0.4     && < 0.11,
@@ -61,21 +61,24 @@
         hostname                           < 1.1 ,
         managed              >= 1.0.3   && < 1.1 ,
         process              >= 1.0.1.1 && < 1.7 ,
-        semigroups           >= 0.5.0   && < 0.19,
+        semigroups           >= 0.5.0   && < 0.20,
         system-filepath      >= 0.3.1   && < 0.5 ,
         system-fileio        >= 0.2.1   && < 0.4 ,
         stm                                < 2.6 ,
+        streaming-commons                  < 0.3 ,
         temporary                          < 1.4 ,
-        text                               < 1.3 ,
-        time                               < 1.9 ,
+        text                 >= 1.0.0   && < 1.3 ,
+        time                               < 1.10,
         transformers         >= 0.2.0.0 && < 0.6 ,
-        optparse-applicative >= 0.13    && < 0.15,
+        optparse-applicative >= 0.13    && < 0.16,
         optional-args        >= 1.0     && < 2.0 ,
         unix-compat          >= 0.4     && < 0.6
     if os(windows)
         Build-Depends: Win32 >= 2.2.0.1 && < 2.9
     else
         Build-Depends: unix  >= 2.5.1.0 && < 2.8
+    if !impl(ghc >= 8.0)
+        Build-Depends: fail  >= 4.9.0.0 && < 4.10
     Exposed-Modules:
         Turtle,
         Turtle.Bytes,
@@ -129,6 +132,7 @@
     Default-Language: Haskell2010
     Build-Depends:
         base   >= 4 && < 5,
+        fail,
         temporary,
         system-filepath >= 0.4,
         turtle
