diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,12 @@
 # Changelog for prairie
 
+## 0.1.1.0
+
+- [#24](https://github.com/parsonsmatt/prairie/pull/24)
+    - Introduce `sequenceRecordA`, for monadic tabulation of values, using `Distributed`.
+- [#29](https://github.com/parsonsmatt/prairie/pull/29)
+    - Bump `template-haskell` bound to 2.25
+
 ## 0.1.0.0
 
 - [#22](https://github.com/parsonsmatt/prairie/pull/22)
diff --git a/prairie.cabal b/prairie.cabal
--- a/prairie.cabal
+++ b/prairie.cabal
@@ -1,7 +1,7 @@
 cabal-version: 1.12
 
 name:           prairie
-version:        0.1.0.0
+version:        0.1.1.0
 description:    Please see the README on GitHub at <https://github.com/parsonsmatt/prairie#readme>
 homepage:       https://github.com/parsonsmatt/prairie#readme
 bug-reports:    https://github.com/parsonsmatt/prairie/issues
@@ -43,7 +43,7 @@
     , containers
     , mtl
     , semigroupoids
-    , template-haskell  >=2.15 && <2.24
+    , template-haskell          >=2.15 && <2.25
     , text
     , foldable1-classes-compat
 
diff --git a/src/Prairie/Class.hs b/src/Prairie/Class.hs
--- a/src/Prairie/Class.hs
+++ b/src/Prairie/Class.hs
@@ -19,6 +19,7 @@
 import Data.Aeson (FromJSON (..), ToJSON (..), withText)
 import Data.Constraint (Dict (..))
 import Data.Functor.Apply (Apply (..))
+import Data.Functor.Compose (Compose)
 import Data.Functor.Const (Const (..))
 import Data.Functor.Identity (Identity (..))
 import Data.Kind (Constraint, Type)
@@ -30,7 +31,6 @@
 import Data.Typeable (Typeable, eqT, (:~:) (..))
 import GHC.OverloadedLabels (IsLabel (..))
 import GHC.TypeLits (Symbol)
-
 import Prairie.Internal (Lens', set, view)
 
 -- | Instances of this class have a datatype 'Field' which allow you to
@@ -88,6 +88,10 @@
     -- |  Construct a 'Record' by providing an 'Applicative' action
     -- returning a value for each 'Field' on the 'Record'.
     --
+    -- Valid input functions are written in the "function format" of the record, contrasting
+    -- the @f@-wrapped "data format" of its return type: the @rec@ value as though it were
+    -- written in Haskell record syntax (that does not support 'Applicative' actions).
+    --
     -- Example:
     --
     -- @
@@ -111,6 +115,19 @@
     -- allows you to use 'Foldable1' and 'NonEmpty'.
     tabulateRecordApply :: (Apply m) => (forall ty. Field rec ty -> m ty) -> m rec
 
+    -- | Unwrap one 'Applicative' layer of a stack @m (f ty)@ in "function format".
+    -- The resulting @m@-action returns the "function format" in @f@-actions,
+    -- which can then be addressed directly later in the @m@ action without needing
+    -- to tabulate the entire record right away. This is especially useful when the
+    -- inner functor has side-effects worth avoiding, like the concatenating behaviour
+    -- of @'Monoid' m => 'Const' m@ or the validation of 'Maybe' and @'Either' e@
+    -- when the specific field's @m@ or @'Maybe' ty@/@'Either' e ty@ value is of interest
+    -- prior to processing the record entirely through that functor.
+    --
+    -- @since 0.1.1.0
+    sequenceRecordA
+        :: (Applicative m) => Distributed (Compose m f) rec -> m (Distributed f rec)
+
     -- | Assign a 'Text' label for a record 'Field'.
     --
     -- This allows 'Field's to be converted to 'Text', which is useful for
@@ -124,6 +141,26 @@
     recordFieldLabel :: Field rec ty -> Text
     default recordFieldLabel :: (Show (Field rec ty)) => Field rec ty -> Text
     recordFieldLabel = Text.pack . show
+
+-- | A @'Distributed' f rec@ is a 'Record' with fields that are wrapped in
+-- the @f@ type constructor.
+--
+-- The simplest example is the trivial 'Identity' - @'Distributed'
+-- 'Identity' rec@ wraps each field in 'Identity', which doesn't do
+-- anything.
+--
+-- But you can also represent a @'Distributed' 'IO' rec@, where each field
+-- is an 'IO' action.
+--
+-- This type is equivalently powerful to the @Higher Kinded Data@ pattern,
+-- but significantly more flexible, since you don't need to munge the
+-- underlying datatype with the complexity of this.
+--
+-- You can use @OverloadedRecordDot@ to access fields on this directly, or
+-- you can also use 'getRecordFieldDistributed'.
+--
+-- @since 0.1.0.0
+newtype Distributed f rec = Distributed (forall x. Field rec x -> f x)
 
 -- | An enumeration of fields on the record.
 --
diff --git a/src/Prairie/Distributed.hs b/src/Prairie/Distributed.hs
--- a/src/Prairie/Distributed.hs
+++ b/src/Prairie/Distributed.hs
@@ -28,26 +28,6 @@
 import GHC.Records
 import Prairie.Class
 
--- | A @'Distributed' f rec@ is a 'Record' with fields that are wrapped in
--- the @f@ type constructor.
---
--- The simplest example is the trivial 'Identity' - @'Distributed'
--- 'Identity' rec@ wraps each field in 'Identity', which doesn't do
--- anything.
---
--- But you can also represent a @'Distributed' 'IO' rec@, where each field
--- is an 'IO' action.
---
--- This type is equivalently powerful to the @Higher Kinded Data@ pattern,
--- but significantly more flexible, since you don't need to munge the
--- underlying datatype with the complexity of this.
---
--- You can use @OverloadedRecordDot@ to access fields on this directly, or
--- you can also use 'getRecordFieldDistributed'.
---
--- @since 0.1.0.0
-newtype Distributed f rec = Distributed (forall x. Field rec x -> f x)
-
 -- |  This instance allows you to use the @OverloadedRecordDot@ with
 -- distributed records.
 --
diff --git a/src/Prairie/TH.hs b/src/Prairie/TH.hs
--- a/src/Prairie/TH.hs
+++ b/src/Prairie/TH.hs
@@ -8,6 +8,7 @@
 import Data.Char (toLower, toUpper)
 import Data.Constraint (Dict (..))
 import Data.Functor.Apply (Apply (..))
+import Data.Functor.Compose
 import qualified Data.List as List
 import qualified Data.Text as Text
 import Data.Traversable (for)
@@ -185,6 +186,37 @@
                 [ Clause [VarP fromFieldName] (NormalB body) []
                 ]
 
+    mkSequenceRecordA <- do
+        fromFieldName <- newName "s"
+        let
+            mkMatch (VarP lamArg) (fieldName, _typ) = do
+                let
+                    pat = compatConP $ mkConstrFieldName fieldName
+                 in
+                    Match pat (NormalB (VarE lamArg)) []
+        lambda <- do
+            vars <- traverse (fmap VarP . newName . nameBase . fst) names'types
+            pure $
+                LamE
+                    vars
+                    (ConE 'Distributed `AppE` LamCaseE (List.zipWith mkMatch vars names'types))
+        let
+            body =
+                List.foldl'
+                    ( \acc (n, _) ->
+                        VarE '(<*>)
+                            `AppE` acc
+                            `AppE` (VarE 'getCompose `AppE` (VarE fromFieldName `AppE` ConE (mkConstrFieldName n)))
+                    )
+                    (VarE 'pure `AppE` lambda)
+                    names'types
+
+        pure $
+            FunD
+                'sequenceRecordA
+                [ Clause [compatConP' 'Distributed [VarP fromFieldName]] (NormalB body) []
+                ]
+
     mkRecordFieldLabel <- do
         fieldName <- newName "fieldName"
         body <- pure $
@@ -230,6 +262,7 @@
                   , recordFieldLensDec
                   , mkTabulateRecord
                   , mkTabulateRecordApply
+                  , mkSequenceRecordA
                   , mkRecordFieldLabel
                   ]
                 )
@@ -290,4 +323,13 @@
 #else
 compatConP constrFieldName =
     ConP constrFieldName []
+#endif
+
+compatConP' :: Name -> [Pat] -> Pat
+#if MIN_VERSION_template_haskell(2,18,0)
+compatConP' constrFieldName ps =
+    ConP constrFieldName [] ps
+#else
+compatConP' constrFieldName ps  =
+    ConP constrFieldName ps 
 #endif
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -28,6 +28,7 @@
 import Control.Monad
 import Data.Aeson
 import Data.Functor.Apply (Apply (..))
+import Data.Functor.Compose
 import Data.List.NonEmpty (NonEmpty (..))
 import Data.Monoid
 import Prairie.AsRecord
@@ -120,6 +121,18 @@
                     { name = "Matt"
                     , age = 33
                     }
+
+        it "sequenceRecordA" $ do
+            Distributed user' <- do
+                sequenceRecordA @User @IO @Maybe $ Distributed \case
+                    UserName -> Compose do
+                        print @Int 10 >> pure (Just "Matt")
+                    UserAge -> Compose do
+                        print @Int 20 >> pure Nothing
+            user' UserName `shouldBe` Just "Matt"
+            user' UserAge `shouldBe` Nothing
+            tabulateRecordA user' `shouldBe` Nothing
+
         describe "Fold" $ do
             describe "foldRecord" $ do
                 it "can count the fields" $ do
