diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+# 0.7.1
+
+- Add `showFrame`, `printFrame`, `takeRows`, and `dropRows` to the `Frames.Exploration` module. These helpers for working with `Frames` are re-exported from the `Frames` module itself. Thanks to @chfin.
+
+- GHC-9.0.1 support.
+
 # 0.7.0
 
 GHC-8.10 support in Vinyl requires a major version bump.
diff --git a/Frames.cabal b/Frames.cabal
--- a/Frames.cabal
+++ b/Frames.cabal
@@ -1,5 +1,5 @@
 name:                Frames
-version:             0.7.0
+version:             0.7.1
 synopsis:            Data frames For working with tabular data files
 description:         User-friendly, type safe, runtime efficient tooling for
                      working with tabular data deserialized from
@@ -30,7 +30,7 @@
                      data/left1.csv data/right1.csv data/left_summary.csv
                      data/FL2.csv
 cabal-version:       >=1.10
-tested-with:         GHC == 8.4.4 || == 8.6.5 || == 8.8.3 || == 8.10.1
+tested-with:         GHC == 8.4.4 || == 8.6.5 || == 8.8.4 || == 8.10.4 || == 9.0.1
 
 source-repository head
   type:     git
@@ -64,8 +64,8 @@
                        TypeOperators, ConstraintKinds, StandaloneDeriving,
                        UndecidableInstances, ScopedTypeVariables,
                        OverloadedStrings, TypeApplications
-  build-depends:       base >=4.10 && <4.15,
-                       ghc-prim >=0.3 && <0.7,
+  build-depends:       base >=4.10 && <4.16,
+                       ghc-prim >=0.3 && <0.8,
                        primitive >= 0.6 && < 0.8,
                        text >= 1.1.1.0,
                        template-haskell,
diff --git a/src/Frames/CSV.hs b/src/Frames/CSV.hs
--- a/src/Frames/CSV.hs
+++ b/src/Frames/CSV.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE InstanceSigs #-}
 {-# LANGUAGE CPP, DataKinds, DeriveLift, FlexibleContexts, FlexibleInstances, GADTs,
              LambdaCase, OverloadedStrings, RankNTypes,
              ScopedTypeVariables, TemplateHaskell, TypeApplications,
@@ -68,7 +69,11 @@
           hs' = [|map T.pack $(listE $  map (stringE . T.unpack) hs)|]
           quoting' = lift quoting
 #if MIN_VERSION_template_haskell(2,16,0)
+#if MIN_VERSION_template_haskell(2,17,0)
+  liftTyped = liftCode . unsafeTExpCoerce . lift
+#else
   liftTyped = unsafeTExpCoerce . lift
+#endif
 #endif
 
 -- | Default 'ParseOptions' get column names from a header line, and
diff --git a/src/Frames/Exploration.hs b/src/Frames/Exploration.hs
--- a/src/Frames/Exploration.hs
+++ b/src/Frames/Exploration.hs
@@ -10,8 +10,11 @@
 -- | Functions useful for interactively exploring and experimenting
 -- with a data set.
 module Frames.Exploration (pipePreview, select, lenses, recToList,
-                           pr, pr1) where
+                           pr, pr1, showFrame, printFrame,
+                           takeRows, dropRows) where
 import Data.Char (isSpace, isUpper)
+import qualified Data.Foldable as F
+import Data.List (intercalate)
 import Data.Proxy
 import qualified Data.Vinyl as V
 import qualified Data.Vinyl.Class.Method as V
@@ -21,8 +24,11 @@
 import Language.Haskell.TH
 import Language.Haskell.TH.Quote
 import Pipes hiding (Proxy)
+import qualified Pipes as P
 import qualified Pipes.Prelude as P
 import Pipes.Safe (SafeT, runSafeT, MonadMask)
+import Frames.Frame (Frame(Frame))
+import Frames.RecF (columnHeaders, ColumnHeaders)
 
 -- * Preview Results
 
@@ -90,7 +96,7 @@
              (V.RecMapMethod ((~) a) ElField rs, V.RecordToList rs)
           => Record rs -> [a]
 recToList = V.recordToList . V.rmapMethod @((~) a) aux
-  where aux :: a ~ (V.PayloadType ElField t)  => V.ElField t -> Const a t
+  where aux :: a ~ V.PayloadType ElField t  => V.ElField t -> Const a t
         aux (Field x) = Const x
 
 -- * Helpers
@@ -107,3 +113,34 @@
 -- | Remove white space from both ends of a 'String'.
 strip :: String -> String
 strip = takeWhile (not . isSpace) . dropWhile isSpace
