diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # dickinson
 
+## 0.1.2.0
+
+  * Fail on bad patterns, e.g. `(:match xy (x, x) x)`
+  * Add `Language.Dickinson.QuasiQuoter` module
+
 ## 0.1.1.2
 
   * Ignore lines starting with `#!` (for shell shebangs)
diff --git a/bench/Bench.hs b/bench/Bench.hs
--- a/bench/Bench.hs
+++ b/bench/Bench.hs
@@ -1,22 +1,22 @@
 module Main (main) where
 
-import           Control.Exception                 (throw)
-import           Control.Exception.Value           (eitherThrow)
-import           Control.Monad                     (void)
+import           Control.Exception                  (throw)
+import           Control.Exception.Value            (eitherThrow)
+import           Control.Monad                      (void)
 import           Criterion.Main
-import           Data.Binary                       (decode, encode)
-import qualified Data.ByteString.Lazy              as BSL
-import qualified Data.Text                         as T
+import           Data.Binary                        (decode, encode)
+import qualified Data.ByteString.Lazy               as BSL
+import qualified Data.Text                          as T
 import           Language.Dickinson.Check
+import           Language.Dickinson.Check.Duplicate
 import           Language.Dickinson.Check.Internal
-import           Language.Dickinson.DuplicateCheck
+import           Language.Dickinson.Check.Scope
 import           Language.Dickinson.Eval
 import           Language.Dickinson.File
 import           Language.Dickinson.Lexer
 import           Language.Dickinson.Parser
 import           Language.Dickinson.Pipeline
 import           Language.Dickinson.Rename
-import           Language.Dickinson.ScopeCheck
 import           Language.Dickinson.Type
 import           Language.Dickinson.TypeCheck
 import           Language.Dickinson.Unique
diff --git a/examples/fionaBot.dck b/examples/fionaBot.dck
--- a/examples/fionaBot.dck
+++ b/examples/fionaBot.dck
@@ -16,6 +16,7 @@
     (| "I'm full as a tick")
     (| "I wanna make a mistake")
     (| "Scratch me out, free yourself.")
+    (| "I watch you live to have my fun.")
     (| "Run, free yourself of me.")
     (|
       '''
@@ -40,6 +41,11 @@
       '''
       All that love must've been lacking something
       If I got bored trying to figure you out
+      ''')
+    (|
+      '''
+      How can I ask anyone to love me
+      When all I do is beg to be left alone?
       ''')
     (|
       '''
diff --git a/examples/fortune.dck b/examples/fortune.dck
--- a/examples/fortune.dck
+++ b/examples/fortune.dck
@@ -28,10 +28,13 @@
     (| "Spurn your family.")
     (| "Salvation is finality.")
     (| "Knowledge begets humility.")
+    (| "Mushrooms are made of flesh.")
     (| "Love animals.")
     (| "Today is a good day to practice bilocation.")
     (| "Drugs have no mystic content.")
+    (| "Violent people are contemptible.")
     (| "Irony is the weakest brand of pessimism")
+    (| "Sin rots the flesh.")
     (| "Sex wastes the male body.")
     (| "Did you remember to put apotheosis on your to-do list?")
     (| "Cultivate weakness.")
diff --git a/language-dickinson.cabal b/language-dickinson.cabal
--- a/language-dickinson.cabal
+++ b/language-dickinson.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.0
 name:               language-dickinson
-version:            0.1.1.2
+version:            0.1.2.0
 license:            BSD3
 license-file:       LICENSE
 copyright:          Copyright: (c) 2020 Vanessa McHale
@@ -55,6 +55,17 @@
         base >=4.9 && <5,
         dickinson -any
 
+    if !flag(cross)
+        exposed-modules: Language.Dickinson.TH
+
+    if !flag(cross)
+        other-extensions: TemplateHaskell
+
+    if !flag(cross)
+        build-depends:
+            template-haskell -any,
+            text -any
+
     if impl(ghc >=8.0)
         ghc-options:
             -Wincomplete-uni-patterns -Wincomplete-record-updates
@@ -81,14 +92,15 @@
         Language.Dickinson.Eval
         Language.Dickinson.Error
         Language.Dickinson.Check
-        Language.Dickinson.ScopeCheck
-        Language.Dickinson.DuplicateCheck
+        Language.Dickinson.Check.Scope
+        Language.Dickinson.Check.Internal
+        Language.Dickinson.Check.Pattern
+        Language.Dickinson.Check.Duplicate
         Language.Dickinson.Unique
         Language.Dickinson.File
         Language.Dickinson.Pipeline
         Language.Dickinson.Import
         Language.Dickinson.Lib
-        Language.Dickinson.Check.Internal
         Data.Tuple.Ext
         Control.Exception.Value
         Data.Text.Prettyprint.Doc.Ext
@@ -96,7 +108,6 @@
     hs-source-dirs:   src
     other-modules:
         Paths_language_dickinson
-        Language.Dickinson.Check.Pattern
         Language.Dickinson.Lib.Get
         Control.Monad.Ext
         Data.Foldable.Ext
@@ -106,7 +117,7 @@
     other-extensions:
         DeriveAnyClass DeriveFunctor DeriveGeneric FlexibleContexts
         FlexibleInstances GeneralizedNewtypeDeriving OverloadedStrings
-        StandaloneDeriving TupleSections
+        StandaloneDeriving TupleSections DeriveDataTypeable
 
     ghc-options:      -Wall -O2
     build-depends:
@@ -208,9 +219,10 @@
         Golden
         Eval
         TypeCheck
+        TH
 
     default-language: Haskell2010
-    other-extensions: OverloadedStrings
+    other-extensions: OverloadedStrings TemplateHaskell
     ghc-options:      -threaded -rtsopts "-with-rtsopts=-N -K1K" -Wall
     build-depends:
         base -any,
@@ -222,7 +234,8 @@
         text -any,
         filepath -any,
         tasty-golden -any,
-        pretty-simple -any
+        pretty-simple -any,
+        language-dickinson -any
 
     if impl(ghc >=8.0)
         ghc-options:
diff --git a/lib/adjectives.dck b/lib/adjectives.dck
--- a/lib/adjectives.dck
+++ b/lib/adjectives.dck
@@ -8,4 +8,5 @@
     (| "bromide")
     (| "banal")
     (| "pithy")
+    (| "orotund")
     (| "stentorian")))
