diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # dickinson
 
+## 1.4.1.0
+
+  * Export `dckPath` and `defaultLibPath`
+  * Add `pipelineBSL`, `validateBSL`, `patternExhaustivenessBSL`, and `warnBSL`
+
 ## 1.4.0.0
 
   * Add `:bind` construct, like `:let` but stricter
diff --git a/examples/divinationBot.dck b/examples/divinationBot.dck
--- a/examples/divinationBot.dck
+++ b/examples/divinationBot.dck
@@ -1,9 +1,9 @@
 %-
 
-; @botmancy from twitter but more sensical\
+; @botmancy from twitter but more sensical
 ; http://phrontistery.info/divine.html
 ; https://en.wikipedia.org/wiki/Methods_of_divination
-; bones, topo-, xylo-, crystallo-
+; crystallo-
 tydecl means = Fish
              | Stars
              | Plants
@@ -32,6 +32,7 @@
              | Teeth
              | Bones
              | Land
+             | Nails
 
 (:def prefix
   (:lambda x means
@@ -63,7 +64,8 @@
       [Small "micro"]
       [Teeth "odonto"]
       [Bones "osteo"]
-      [Land "topo"])))
+      [Land "topo"]
+      [Nails "onycho"])))
 
 (:def english
   (:lambda x means
@@ -92,10 +94,11 @@
       [Rock "rocks"]
       [Large "large objects"]
       [Pearl "pearls"]
-      [Small "micro"]
+      [Small "small objects"]
       [Teeth "teeth"]
       [Bones "bones"]
-      [Land "landforms"])))
+      [Land "landforms"]
+      [Nails "fingernails"])))
 
 (:def mod
   (:branch
@@ -112,7 +115,7 @@
     (| 1.0 "mancy")
     (| 0.065 "scopy")
     (| 0.03 "spication")
-    (| 0.015 "logy")))
+    (| 0.06 "logy")))
 
 (:def main
   (:bind
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:            1.4.0.0
+version:            1.4.1.0
 license:            BSD3
 license-file:       LICENSE
 copyright:          Copyright: (c) 2020 Vanessa McHale
@@ -19,6 +19,7 @@
     lib/*.dck
     prelude/*.dck
     lib/grammar/*.dck
+    lib/fun/*.dck
 
 extra-source-files:
     test/data/*.pretty
@@ -141,7 +142,7 @@
         deepseq -any,
         microlens -any,
         microlens-mtl -any,
-        composition-prelude >=1.1.0.1,
+        composition-prelude >=2.0.0.0,
         binary >=0.8.4.0,
         filepath -any,
         directory -any,
diff --git a/lib/fun/bool.dck b/lib/fun/bool.dck
new file mode 100644
--- /dev/null
+++ b/lib/fun/bool.dck
@@ -0,0 +1,25 @@
+%-
+
+tydecl bool = True | False
+
+(:def and
+  (:lambda x bool
+    (:lambda y bool
+      (:match (x, y)
+        [(True, True) True]
+        [_ False]
+        ))))
+
+(:def or
+  (:lambda x bool
+    (:lambda y bool
+      (:match (x, y)
+        [(False, False) False]
+        [_ True]
+        ))))
+
+(:def not
+  (:lambda x bool
+    (:match x
+      [True False]
+      [False True])))
diff --git a/lib/noun.dck b/lib/noun.dck
--- a/lib/noun.dck
+++ b/lib/noun.dck
@@ -36,6 +36,7 @@
       (| "head")
       (| "punnet")
       (| "sitophobia")
+      (| "acedia")
       (| "tail")
       (| "joke")
       (| "autocracy")
diff --git a/man/emd.1 b/man/emd.1
--- a/man/emd.1
+++ b/man/emd.1
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pandoc 2.10.1
+.\" Automatically generated by Pandoc 2.11.1
 .\"
 .TH "emd (1)" "" "" "" ""
 .hy
diff --git a/public/Language/Dickinson.hs b/public/Language/Dickinson.hs
--- a/public/Language/Dickinson.hs
+++ b/public/Language/Dickinson.hs
@@ -20,14 +20,24 @@
                           , renameExpressionM
                           -- * Imports
                           , resolveImport
+                          -- * Evaluation
+                          , pipelineBSL
+                          , validateBSL
+                          , patternExhaustivenessBSL
+                          , warnBSL
+                          -- * Path
+                          , defaultLibPath
+                          , dckPath
                           -- * Version info
                           , dickinsonVersion
                           , dickinsonVersionString
                           ) where
 
 import qualified Data.Version              as V
+import           Language.Dickinson.File
 import           Language.Dickinson.Import
 import           Language.Dickinson.Lexer
+import           Language.Dickinson.Lib
 import           Language.Dickinson.Name
 import           Language.Dickinson.Parser
 import           Language.Dickinson.Rename
diff --git a/run/Format.hs b/run/Format.hs
--- a/run/Format.hs
+++ b/run/Format.hs
@@ -2,11 +2,17 @@
               , fmtInplace
               ) where
 
-import           Control.Monad               ((<=<))
-import qualified Data.ByteString             as BS
-import qualified Data.ByteString.Lazy        as BSL
-import           Data.Text.IO                as TIO
-import           Language.Dickinson.Pipeline
+import           Control.Exception.Value       (eitherThrow)
+import           Control.Monad                 ((<=<))
+import qualified Data.ByteString               as BS
+import qualified Data.ByteString.Lazy          as BSL
+import qualified Data.Text                     as T
+import           Data.Text.IO                  as TIO
+import           Data.Text.Prettyprint.Doc.Ext (prettyText)
+import           Language.Dickinson.Parser
+
+format :: BSL.ByteString -> T.Text
+format = prettyText . eitherThrow . parse
 
 fmtFile :: FilePath -> IO ()
 fmtFile = TIO.putStrLn . format <=< BSL.readFile
diff --git a/run/REPL.hs b/run/REPL.hs
--- a/run/REPL.hs
+++ b/run/REPL.hs
@@ -44,7 +44,8 @@
 import           System.FilePath                ((</>))
 import           System.Random                  (newStdGen, randoms)
 
-dickinsonRepl :: [FilePath] -> IO ()
+dickinsonRepl :: [FilePath] -- ^ Files to be loaded beforehand
+              -> IO ()
 dickinsonRepl fps = runRepl (traverse_ loadFile fps *> loop)
 
 type Repl a = InputT (StateT (EvalSt a) IO)
@@ -132,6 +133,9 @@
 
 listNames :: Repl AlexPosn ()
 listNames = liftIO . traverse_ TIO.putStrLn =<< names
+
+namesState :: StateT (EvalSt a) IO [T.Text]
+namesState = gets (M.keys . topLevel)
 
 names :: Repl AlexPosn [T.Text]
 names = appendBuiltins <$> lift namesState
diff --git a/run/REPL/Completions.hs b/run/REPL/Completions.hs
--- a/run/REPL/Completions.hs
+++ b/run/REPL/Completions.hs
@@ -1,7 +1,6 @@
 {-# LANGUAGE TupleSections #-}
 
 module REPL.Completions ( emdCompletions
-                        , namesState
                         ) where
 
 import           Control.Monad.State      (StateT, gets)
@@ -10,9 +9,6 @@
 import qualified Data.Text                as T
 import           Language.Dickinson.Eval
 import           System.Console.Haskeline (Completion, CompletionFunc, simpleCompletion)
-
-namesState :: StateT (EvalSt a) IO [T.Text]
-namesState = gets (M.keys . topLevel)
 
 namesStr :: StateT (EvalSt a) IO [String]
 namesStr = gets (fmap T.unpack . M.keys . topLevel)
diff --git a/src/Language/Dickinson/Check/Exhaustive.hs b/src/Language/Dickinson/Check/Exhaustive.hs
--- a/src/Language/Dickinson/Check/Exhaustive.hs
+++ b/src/Language/Dickinson/Check/Exhaustive.hs
@@ -10,6 +10,8 @@
 import           Language.Dickinson.Pattern.Useless
 import           Language.Dickinson.Type
 
+-- | Check that there are no useless pattern clauses and check that the pattern
+-- matches are exhaustive
 checkExhaustive :: [Declaration a] -> Maybe (DickinsonWarning a)
 checkExhaustive ds = runPatternM (checkDeclsM ds)
 
diff --git a/src/Language/Dickinson/Check/Internal.hs b/src/Language/Dickinson/Check/Internal.hs
--- a/src/Language/Dickinson/Check/Internal.hs
+++ b/src/Language/Dickinson/Check/Internal.hs
@@ -31,6 +31,7 @@
 
 -- TODO: see http://hackage.haskell.org/package/uniplate-1.6.12/docs/Data-Generics-Uniplate-Operations.html
 -- TODO: recursion schemes? would need benchmark...
+-- | I exported this so I could benchmark it
 maxUniqueDeclaration :: Declaration a -> Int
 maxUniqueDeclaration (Define _ (Name _ (Unique i) _) e)   = max i (maxUniqueExpression e)
 maxUniqueDeclaration (TyDecl _ (Name _ (Unique i) _) tns) =
@@ -69,6 +70,12 @@
         , maximum (fmap (maxUniqueExpression . snd) brs)
         ]
 maxUniqueExpression (Let _ bs e) =
+    maximum
+        [ maxUniqueExpression e
+        , maximum (maxUniqueExpression . snd <$> bs)
+        , maximum (unUnique . unique . fst <$> bs)
+        ]
+maxUniqueExpression (Bind _ bs e) =
     maximum
         [ maxUniqueExpression e
         , maximum (maxUniqueExpression . snd <$> bs)
diff --git a/src/Language/Dickinson/Check/Scope.hs b/src/Language/Dickinson/Check/Scope.hs
--- a/src/Language/Dickinson/Check/Scope.hs
+++ b/src/Language/Dickinson/Check/Scope.hs
@@ -10,6 +10,7 @@
 import           Control.Monad.State.Strict       (State, evalState, get, modify)
 import           Data.Foldable                    (traverse_)
 import qualified Data.IntSet                      as IS
+import           Data.List.NonEmpty               (NonEmpty)
 import           Language.Dickinson.Check.Common
 import           Language.Dickinson.Check.Pattern
 import           Language.Dickinson.Error
@@ -85,20 +86,22 @@
     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 (Bind _ bs e) = do
-    let ns = fst <$> bs
-    traverse_ insertName ns
-    (<|>) <$> checkExpr e <*> mapSumM checkExpr (snd <$> bs)
-        <* traverse_ deleteName ns
+checkExpr (Let _ bs e) = checkLet bs e
+checkExpr (Bind _ bs e) = checkLet bs e
 checkExpr (Match _ e brs) =
     ((<|>) <$> checkExpr e) <*> mapSumM checkPair brs
 checkExpr (Random l n) =
     checkType (TyNamed l n)
+
+-- @:let@s and @:bind@s are the same here
+checkLet :: NonEmpty (Name a, Expression a)
+         -> Expression a
+         -> CheckM (Maybe (DickinsonError a))
+checkLet bs e = do
+    let ns = fst <$> bs
+    traverse_ insertName ns
+    (<|>) <$> checkExpr e <*> mapSumM checkExpr (snd <$> bs)
+        <* traverse_ deleteName ns
 
 checkPair :: (Pattern a, Expression a) -> CheckM (Maybe (DickinsonError a))
 checkPair (p, e) = do
diff --git a/src/Language/Dickinson/Eval.hs b/src/Language/Dickinson/Eval.hs
--- a/src/Language/Dickinson/Eval.hs
+++ b/src/Language/Dickinson/Eval.hs
@@ -51,10 +51,8 @@
     , topLevel      :: M.Map T.Text Unique
     -- Used in the REPL, for instance
     , lexerState    :: AlexUserState
-    -- For error messages
-    , tyEnv         :: TyEnv a
-    -- For :pick expressions
-    , constructors  :: IM.IntMap (NonEmpty (TyName a))
+    , tyEnv         :: TyEnv a -- ^ For error messages
+    , constructors  :: IM.IntMap (NonEmpty (TyName a)) -- ^ This is used for @:pick@ expressions.
     }
 
 instance HasLexerState (EvalSt a) where
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,18 +4,22 @@
                                , evalFile
                                , checkFile
                                , validateFile
+                               , validateBSL
                                , validateAmalgamate
                                , warnFile
+                               , warnBSL
                                , patternExhaustivenessFile
+                               , patternExhaustivenessBSL
                                , tcFile
                                , amalgamateRename
                                , amalgamateRenameM
                                , pipeline
+                               , pipelineBSL
                                , resolveFile
                                ) where
 
 import           Control.Applicative                  ((<|>))
-import           Control.Composition                  ((.*))
+import           Control.Composition                  ((.*), (.**), (<=*<))
 import           Control.Exception                    (Exception)
 import           Control.Exception.Value
 import           Control.Monad                        (void, (<=<))
@@ -69,11 +73,24 @@
                   -> m [Declaration AlexPosn]
 amalgamateRenameM is = (balanceMax *>) . renameDeclarationsM <=< fileDecls is
 
+amalgamateRenameInpM :: (HasRenames s, HasLexerState s, MonadIO m, MonadError (DickinsonError AlexPosn) m, MonadState s m)
+                     => [FilePath]
+                     -> FilePath -- ^ For error reporting
+                     -> BSL.ByteString
+                     -> m [Declaration AlexPosn]
+amalgamateRenameInpM is = (balanceMax *>) . renameDeclarationsM <=*< bslDecls is
+
 amalgamateRename :: [FilePath]
                  -> FilePath
                  -> IO [Declaration AlexPosn]
 amalgamateRename is fp = flip evalStateT initAmalgamateSt $ fmap eitherThrow $ runExceptT $ amalgamateRenameM is fp
 
+amalgamateRenameBSL :: [FilePath]
+                    -> FilePath -- ^ For error reporting
+                    -> BSL.ByteString
+                    -> IO [Declaration AlexPosn]
+amalgamateRenameBSL is fp bsl = flip evalStateT initAmalgamateSt $ fmap eitherThrow $ runExceptT $ amalgamateRenameInpM is fp bsl
+
 -- | Check scoping
 checkFile :: [FilePath] -> FilePath -> IO ()
 checkFile = ioChecker checkScope
@@ -82,17 +99,33 @@
 validateFile :: [FilePath] -> FilePath -> IO ()
 validateFile = void .* validateAmalgamate
 
+-- | Check scoping and types
+--
+-- @since 1.4.1.0
+validateBSL :: [FilePath] -> FilePath -> BSL.ByteString -> IO ()
+validateBSL = void .** validateAmalgamateBSL
+
 validateAmalgamate :: [FilePath] -> FilePath -> IO [Declaration AlexPosn]
 validateAmalgamate is fp = do
     d <- amalgamateRename is fp
     maybeThrowIO $ checkScope d
     eitherThrowIO (tyRun d) $> d
 
--- | Run some lints
+validateAmalgamateBSL :: [FilePath] -> FilePath -> BSL.ByteString -> IO [Declaration AlexPosn]
+validateAmalgamateBSL is fp bsl = do
+    d <- amalgamateRenameBSL is fp bsl
+    maybeThrowIO $ checkScope d
+    eitherThrowIO (tyRun d) $> d
+
 warnFile :: FilePath -> IO ()
-warnFile = maybeThrowIO . (\x -> checkDuplicates x <|> checkMultiple x) . modDefs
+warnFile = warnBSL <=< BSL.readFile
+
+-- | Run some lints
+--
+-- @since 1.4.2.0
+warnBSL :: BSL.ByteString -> IO ()
+warnBSL = maybeThrowIO . (\x -> checkDuplicates x <|> checkMultiple x) . modDefs
     <=< eitherThrowIO . parse
-    <=< BSL.readFile
 
 ioChecker :: Exception e => ([Declaration AlexPosn] -> Maybe e) -> [FilePath] -> FilePath -> IO ()
 ioChecker checker is = maybeThrowIO . checker <=< amalgamateRename is
@@ -100,9 +133,18 @@
 tcFile :: [FilePath] -> FilePath -> IO ()
 tcFile is = eitherThrowIO . tyRun <=< amalgamateRename is
 
-patternExhaustivenessFile :: [FilePath] -> FilePath -> IO ()
+patternExhaustivenessFile :: [FilePath] -- ^ Includes
+                          -> FilePath
+                          -> IO ()
 patternExhaustivenessFile is = maybeThrowIO . checkExhaustive <=< amalgamateRename is
 
+-- | @since 1.4.1.0
+patternExhaustivenessBSL :: [FilePath] -- ^ Includes
+                         -> FilePath -- ^ Source file (for error reporting)
+                         -> BSL.ByteString
+                         -> IO ()
+patternExhaustivenessBSL is = maybeThrowIO . checkExhaustive <=*< amalgamateRenameBSL is
+
 evalFile :: [FilePath] -> FilePath -> IO T.Text
 evalFile is fp = (\g -> evalFileGen g is fp) =<< newStdGen
 
@@ -115,3 +157,11 @@
 pipeline :: [FilePath] -> FilePath -> IO T.Text
 pipeline is fp = fmap eitherThrow $ evalIO $
     checkEvalM =<< amalgamateRenameM is fp
+
+-- | @since 1.4.1.0
+pipelineBSL :: [FilePath]
+            -> FilePath -- ^ For error reporting
+            -> BSL.ByteString
+            -> IO T.Text
+pipelineBSL is fp bsl = fmap eitherThrow $ evalIO $
+    checkEvalM =<< amalgamateRenameInpM is fp bsl
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
@@ -207,6 +207,7 @@
 
 doSym t act = tok (\p _ -> TokSym p t <$ act)
 
+-- | Used to track nesting level for string interpolations
 data ScdState = InStr
               | InMultiStr
 
diff --git a/src/Language/Dickinson/Lib.hs b/src/Language/Dickinson/Lib.hs
--- a/src/Language/Dickinson/Lib.hs
+++ b/src/Language/Dickinson/Lib.hs
@@ -7,6 +7,7 @@
 import           System.Environment       (lookupEnv)
 import           System.FilePath          ((</>))
 
+-- | Parsed @DCK_PATH@ environment variable
 dckPath :: IO [FilePath]
 dckPath = maybe [] splitEnv <$> lookupEnv "DCK_PATH"
 
diff --git a/src/Language/Dickinson/Lib/Get.hs b/src/Language/Dickinson/Lib/Get.hs
--- a/src/Language/Dickinson/Lib/Get.hs
+++ b/src/Language/Dickinson/Lib/Get.hs
@@ -2,6 +2,7 @@
 
 module Language.Dickinson.Lib.Get ( parseImportM
                                   , parseFpM
+                                  , parseBSLM
                                   ) where
 
 import           Control.Composition       ((.*))
@@ -43,17 +44,31 @@
         Just fp -> parseFp fp lSt
         Nothing -> throwError $ ModuleNotFound l n
 
+parseBSL :: (MonadError (DickinsonError AlexPosn) m)
+         => FilePath
+         -> BSL.ByteString
+         -> AlexUserState
+         -> m (AlexUserState, Dickinson AlexPosn)
+parseBSL fp bsl lSt =
+    case parseWithCtx bsl lSt of
+        Right x  -> pure x
+        Left err -> throwError (ParseErr fp err)
+
 parseFp :: (MonadError (DickinsonError AlexPosn) m, MonadIO m)
         => FilePath -- ^ Source file
         -> AlexUserState -- ^ Lexer state
         -> m (AlexUserState, Dickinson AlexPosn)
 parseFp fp lSt = do
     bsl <- liftIO $ BSL.readFile fp
-    case parseWithCtx bsl lSt of
-        Right x  -> pure x
-        Left err -> throwError (ParseErr fp err)
+    parseBSL fp bsl lSt
 
 parseFpM :: (HasLexerState s, MonadState s m, MonadError (DickinsonError AlexPosn) m, MonadIO m)
         => FilePath -- ^ Source file
         -> m (Dickinson AlexPosn)
 parseFpM fp = liftLexerState (parseFp fp)
+
+parseBSLM :: (HasLexerState s, MonadState s m, MonadError (DickinsonError AlexPosn) m)
+        => FilePath -- ^ Source file name (for error reporting)
+        -> BSL.ByteString
+        -> m (Dickinson AlexPosn)
+parseBSLM fp bsl = liftLexerState (parseBSL fp bsl)
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
@@ -1,20 +1,15 @@
 {-# LANGUAGE FlexibleContexts #-}
 
 module Language.Dickinson.Pipeline ( checkEvalM
-                                   , format
                                    , validateDecl
                                    ) 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           Language.Dickinson.Check.Scope
 import           Language.Dickinson.Error
 import           Language.Dickinson.Eval
-import           Language.Dickinson.Parser
 import           Language.Dickinson.Type
 import           Language.Dickinson.TypeCheck
 
@@ -23,9 +18,6 @@
 checkEvalM ds =
     validateDecl ds *>
     evalDickinsonAsMain ds
-
-format :: BSL.ByteString -> T.Text
-format = prettyText . eitherThrow . parse
 
 -- the 'do'-notation is faster than using *>
 validateDecl :: (HasTyEnv s, MonadState (s a) m, MonadError (DickinsonError a) m) => [Declaration a] -> m ()
diff --git a/src/Language/Dickinson/Rename.hs b/src/Language/Dickinson/Rename.hs
--- a/src/Language/Dickinson/Rename.hs
+++ b/src/Language/Dickinson/Rename.hs
@@ -19,7 +19,6 @@
 
 import           Control.Composition           (thread)
 import           Control.Monad                 (forM, (<=<))
-import           Control.Monad.Ext             (zipWithM)
 import           Control.Monad.State           (MonadState, State, runState)
 import           Data.Bifunctor                (second)
 import           Data.Binary                   (Binary)
@@ -173,17 +172,24 @@
         (modP, p') <- renamePatternM p
         (p' ,) <$> withRenames modP (renameExpressionM e')
     pure $ Match l preE brs'
-renameExpressionM (Bind p bs e) = do
-    newBs <- traverse withName (fst <$> bs)
-    let localRenames = snd <$> newBs
-        newBinds = thread localRenames
-        newNames = fst <$> newBs
-        preNewBound = snd <$> bs
-    newBound <-
-        traverse renameExpressionM preNewBound
-    withRenames newBinds $
-        Bind p (NE.zip newNames newBound) <$> renameExpressionM e
-renameExpressionM (Let p bs e) = do
+renameExpressionM (Bind p bs e) = renameLet Bind p bs e
+renameExpressionM (Let p bs e) = renameLet Let p bs e
+renameExpressionM (Flatten l e) =
+    Flatten l <$> renameExpressionM e
+renameExpressionM (Annot l e ty) =
+    Annot l <$> renameExpressionM e <*> pure ty
+renameExpressionM c@Constructor{} = pure c
+renameExpressionM c@BuiltinFn{} = pure c
+renameExpressionM c@Random{} = pure c
+
+-- since bind/let are the same at this stage
+renameLet :: (MonadState s m, HasRenames s)
+          => (a -> NE.NonEmpty (Name a, Expression a) -> Expression a -> Expression a)
+          -> a
+          -> NE.NonEmpty (Name a, Expression a)
+          -> Expression a
+          -> m (Expression a)
+renameLet constructor p bs e = do
     newBs <- traverse withName (fst <$> bs)
     let localRenames = snd <$> newBs
         newBinds = thread localRenames
@@ -192,11 +198,4 @@
     newBound <-
         traverse renameExpressionM preNewBound
     withRenames newBinds $
-        Let p (NE.zip newNames newBound) <$> renameExpressionM e
-renameExpressionM (Flatten l e) =
-    Flatten l <$> renameExpressionM e
-renameExpressionM (Annot l e ty) =
-    Annot l <$> renameExpressionM e <*> pure ty
-renameExpressionM c@Constructor{} = pure c
-renameExpressionM c@BuiltinFn{} = pure c
-renameExpressionM c@Random{} = pure c
+        constructor p (NE.zip newNames newBound) <$> renameExpressionM e
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
@@ -2,12 +2,15 @@
 
 module Language.Dickinson.Rename.Amalgamate ( amalgamateM
                                             , fileDecls
+                                            , bslDecls
                                             ) where
 
+import           Control.Composition              ((<=*<))
 import           Control.Monad                    ((<=<))
 import           Control.Monad.Except             (MonadError)
 import           Control.Monad.IO.Class           (MonadIO)
 import           Control.Monad.State              (MonadState)
+import qualified Data.ByteString.Lazy             as BSL
 import           Data.Functor                     (($>))
 import           Data.Semigroup                   ((<>))
 import           Language.Dickinson.Check.Pattern
@@ -39,3 +42,10 @@
           -> FilePath -- ^ Source file
           -> m [Declaration AlexPosn]
 fileDecls is = amalgamateM is <=< parseFpM
+
+bslDecls :: (HasLexerState s, MonadIO m, MonadError (DickinsonError AlexPosn) m, MonadState s m)
+          => [FilePath] -- ^ Includes
+          -> FilePath -- ^ Source file (for reporting errors)
+          -> BSL.ByteString
+          -> m [Declaration AlexPosn]
+bslDecls is = amalgamateM is <=*< parseBSLM
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
@@ -65,10 +65,10 @@
                         , letBinds :: NonEmpty (Name a, Expression a)
                         , letExpr  :: Expression a
                         }
-                  | Bind { exprAnn :: a
-                        , letBinds :: NonEmpty (Name a, Expression a)
-                        , letExpr  :: Expression a
-                        }
+                  | Bind { exprAnn  :: a
+                         , letBinds :: NonEmpty (Name a, Expression a)
+                         , letExpr  :: Expression a
+                         }
                   | Var { exprAnn :: a, exprVar :: Name a }
                   | Interp { exprAnn :: a, exprInterp :: [Expression a] }
                   | MultiInterp { exprAnn :: a, exprMultiInterp :: [Expression a] }
diff --git a/src/Language/Dickinson/TypeCheck.hs b/src/Language/Dickinson/TypeCheck.hs
--- a/src/Language/Dickinson/TypeCheck.hs
+++ b/src/Language/Dickinson/TypeCheck.hs
@@ -121,16 +121,8 @@
             tyAssert ty' e'
             pure ty''
         _ -> throwError $ ExpectedLambda e ty
-typeOf (Let _ bs e) = do
-    es' <- traverse typeOf (snd <$> bs)
-    let ns = fst <$> bs
-    Ext.zipWithM_ tyInsert ns es'
-    typeOf e
-typeOf (Bind _ bs e) = do
-    es' <- traverse typeOf (snd <$> bs)
-    let ns = fst <$> bs
-    Ext.zipWithM_ tyInsert ns es'
-    typeOf e
+typeOf (Let _ bs e) = tyLet bs e
+typeOf (Bind _ bs e) = tyLet bs e
 typeOf (Match _ e brs@((_,e') :| _)) = do
     ty <- typeOf e
     forM_ (fst <$> brs) $ \p ->
@@ -149,3 +141,14 @@
 typeOf (BuiltinFn l _) = pure $ -- all builtins have type (-> text text)
     TyFun l (TyText l) (TyText l)
 typeOf (Random l n) = pure $ TyNamed l n
+
+-- since :let and :bind are the same in this scheme
+tyLet :: (HasTyEnv s, MonadState (s a) m, MonadError (DickinsonError a) m)
+      => NonEmpty (Name a, Expression a)
+      -> Expression a
+      -> m (DickinsonTy a)
+tyLet bs e = do
+    es' <- traverse typeOf (snd <$> bs)
+    let ns = fst <$> bs
+    Ext.zipWithM_ tyInsert ns es'
+    typeOf e