+
+-- | @takeRows n frame@ produces a new 'Frame' made up of the first
+-- @n@ rows of @frame@.
+takeRows :: Int -> Frame (Record rs) -> Frame (Record rs)
+takeRows n (Frame len rows) = Frame (min n len) rows
+
+-- | @dropRows n frame@ produces a new 'Frame' just like @frame@, but
+-- not including its first @n@ rows.
+dropRows :: Int -> Frame (Record rs) -> Frame (Record rs)
+dropRows n (Frame len rows) = Frame (max 0 (len - n)) (\i -> rows (i + n))
+
+-- | Format a 'Frame' to a 'String'.
+showFrame :: forall rs.
+  (ColumnHeaders rs, V.RecMapMethod Show ElField rs, V.RecordToList rs)
+  => String -- ^ Separator between fields
+  -> Frame (Record rs) -- ^ The 'Frame' to be formatted to a 'String'
+  -> String
+showFrame sep frame =
+  unlines (intercalate sep (columnHeaders (Proxy :: Proxy (Record rs))) : rows)
+  where rows = P.toList (F.mapM_ (P.yield . intercalate sep . showFields) frame)
+
+-- | Print a 'Frame' to 'System.IO.stdout'.
+printFrame :: forall rs.
+  (ColumnHeaders rs, V.RecMapMethod Show ElField rs, V.RecordToList rs)
+  => String -- ^ Separator between fields
+  -> Frame (Record rs) -- ^ The 'Frame' to be printed to @stdout@
+  -> IO ()
+printFrame sep frame = do
+  putStrLn (intercalate sep (columnHeaders (Proxy :: Proxy (Record rs))))
+  P.runEffect (rows >-> P.stdoutLn)
+  where rows = F.mapM_ (P.yield . intercalate sep . showFields) frame
diff --git a/src/Frames/ExtraInstances.hs b/src/Frames/ExtraInstances.hs
--- a/src/Frames/ExtraInstances.hs
+++ b/src/Frames/ExtraInstances.hs
@@ -49,8 +49,11 @@
 instance NFData1 VF.Identity where
   liftRnf r = r . getIdentity
 
+#if MIN_VERSION_vinyl(0,13,1)
+#else
 instance (NFData (f r), NFData (Rec f rs)) => NFData (Rec f (r ': rs)) where
   rnf (x :& xs) = rnf x `seq` rnf xs
+#endif
 
 instance NFData (Rec f '[]) where
   rnf RNil = ()
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -10,7 +10,7 @@
 import Data.List (find, isPrefixOf)
 import Data.Monoid (First(..))
 import qualified Data.Text as T
-import Language.Haskell.TH as TH
+import qualified Language.Haskell.TH as TH
 import Language.Haskell.TH.Syntax (addDependentFile)
 import Frames
 import Frames.CSV (produceCSV)
@@ -25,7 +25,7 @@
 import Test.HUnit.Lang (assertFailure)
 
 import qualified LatinTest as Latin
-import qualified Issue114 as Issue114
+import qualified Issue114
 import qualified Issue145
 import qualified NoHeader
 import qualified Categorical
@@ -33,15 +33,16 @@
 import qualified UncurryFold
 import qualified UncurryFoldNoHeader
 import qualified UncurryFoldPartialData
+import Data.Vinyl (xrec)
 
 -- | Extract all example @(CSV, generatedCode)@ pairs from
 -- @test/examples.toml@
 csvTests :: [(CsvExample, String)]
 csvTests = $(do addDependentFile "test/examples.toml"
                 csvExamples <- TH.runIO (examplesFrom "test/examples.toml")
-                ListE <$> mapM (\x@(CsvExample _ c _) ->
-                                  [e|(x,$(generateCode "Row" c))|])
-                               csvExamples)
+                TH.ListE <$> mapM (\x@(CsvExample _ c _) ->
+                                     [e|(x,$(generateCode "Row" c))|])
+                                  csvExamples)
 
 -- | Detect type-compatible re-used names and do not attempt to
 -- re-generate definitions for them. This does not do the right thing
@@ -67,7 +68,7 @@
 --
 -- Note that to load this file into a REPL may require some fiddling
 -- with the path to the examples file in the 'csvTests' splice above.
-manualGeneration :: String -> Q Exp
+manualGeneration :: String -> TH.Q TH.Exp
 manualGeneration k = do csvExamples <- TH.runIO (examplesFrom "test/examples.toml")
                         maybe (error ("Table " ++ k ++ " not found"))
                               (generateCode "Row")
@@ -106,10 +107,10 @@
 main = do
   hspec $
     do
-#if __GLASGOW_HASKELL__ >= 804
+
        describe "Haskell type generation" $
          mapM_ (\(CsvExample k _ g, g') -> it k (Code g' `shouldBe` Code g)) csvTests
-#endif
+
        -- describe "Multiple tables" $
        --    do _g <- H.runIO $
        --             generatedFrom "test/examples.toml" "managers_employees"
@@ -144,7 +145,7 @@
            unless (frame == frame2)
                   (assertFailure "Reparsed CSV differs from input")
        describe "Latin1 Text Encoding" $
-         do managers <- H.runIO (Latin.managers)
+         do managers <- H.runIO Latin.managers
             it "Parses" $
               managers `shouldBe` ["João", "Esperança"]
        describe "Skip Missing Data" $ do
@@ -225,3 +226,24 @@
          streamedChunks <- H.runIO Chunks.chunkStream
          it "Can split an input stream into Frame chunks" $
            streamedChunks `shouldBe` everyTenthEducation
+       describe "Printing with take and drop" $ do
+         let frame :: Frame (Record '[ "id" :-> Int, "manager" :-> Text
+                                     , "age" :-> Int, "pay" :-> Double ])
+             frame = toFrame [ xrec (1, "Joe", 53, 80000)
+                             , xrec (2, "Sarah", 44, 80000) ]
+         it "Can format a Frame to a String" $
+           showFrame "\t" frame `shouldBe`
+           unlines [
+              "id\tmanager\tage\tpay"
+            , "1\t\"Joe\"\t53\t80000.0"
+            , "2\t\"Sarah\"\t44\t80000.0" ]
+         it "Can take the first row" $
+           showFrame "\t" (takeRows 1 frame) `shouldBe`
+           unlines [
+              "id\tmanager\tage\tpay"
+            , "1\t\"Joe\"\t53\t80000.0" ]
+         it "Can drop the first row" $
+           showFrame "\t" (dropRows 1 frame) `shouldBe`
+           unlines [
+              "id\tmanager\tage\tpay"
+            , "2\t\"Sarah\"\t44\t80000.0" ]