diff --git a/public/Language/Dickinson/TH.hs b/public/Language/Dickinson/TH.hs
new file mode 100644
--- /dev/null
+++ b/public/Language/Dickinson/TH.hs
@@ -0,0 +1,35 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+-- | @since 0.1.2.0
+module Language.Dickinson.TH ( dickinson
+                             , run
+                             ) where
+
+import           Control.Exception.Value    (eitherThrow)
+import           Data.Data                  (Data)
+import           Data.Foldable              (traverse_)
+import qualified Data.Text                  as T
+import           Data.Typeable              (cast)
+import           Language.Dickinson.Eval
+import           Language.Dickinson.File
+import           Language.Dickinson.Lexer
+import           Language.Dickinson.Type
+import           Language.Haskell.TH        (Exp, Q)
+import           Language.Haskell.TH.Syntax (Exp (AppE, VarE))
+import qualified Language.Haskell.TH.Syntax as TH
+
+run :: [Declaration AlexPosn] -> IO T.Text
+run = fmap eitherThrow . evalIO . evalDickinsonAsMain
+
+dickinson :: [FilePath] -> FilePath -> Q Exp
+dickinson is fp = do
+    traverse_ TH.addDependentFile (fp:is) -- TODO: resolve dependencies
+    ds <- TH.runIO (validateAmalgamate is fp)
+    liftDataWithText ds
+
+liftText :: T.Text -> Q Exp
+liftText txt = AppE (VarE 'T.pack) <$> TH.lift (T.unpack txt)
+
+-- see this mess: https://stackoverflow.com/a/38182444
+liftDataWithText :: Data a => a -> Q Exp
+liftDataWithText = TH.dataToExpQ (fmap liftText . cast)
diff --git a/run/Main.hs b/run/Main.hs
--- a/run/Main.hs
+++ b/run/Main.hs
@@ -81,7 +81,7 @@
 run :: Act -> IO ()
 run (Run fp is)       = do { pGo <- defaultLibPath ; TIO.putStrLn =<< pipeline (pGo is) fp }
 run (REPL _)          = dickinsonRepl
-run (Check fs i)      = do { pathMod <- defaultLibPath ; traverse_ (validateFile (pathMod i)) fs } -- FIXME: reuse
+run (Check fs i)      = do { pathMod <- defaultLibPath ; traverse_ (validateFile (pathMod i)) fs }
 run (Lint fs)         = traverse_ warnFile fs
 run (Format fp False) = fmtFile fp
 run (Format fp True)  = fmtInplace fp
diff --git a/run/REPL.hs b/run/REPL.hs
--- a/run/REPL.hs
+++ b/run/REPL.hs
@@ -21,6 +21,7 @@
 import           Data.Text.Prettyprint.Doc             (Pretty (pretty), hardline)
 import           Data.Text.Prettyprint.Doc.Render.Text (putDoc)
 import           Data.Tuple.Ext                        (fst4)
+import           Language.Dickinson.Check.Scope
 import           Language.Dickinson.Error
 import           Language.Dickinson.Eval
 import           Language.Dickinson.File
@@ -28,7 +29,6 @@
 import           Language.Dickinson.Lib
 import           Language.Dickinson.Parser
 import           Language.Dickinson.Rename
-import           Language.Dickinson.ScopeCheck
 import           Language.Dickinson.Type
 import           Language.Dickinson.TypeCheck
 import           Language.Dickinson.Unique
diff --git a/src/Language/Dickinson/Check.hs b/src/Language/Dickinson/Check.hs
--- a/src/Language/Dickinson/Check.hs
+++ b/src/Language/Dickinson/Check.hs
@@ -26,8 +26,6 @@
     <|> checkNames (concatMap collectConstructors ds)
     where defNameM (Define _ n _) = Just n
           defNameM TyDecl{}       = Nothing
-          defExprM (Define _ _ e) = Just e
-          defExprM TyDecl{}       = Nothing
           tyDeclNameM Define{}       = Nothing
           tyDeclNameM (TyDecl _ n _) = Just n
 
@@ -50,7 +48,7 @@
 checkMultipleExpr (Flatten _ e)      = checkMultipleExpr e
 checkMultipleExpr (Let _ bs e)       =
         checkNames (toList $ fmap fst bs)
-    <|> foldMapAlternative checkMultipleExpr (snd <$> bs)
+    <|> foldMapAlternative (checkMultipleExpr . snd) bs
     <|> checkMultipleExpr e
 checkMultipleExpr (Annot _ e _)    = checkMultipleExpr e
 checkMultipleExpr Constructor{}    = Nothing
diff --git a/src/Language/Dickinson/Check/Duplicate.hs b/src/Language/Dickinson/Check/Duplicate.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/Dickinson/Check/Duplicate.hs
@@ -0,0 +1,49 @@
+module Language.Dickinson.Check.Duplicate ( checkDuplicates
+                                          ) where
+
+import           Control.Applicative      ((<|>))
+import           Data.Foldable            (toList)
+import           Data.Foldable.Ext        (foldMapAlternative)
+import           Data.Function            (on)
+import           Data.List                (groupBy, sortBy)
+import           Data.Maybe               (mapMaybe)
+import qualified Data.Text                as T
+import           Language.Dickinson.Error
+import           Language.Dickinson.Type
+
+checkNames :: [(a, T.Text)] -> Maybe (DickinsonWarning a)
+checkNames ns = foldMapAlternative announce (groupBy ((==) `on` snd) $ sortBy (compare `on` snd) ns)
+    where announce (_:(l, y):_) = Just $ DuplicateStr l y
+          announce _            = Nothing
+
+-- | Check that there are not duplicates in branches.
+checkDuplicates :: [Declaration a] -> Maybe (DickinsonWarning a)
+checkDuplicates = foldMapAlternative checkDeclDuplicates
+
+checkDeclDuplicates :: Declaration a -> Maybe (DickinsonWarning a)
+checkDeclDuplicates (Define _ _ e) = checkExprDuplicates e
+checkDeclDuplicates TyDecl{}       = Nothing
+
+extrText :: Expression a -> Maybe (a, T.Text)
+extrText (Literal l t) = pure (l, t)
+extrText _             = Nothing
+
+collectText :: [(b, Expression a)] -> [(a, T.Text)]
+collectText = mapMaybe (extrText . snd)
+
+checkExprDuplicates :: Expression a -> Maybe (DickinsonWarning a)
+checkExprDuplicates Var{}              = Nothing
+checkExprDuplicates Literal{}          = Nothing
+checkExprDuplicates StrChunk{}         = Nothing
+checkExprDuplicates (Interp _ es)      = foldMapAlternative checkExprDuplicates es
+checkExprDuplicates (MultiInterp _ es) = foldMapAlternative checkExprDuplicates es
+checkExprDuplicates (Concat _ es)      = foldMapAlternative checkExprDuplicates es
+checkExprDuplicates (Tuple _ es)       = foldMapAlternative checkExprDuplicates es
+checkExprDuplicates (Apply _ e e')     = checkExprDuplicates e <|> checkExprDuplicates e'
+checkExprDuplicates (Choice _ brs)     = checkNames (collectText $ toList brs)
+checkExprDuplicates (Let _ brs es)     = foldMapAlternative checkExprDuplicates (snd <$> brs) <|> checkExprDuplicates es
+checkExprDuplicates (Lambda _ _ _ e)   = checkExprDuplicates e
+checkExprDuplicates (Match _ e _ e')   = checkExprDuplicates e <|> checkExprDuplicates e'
+checkExprDuplicates (Flatten _ e)      = checkExprDuplicates e
+checkExprDuplicates (Annot _ e _)      = checkExprDuplicates e
+checkExprDuplicates Constructor{}      = Nothing
diff --git a/src/Language/Dickinson/Check/Pattern.hs b/src/Language/Dickinson/Check/Pattern.hs
--- a/src/Language/Dickinson/Check/Pattern.hs
+++ b/src/Language/Dickinson/Check/Pattern.hs
@@ -1,10 +1,12 @@
-module Language.Dickinson.Check.Pattern ( checkNames
-                                        , traversePattern
+module Language.Dickinson.Check.Pattern ( traversePattern
+                                        , checkPatternDecl
                                         ) where
 
+import           Control.Applicative      ((<|>))
 import           Data.Foldable            (toList)
 import           Data.Foldable.Ext        (foldMapAlternative)
 import           Data.List                (group, sort)
+import           Data.Maybe               (mapMaybe)
 import           Language.Dickinson.Error
 import           Language.Dickinson.Name
 import           Language.Dickinson.Type
@@ -13,9 +15,30 @@
 traversePattern (PatternVar _ n)    = [n]
 traversePattern (PatternTuple _ ps) = traversePattern =<< toList ps
 traversePattern Wildcard{}          = []
+traversePattern PatternCons{}       = []
 
--- TODO: use this
 checkNames :: Pattern a -> Maybe (DickinsonError a)
 checkNames p = foldMapAlternative announce (group $ sort (traversePattern p))
     where announce (_:y:_) = Just $ MultiBind (loc y) y p
           announce _       = Nothing
+
+checkPatternExpr :: Expression a -> Maybe (DickinsonError a)
+checkPatternExpr Var{}              = Nothing
+checkPatternExpr Literal{}          = Nothing
+checkPatternExpr StrChunk{}         = Nothing
+checkPatternExpr (Interp _ es)      = foldMapAlternative checkPatternExpr es
+checkPatternExpr (MultiInterp _ es) = foldMapAlternative checkPatternExpr es
+checkPatternExpr (Apply _ e e')     = checkPatternExpr e <|> checkPatternExpr e'
+checkPatternExpr (Match _ e p e')   = checkNames p <|> checkPatternExpr e <|> checkPatternExpr e'
+checkPatternExpr (Choice _ brs)     = foldMapAlternative (checkPatternExpr . snd) brs
+checkPatternExpr (Concat _ es)      = foldMapAlternative checkPatternExpr es
+checkPatternExpr (Tuple _ es)       = foldMapAlternative checkPatternExpr es
+checkPatternExpr (Lambda _ _ _ e)   = checkPatternExpr e
+checkPatternExpr (Flatten _ e)      = checkPatternExpr e
+checkPatternExpr (Let _ bs e)       = foldMapAlternative (checkPatternExpr . snd) bs <|> checkPatternExpr e
+checkPatternExpr (Annot _ e _)      = checkPatternExpr e
+checkPatternExpr Constructor{}      = Nothing
+
+checkPatternDecl :: [Declaration a] -> Maybe (DickinsonError a) -- Expression
+checkPatternDecl ds =
+    foldMapAlternative checkPatternExpr (mapMaybe defExprM ds)
diff --git a/src/Language/Dickinson/Check/Scope.hs b/src/Language/Dickinson/Check/Scope.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/Dickinson/Check/Scope.hs
@@ -0,0 +1,98 @@
+{-# LANGUAGE FlexibleContexts #-}
+
+module Language.Dickinson.Check.Scope ( checkScope
+                                      , checkScopeExpr
+                                      , checkScopeDecl
+                                      ) where
+
+import           Control.Applicative              (Alternative, (<|>))
+import           Control.Monad.Except             (MonadError)
+import           Control.Monad.State              (State, evalState, get, modify)
+import           Data.Foldable                    (asum, traverse_)
+import qualified Data.IntSet                      as IS
+import           Language.Dickinson.Check.Pattern
+import           Language.Dickinson.Error
+import           Language.Dickinson.Name
+import           Language.Dickinson.Type
+import           Language.Dickinson.Unique
+
+type CheckM = State IS.IntSet
+
+runCheckM :: CheckM a -> a
+runCheckM = flip evalState IS.empty
+
+insertName :: Name a -> CheckM ()
+insertName (Name _ (Unique i) _) = modify (IS.insert i)
+
+deleteName :: Name a -> CheckM ()
+deleteName (Name _ (Unique i) _) = modify (IS.delete i)
+
+-- | Checks that there are not any identifiers that aren't in scope; needs to run
+-- after the renamer
+checkScope :: [Declaration a] -> Maybe (DickinsonError a)
+checkScope = runCheckM . checkDickinson
+
+checkScopeExpr :: MonadError (DickinsonError a) m => Expression a -> m ()
+checkScopeExpr = maybeThrow . runCheckM . checkExpr
+
+checkScopeDecl :: MonadError (DickinsonError a) m => Declaration a -> m ()
+checkScopeDecl = maybeThrow . runCheckM . checkDecl
+
+checkDickinson :: [Declaration a] -> CheckM (Maybe (DickinsonError a))
+checkDickinson d = traverse_ insDecl d *> mapSumM checkDecl d
+
+insDecl :: Declaration a -> CheckM ()
+insDecl (Define _ n _)   = insertName n
+insDecl (TyDecl _ n tys) = insertName n *> traverse_ insertName tys
+
+checkDecl :: Declaration a -> CheckM (Maybe (DickinsonError a))
+checkDecl (Define _ _ e) = checkExpr e
+checkDecl TyDecl{}       = pure Nothing
+
+checkType :: DickinsonTy a -> CheckM (Maybe (DickinsonError a))
+checkType TyText{}         = pure Nothing
+checkType (TyFun _ ty ty') = (<|>) <$> checkType ty <*> checkType ty'
+checkType (TyTuple _ tys)  = mapSumM checkType tys
+checkType (TyNamed l n@(Name _ (Unique k) _)) = do
+    b <- get
+    if k `IS.member` b
+        then pure Nothing
+        else pure $ Just (UnfoundType l n)
+
+checkExpr :: Expression a -> CheckM (Maybe (DickinsonError a))
+checkExpr Literal{}      = pure Nothing
+checkExpr StrChunk{}     = pure Nothing
+checkExpr (Apply _ e e') = (<|>) <$> checkExpr e <*> checkExpr e'
+checkExpr (Interp _ es)  = mapSumM checkExpr es
+checkExpr (MultiInterp _ es)  = mapSumM checkExpr es
+checkExpr (Choice _ brs) = mapSumM checkExpr (snd <$> brs)
+checkExpr (Concat _ es)  = mapSumM checkExpr es
+checkExpr (Tuple _ es)   = mapSumM checkExpr es
+checkExpr (Flatten _ e)  = checkExpr e
+checkExpr (Annot _ e ty) = (<|>) <$> checkExpr e <*> checkType ty
+checkExpr (Lambda _ n _ e) = do
+    insertName n
+    checkExpr e <* deleteName n
+checkExpr (Constructor _ n@(Name _ (Unique i) l)) = do
+    b <- get
+    if i `IS.member` b
+        then pure Nothing
+        else pure $ Just (UnfoundConstructor l n)
+checkExpr (Var _ n@(Name _ (Unique i) l)) = do
+    b <- get
+    if i `IS.member` b
+        then pure Nothing
+        else pure $ Just (UnfoundName l n)
+checkExpr (Let _ bs e) = do
+    let ns = fst <$> bs
+    traverse_ insertName ns
+    (<|>) <$> checkExpr e <*> mapSumM checkExpr (snd <$> bs)
+        <* traverse_ deleteName ns
+checkExpr (Match _ e p e') =
+    ((<|>) <$> checkExpr e) <*> do
+        let ns = traversePattern p
+        traverse_ insertName ns
+        checkExpr e' <* traverse_ deleteName ns
+
+mapSumM :: (Traversable t, Alternative f, Applicative m) => (a -> m (f b)) -> t a -> m (f b)
+mapSumM = (fmap asum .) . traverse
diff --git a/src/Language/Dickinson/DuplicateCheck.hs b/src/Language/Dickinson/DuplicateCheck.hs
deleted file mode 100644
--- a/src/Language/Dickinson/DuplicateCheck.hs
+++ /dev/null
@@ -1,49 +0,0 @@
-module Language.Dickinson.DuplicateCheck ( checkDuplicates
-                                         ) where
-
-import           Control.Applicative      ((<|>))
-import           Data.Foldable            (toList)
-import           Data.Foldable.Ext        (foldMapAlternative)
-import           Data.Function            (on)
-import           Data.List                (groupBy, sortBy)
-import           Data.Maybe               (mapMaybe)
-import qualified Data.Text                as T
-import           Language.Dickinson.Error
-import           Language.Dickinson.Type
-
-checkNames :: [(a, T.Text)] -> Maybe (DickinsonWarning a)
-checkNames ns = foldMapAlternative announce (groupBy ((==) `on` snd) $ sortBy (compare `on` snd) ns)
-    where announce (_:(l, y):_) = Just $ DuplicateStr l y
-          announce _            = Nothing
-
--- | Check that there are not duplicates in branches.
-checkDuplicates :: [Declaration a] -> Maybe (DickinsonWarning a)
-checkDuplicates = foldMapAlternative checkDeclDuplicates
-
-checkDeclDuplicates :: Declaration a -> Maybe (DickinsonWarning a)
-checkDeclDuplicates (Define _ _ e) = checkExprDuplicates e
-checkDeclDuplicates TyDecl{}       = Nothing
-
-extrText :: Expression a -> Maybe (a, T.Text)
-extrText (Literal l t) = pure (l, t)
-extrText _             = Nothing
-
-collectText :: [(b, Expression a)] -> [(a, T.Text)]
-collectText = mapMaybe (extrText . snd)
-
-checkExprDuplicates :: Expression a -> Maybe (DickinsonWarning a)
-checkExprDuplicates Var{}              = Nothing
-checkExprDuplicates Literal{}          = Nothing
-checkExprDuplicates StrChunk{}         = Nothing
-checkExprDuplicates (Interp _ es)      = foldMapAlternative checkExprDuplicates es
-checkExprDuplicates (MultiInterp _ es) = foldMapAlternative checkExprDuplicates es
-checkExprDuplicates (Concat _ es)      = foldMapAlternative checkExprDuplicates es
-checkExprDuplicates (Tuple _ es)       = foldMapAlternative checkExprDuplicates es
-checkExprDuplicates (Apply _ e e')     = checkExprDuplicates e <|> checkExprDuplicates e'
-checkExprDuplicates (Choice _ brs)     = checkNames (collectText $ toList brs)
-checkExprDuplicates (Let _ brs es)     = foldMapAlternative checkExprDuplicates (snd <$> brs) <|> checkExprDuplicates es
-checkExprDuplicates (Lambda _ _ _ e)   = checkExprDuplicates e
-checkExprDuplicates (Match _ e _ e')   = checkExprDuplicates e <|> checkExprDuplicates e'
-checkExprDuplicates (Flatten _ e)      = checkExprDuplicates e
-checkExprDuplicates (Annot _ e _)      = checkExprDuplicates e
-checkExprDuplicates Constructor{}      = Nothing
diff --git a/src/Language/Dickinson/File.hs b/src/Language/Dickinson/File.hs
--- a/src/Language/Dickinson/File.hs
+++ b/src/Language/Dickinson/File.hs
@@ -4,6 +4,7 @@
                                , evalFile
                                , checkFile
                                , validateFile
+                               , validateAmalgamate
                                , warnFile
                                , tcFile
                                , amalgamateRename
@@ -13,17 +14,19 @@
                                ) where
 
 import           Control.Applicative                  ((<|>))
+import           Control.Composition                  ((.*))
 import           Control.Exception                    (Exception)
 import           Control.Exception.Value
-import           Control.Monad                        ((<=<))
+import           Control.Monad                        (void, (<=<))
 import           Control.Monad.Except                 (ExceptT, MonadError, runExceptT)
 import           Control.Monad.IO.Class               (MonadIO)
 import           Control.Monad.State                  (MonadState, StateT, evalStateT)
 import qualified Data.ByteString.Lazy                 as BSL
-import           Data.Semigroup                       ((<>))
+import           Data.Functor                         (($>))
 import           Data.Text                            as T
 import           Language.Dickinson.Check
-import           Language.Dickinson.DuplicateCheck
+import           Language.Dickinson.Check.Duplicate
+import           Language.Dickinson.Check.Scope
 import           Language.Dickinson.Error
 import           Language.Dickinson.Eval
 import           Language.Dickinson.Lexer
@@ -31,7 +34,6 @@
 import           Language.Dickinson.Pipeline
 import           Language.Dickinson.Rename
 import           Language.Dickinson.Rename.Amalgamate
-import           Language.Dickinson.ScopeCheck
 import           Language.Dickinson.Type
 import           Language.Dickinson.TypeCheck
 import           System.Random                        (StdGen, newStdGen, randoms)
@@ -76,10 +78,13 @@
 
 -- | Check scoping and types
 validateFile :: [FilePath] -> FilePath -> IO ()
-validateFile is fp = do
+validateFile = void .* validateAmalgamate
+
+validateAmalgamate :: [FilePath] -> FilePath -> IO [Declaration AlexPosn]
+validateAmalgamate is fp = do
     d <- amalgamateRename is fp
     maybeThrowIO $ checkScope d
-    eitherThrowIO $ tyRun d
+    eitherThrowIO (tyRun d) $> d
 
 -- | Run some lints
 warnFile :: FilePath -> IO ()
@@ -94,7 +99,10 @@
 tcFile is = eitherThrowIO . tyRun <=< amalgamateRename is
 
 evalFile :: [FilePath] -> FilePath -> IO T.Text
-evalFile is = fmap eitherThrow . evalIO . (evalDickinsonAsMain <=< amalgamateRenameM is)
+evalFile is fp = (\g -> evalFileGen g is fp) =<< newStdGen
+
+evalFileGen :: StdGen -> [FilePath] -> FilePath -> IO T.Text
+evalFileGen g is = fmap eitherThrow . evalAllWithGen g . (evalDickinsonAsMain <=< amalgamateRenameM is)
 
 resolveFile :: [FilePath] -> FilePath -> IO [Declaration AlexPosn]
 resolveFile is = fmap eitherThrow . evalIO . (traverse resolveDeclarationM <=< amalgamateRenameM is)
diff --git a/src/Language/Dickinson/Lexer.x b/src/Language/Dickinson/Lexer.x
--- a/src/Language/Dickinson/Lexer.x
+++ b/src/Language/Dickinson/Lexer.x
@@ -1,5 +1,6 @@
 {
     {-# LANGUAGE DeriveAnyClass #-}
+    {-# LANGUAGE DeriveDataTypeable #-}
     {-# LANGUAGE DeriveGeneric #-}
     {-# LANGUAGE OverloadedStrings #-}
     {-# LANGUAGE StandaloneDeriving #-}
@@ -20,6 +21,7 @@
                                     ) where
 
 import Control.Arrow ((&&&))
+import Data.Data (Data)
 import Control.DeepSeq (NFData)
 import Control.Monad.Fail (MonadFail (..))
 import Data.Bifunctor (first)
@@ -297,6 +299,8 @@
 deriving instance NFData AlexPosn
 
 deriving instance Binary AlexPosn
+
+deriving instance Data AlexPosn
 
 data Token a = EOF { loc :: a }
              | TokIdent { loc :: a, ident :: Name a }
diff --git a/src/Language/Dickinson/Name.hs b/src/Language/Dickinson/Name.hs
--- a/src/Language/Dickinson/Name.hs
+++ b/src/Language/Dickinson/Name.hs
@@ -1,7 +1,8 @@
-{-# LANGUAGE DeriveAnyClass    #-}
-{-# LANGUAGE DeriveFunctor     #-}
-{-# LANGUAGE DeriveGeneric     #-}
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DeriveAnyClass     #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveFunctor      #-}
+{-# LANGUAGE DeriveGeneric      #-}
+{-# LANGUAGE OverloadedStrings  #-}
 
 module Language.Dickinson.Name ( TyName
                                , Name (..)
@@ -11,10 +12,10 @@
 
 import           Control.DeepSeq               (NFData (..))
 import           Data.Binary                   (Binary (..))
+import           Data.Data                     (Data)
 import           Data.Foldable                 (toList)
 import qualified Data.IntMap                   as IM
 import           Data.List.NonEmpty            (NonEmpty (..))
-import           Data.Semigroup                ((<>))
 import qualified Data.Text                     as T
 import           Data.Text.Prettyprint.Doc     (Pretty (pretty))
 import           Data.Text.Prettyprint.Doc.Ext (intercalate)
@@ -28,7 +29,7 @@
 data Name a = Name { name   :: NonEmpty T.Text
                    , unique :: !Unique
                    , loc    :: a
-                   } deriving (Functor, Generic, Binary, Show)
+                   } deriving (Functor, Generic, Binary, Show, Data)
 
 instance NFData a => NFData (Name a) where
     rnf (Name _ u x) = rnf x `seq` u `seq` ()
diff --git a/src/Language/Dickinson/Pipeline.hs b/src/Language/Dickinson/Pipeline.hs
--- a/src/Language/Dickinson/Pipeline.hs
+++ b/src/Language/Dickinson/Pipeline.hs
@@ -4,16 +4,16 @@
                                    , format
                                    ) where
 
-import           Control.Exception.Value       (eitherThrow)
-import           Control.Monad.Except          (MonadError)
-import           Control.Monad.State.Lazy      (MonadState)
-import qualified Data.ByteString.Lazy          as BSL
-import qualified Data.Text                     as T
-import           Data.Text.Prettyprint.Doc.Ext (prettyText)
+import           Control.Exception.Value        (eitherThrow)
+import           Control.Monad.Except           (MonadError)
+import           Control.Monad.State.Lazy       (MonadState)
+import qualified Data.ByteString.Lazy           as BSL
+import qualified Data.Text                      as T
+import           Data.Text.Prettyprint.Doc.Ext  (prettyText)
+import           Language.Dickinson.Check.Scope
 import           Language.Dickinson.Error
 import           Language.Dickinson.Eval
 import           Language.Dickinson.Parser
-import           Language.Dickinson.ScopeCheck
 import           Language.Dickinson.Type
 import           Language.Dickinson.TypeCheck
 
diff --git a/src/Language/Dickinson/Rename/Amalgamate.hs b/src/Language/Dickinson/Rename/Amalgamate.hs
--- a/src/Language/Dickinson/Rename/Amalgamate.hs
+++ b/src/Language/Dickinson/Rename/Amalgamate.hs
@@ -4,11 +4,13 @@
                                             , fileDecls
                                             ) where
 
-import           Control.Monad              ((<=<))
-import           Control.Monad.Except       (MonadError)
-import           Control.Monad.IO.Class     (MonadIO)
-import           Control.Monad.State        (MonadState)
-import           Data.Semigroup             ((<>))
+import           Control.Monad                    ((<=<))
+import           Control.Monad.Except             (MonadError)
+import           Control.Monad.IO.Class           (MonadIO)
+import           Control.Monad.State              (MonadState)
+import           Data.Functor                     (($>))
+import           Data.Semigroup                   ((<>))
+import           Language.Dickinson.Check.Pattern
 import           Language.Dickinson.Error
 import           Language.Dickinson.Lexer
 import           Language.Dickinson.Lib.Get
@@ -27,7 +29,7 @@
             => [FilePath] -- ^ Includes
             -> Dickinson AlexPosn
             -> m [Declaration AlexPosn]
-amalgamateM _ (Dickinson [] ds)    = pure ds
+amalgamateM _ (Dickinson [] ds)    = maybeThrow (checkPatternDecl ds) $> ds
 amalgamateM is (Dickinson imps ds) = do
     ids <- traverse (withImportM is) imps
     pure (concat ids <> ds)
diff --git a/src/Language/Dickinson/ScopeCheck.hs b/src/Language/Dickinson/ScopeCheck.hs
deleted file mode 100644
--- a/src/Language/Dickinson/ScopeCheck.hs
+++ /dev/null
@@ -1,98 +0,0 @@
-{-# LANGUAGE FlexibleContexts #-}
-
-module Language.Dickinson.ScopeCheck ( checkScope
-                                     , checkScopeExpr
-                                     , checkScopeDecl
-                                     ) where
-
-import           Control.Applicative              (Alternative, (<|>))
-import           Control.Monad.Except             (MonadError)
-import           Control.Monad.State              (State, evalState, get, modify)
-import           Data.Foldable                    (asum, traverse_)
-import qualified Data.IntSet                      as IS
-import           Language.Dickinson.Check.Pattern
-import           Language.Dickinson.Error
-import           Language.Dickinson.Name
-import           Language.Dickinson.Type
-import           Language.Dickinson.Unique
-
-type CheckM = State IS.IntSet
-
-runCheckM :: CheckM a -> a
-runCheckM = flip evalState IS.empty
-
-insertName :: Name a -> CheckM ()
-insertName (Name _ (Unique i) _) = modify (IS.insert i)
-
-deleteName :: Name a -> CheckM ()
-deleteName (Name _ (Unique i) _) = modify (IS.delete i)
-
--- | Checks that there are not any identifiers that aren't in scope; needs to run
--- after the renamer
-checkScope :: [Declaration a] -> Maybe (DickinsonError a)
-checkScope = runCheckM . checkDickinson
-
-checkScopeExpr :: MonadError (DickinsonError a) m => Expression a -> m ()
-checkScopeExpr = maybeThrow . runCheckM . checkExpr
-
-checkScopeDecl :: MonadError (DickinsonError a) m => Declaration a -> m ()
-checkScopeDecl = maybeThrow . runCheckM . checkDecl
-
-checkDickinson :: [Declaration a] -> CheckM (Maybe (DickinsonError a))
-checkDickinson d = traverse_ insDecl d *> mapSumM checkDecl d
-
-insDecl :: Declaration a -> CheckM ()
-insDecl (Define _ n _)   = insertName n
-insDecl (TyDecl _ n tys) = insertName n *> traverse_ insertName tys
-
-checkDecl :: Declaration a -> CheckM (Maybe (DickinsonError a))
-checkDecl (Define _ _ e) = checkExpr e
-checkDecl TyDecl{}       = pure Nothing
-
-checkType :: DickinsonTy a -> CheckM (Maybe (DickinsonError a))
-checkType TyText{}         = pure Nothing
-checkType (TyFun _ ty ty') = (<|>) <$> checkType ty <*> checkType ty'
-checkType (TyTuple _ tys)  = mapSumM checkType tys
-checkType (TyNamed l n@(Name _ (Unique k) _)) = do
-    b <- get
-    if k `IS.member` b
-        then pure Nothing
-        else pure $ Just (UnfoundType l n)
-
-checkExpr :: Expression a -> CheckM (Maybe (DickinsonError a))
-checkExpr Literal{}      = pure Nothing
-checkExpr StrChunk{}     = pure Nothing
-checkExpr (Apply _ e e') = (<|>) <$> checkExpr e <*> checkExpr e'
-checkExpr (Interp _ es)  = mapSumM checkExpr es
-checkExpr (MultiInterp _ es)  = mapSumM checkExpr es
-checkExpr (Choice _ brs) = mapSumM checkExpr (snd <$> brs)
-checkExpr (Concat _ es)  = mapSumM checkExpr es
-checkExpr (Tuple _ es)   = mapSumM checkExpr es
-checkExpr (Flatten _ e)  = checkExpr e
-checkExpr (Annot _ e ty) = (<|>) <$> checkExpr e <*> checkType ty
-checkExpr (Lambda _ n _ e) = do
-    insertName n
-    checkExpr e <* deleteName n
-checkExpr (Constructor _ n@(Name _ (Unique i) l)) = do
-    b <- get
-    if i `IS.member` b
-        then pure Nothing
-        else pure $ Just (UnfoundConstructor l n)
-checkExpr (Var _ n@(Name _ (Unique i) l)) = do
-    b <- get
-    if i `IS.member` b
-        then pure Nothing
-        else pure $ Just (UnfoundName l n)
-checkExpr (Let _ bs e) = do
-    let ns = fst <$> bs
-    traverse_ insertName ns
-    (<|>) <$> checkExpr e <*> mapSumM checkExpr (snd <$> bs)
-        <* traverse_ deleteName ns
-checkExpr (Match _ e p e') =
-    ((<|>) <$> checkExpr e) <*> do
-        let ns = traversePattern p
-        traverse_ insertName ns
-        checkExpr e' <* traverse_ deleteName ns
-
-mapSumM :: (Traversable t, Alternative f, Applicative m) => (a -> m (f b)) -> t a -> m (f b)
-mapSumM = (fmap asum .) . traverse
diff --git a/src/Language/Dickinson/Type.hs b/src/Language/Dickinson/Type.hs
--- a/src/Language/Dickinson/Type.hs
+++ b/src/Language/Dickinson/Type.hs
@@ -1,7 +1,8 @@
-{-# LANGUAGE DeriveAnyClass    #-}
-{-# LANGUAGE DeriveFunctor     #-}
-{-# LANGUAGE DeriveGeneric     #-}
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DeriveAnyClass     #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveFunctor      #-}
+{-# LANGUAGE DeriveGeneric      #-}
+{-# LANGUAGE OverloadedStrings  #-}
 
 module Language.Dickinson.Type ( Dickinson (..)
                                , Declaration (..)
@@ -9,10 +10,13 @@
                                , Expression (..)
                                , Pattern (..)
                                , DickinsonTy (..)
+                               -- * Accesors
+                               , defExprM
                                ) where
 
 import           Control.DeepSeq                    (NFData)
 import           Data.Binary                        (Binary)
+import           Data.Data                          (Data)
 import           Data.Foldable                      (toList)
 import           Data.List.NonEmpty                 (NonEmpty (..))
 import qualified Data.List.NonEmpty                 as NE
@@ -38,7 +42,7 @@
                             , tyName  :: Name a
                             , tyCons  :: NonEmpty (TyName a)
                             }
-                   deriving (Generic, NFData, Binary, Functor, Show)
+                   deriving (Generic, NFData, Binary, Functor, Show, Data)
 
 data Import a = Import { importAnn :: a
                        , declMod   :: Name a
@@ -49,7 +53,7 @@
                | PatternTuple a (NonEmpty (Pattern a))
                | PatternCons a (TyName a)
                | Wildcard a
-               deriving (Generic, NFData, Binary, Functor, Show)
+               deriving (Generic, NFData, Binary, Functor, Show, Data)
 
 data Expression a = Literal { exprAnn :: a, litText :: T.Text }
                   | StrChunk { exprAnn :: a, chunkText :: T.Text }
@@ -85,14 +89,14 @@
                           , exprTy  :: DickinsonTy a
                           }
                   | Constructor { exprAnn :: a, constructorName :: TyName a }
-                  deriving (Generic, NFData, Binary, Functor, Show)
+                  deriving (Generic, NFData, Binary, Functor, Show, Data)
                   -- TODO: builtins?
 
 data DickinsonTy a = TyText a
                    | TyFun a (DickinsonTy a) (DickinsonTy a)
                    | TyTuple a (NonEmpty (DickinsonTy a))
                    | TyNamed a (Name a)
-                   deriving (Generic, NFData, Binary, Show, Functor)
+                   deriving (Generic, NFData, Binary, Show, Functor, Data)
 
 instance Eq (DickinsonTy a) where
     (==) TyText{} TyText{}                     = True
@@ -126,7 +130,7 @@
 
 prettyMultiInterp :: [Expression a] -> Doc b
 prettyMultiInterp = mconcat . fmap prettyChunk
-    where prettyChunk (StrChunk _ t) = textHard t -- FIXME: convert "\n" to hardline...
+    where prettyChunk (StrChunk _ t) = textHard t
           prettyChunk e              = "${" <> pretty e <> "}"
 
 textHard :: T.Text -> Doc a
@@ -173,3 +177,7 @@
     pretty (TyFun _ t t') = parens ("⟶" <+> pretty t <+> pretty t')
     pretty (TyTuple _ ts) = tupled (toList (pretty <$> ts))
     pretty (TyNamed _ n)  = pretty n
+
+defExprM :: Declaration a -> Maybe (Expression a)
+defExprM (Define _ _ e) = Just e
+defExprM TyDecl{}       = Nothing
diff --git a/src/Language/Dickinson/Unique.hs b/src/Language/Dickinson/Unique.hs
--- a/src/Language/Dickinson/Unique.hs
+++ b/src/Language/Dickinson/Unique.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE DeriveDataTypeable         #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 
 module Language.Dickinson.Unique ( Unique (..)
@@ -7,11 +8,12 @@
 
 import           Control.DeepSeq           (NFData)
 import           Data.Binary               (Binary (..))
+import           Data.Data                 (Data)
 import           Data.Text.Prettyprint.Doc (Pretty)
 
 -- | For interning identifiers.
 newtype Unique = Unique { unUnique :: Int }
-    deriving (Eq, Ord, Pretty, NFData, Binary, Show)
+    deriving (Eq, Ord, Pretty, NFData, Binary, Show, Data)
 
 -- | Dummy unique for sake of testing
 dummyUnique :: Unique
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -2,27 +2,29 @@
 
 module Main (main) where
 
-import           Control.Exception.Value           (eitherThrow)
-import qualified Data.ByteString.Lazy              as BSL
-import           Data.Either                       (isRight)
-import           Data.List.NonEmpty                (NonEmpty (..))
-import           Data.Maybe                        (isJust, isNothing)
+import           Control.Exception.Value            (eitherThrow)
+import qualified Data.ByteString.Lazy               as BSL
+import           Data.Either                        (isRight)
+import           Data.List.NonEmpty                 (NonEmpty (..))
+import           Data.Maybe                         (isJust, isNothing)
 import           Eval
 import           Golden
 import           Language.Dickinson.Check
+import           Language.Dickinson.Check.Duplicate
 import           Language.Dickinson.Check.Internal
-import           Language.Dickinson.DuplicateCheck
+import           Language.Dickinson.Check.Pattern
+import           Language.Dickinson.Check.Scope
 import           Language.Dickinson.File
 import           Language.Dickinson.Import
 import           Language.Dickinson.Lexer
 import           Language.Dickinson.Name
 import           Language.Dickinson.Parser
 import           Language.Dickinson.Rename
-import           Language.Dickinson.ScopeCheck
 import           Language.Dickinson.Type
 import           Language.Dickinson.Unique
 import           Test.Tasty
 import           Test.Tasty.HUnit
+import           TH
 import           TypeCheck
 
 main :: IO ()
@@ -65,6 +67,8 @@
         , parseNoError "test/data/multiQuoteify.dck"
         , findPath
         , sanityCheckTest "test/data/adt.dck"
+        , detectSuspiciousPattern "test/error/badMatch.dck"
+        , thTests
         ]
 
 findPath :: TestTree
@@ -84,6 +88,11 @@
 detectDuplicate fp = testCase ("Detects duplicate name (" ++ fp ++ ")") $ do
     (Dickinson _ parsed) <- readNoFail fp
     assertBool fp $ isJust (checkMultiple parsed)
+
+detectSuspiciousPattern :: FilePath -> TestTree
+detectSuspiciousPattern fp = testCase fp $ do
+    (Dickinson _ parsed) <- readNoFail fp
+    assertBool fp $ isJust (checkPatternDecl parsed)
 
 detectScopeError :: FilePath -> TestTree
 detectScopeError fp = testCase "Finds scoping error" $ do
diff --git a/test/TH.hs b/test/TH.hs
new file mode 100644
--- /dev/null
+++ b/test/TH.hs
@@ -0,0 +1,19 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell   #-}
+
+module TH ( thTests
+          ) where
+
+import           Language.Dickinson
+import           Language.Dickinson.TH
+import           Test.Tasty
+import           Test.Tasty.HUnit
+
+thDecl :: [Declaration AlexPosn]
+thDecl = $(dickinson [] "test/eval/context.dck")
+
+thTests :: TestTree
+thTests =
+    testCase "Returns result" $ do
+        res <- run thDecl
+        res @?= "woman"
diff --git a/test/error/badMatch.dck b/test/error/badMatch.dck
new file mode 100644
--- /dev/null
+++ b/test/error/badMatch.dck
@@ -0,0 +1,5 @@
+%-
+
+(:def main
+  (:match ("Hello", "World") (t, t)
+    t))
