diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,7 +1,50 @@
 
 # ChangeLog
 
-## [(diff)](https://github.com/haskell-nix/hnix/compare/0.13.1...0.14.0#files_bucket) 0.14.0
+## [(diff)](https://github.com/haskell-nix/hnix/compare/0.14.0...0.15.0#files_bucket) 0.15.0
+
+For support refere to: https://github.com/haskell-nix/hnix/issues/984
+
+Partial log (for now):
+
+* Breaking:
+
+  * `Nix.Expr.Shorthands`:
+    * `inherit{,From}`:
+      * dropped second(/third) argument as irrelevant ([report](https://github.com/haskell-nix/hnix/issues/326))
+      * bindings to inherit changed type from complex `[NKeyName]` (which is for static & dynamic keys) to `[VarName]` (`VarName` is newtype of `Text`).
+      * So examples of use now are: `inherit ["a", "b"]`, `inheritFrom (var "a") ["b", "c"]`
+    * `mkAssert`: fixed ([report](https://github.com/haskell-nix/hnix/issues/969)).
+    * fx presedence between the operators:
+        
+        ```haskell
+        (@@), (@.), (@./), ($==), ($!=), ($<), ($<=), ($>), ($>=), ($&&), ($||), ($->), ($//), ($+), ($-), ($*), ($/), ($++), (==>)
+        ```
+        
+        Now these shorthands can be used without sectioning & so represent the Nix expressions one to one.
+        
+        ```haskell
+        nix = "           a/b   //            c/def   //           <g>  <             def/d"
+        hask = mkRelPath "a/b" $// mkRelPath "c/def" $// mkEnvPath "g" $<  mkRelPath "def/d"
+        ```
+
+* Additional
+  * `Nix.Expr.Shorthands`:
+    * added:
+      * `emptySet`
+      * `emptyList`
+      * `mkOp{,2}`
+      * `mk{,Named,Variadic,General}ParamSet`
+      * `mkNeg` - number negation.
+      * `@.<|>` for Nix language `s.x or y` expession.
+    * entered deprecation:
+      * `mkOper{,2}` bacame `mkOp{,2}`.
+      * `mkBinop` became `mkOp2`.
+      * `mkParaset` supeceeded by `mk{,Named{,Variadic},Variadic,General}ParamSet`.
+    * fixed:
+      * `mkAssert` was creating `with`, now properly creates `assert`.
+
+## [(diff)](https://github.com/haskell-nix/hnix/compare/0.13.1...0.14.0#files_bucket) 0.14.0 (2021-07-08)
 
 * GHC 9.0 support.
 
diff --git a/benchmarks/Main.hs b/benchmarks/Main.hs
--- a/benchmarks/Main.hs
+++ b/benchmarks/Main.hs
@@ -5,4 +5,4 @@
 import qualified ParserBench
 
 main :: IO ()
-main = defaultMain [ParserBench.benchmarks]
+main = defaultMain $ one ParserBench.benchmarks
diff --git a/benchmarks/ParserBench.hs b/benchmarks/ParserBench.hs
--- a/benchmarks/ParserBench.hs
+++ b/benchmarks/ParserBench.hs
@@ -4,8 +4,8 @@
 
 import           Criterion
 
-benchFile :: FilePath -> Benchmark
-benchFile = bench <*> whnfIO . parseNixFile . ("data/" <>)
+benchFile :: Path -> Benchmark
+benchFile = bench . coerce <*> whnfIO . parseNixFile . ("data/" <>)
 
 benchmarks :: Benchmark
 benchmarks = bgroup
diff --git a/hnix.cabal b/hnix.cabal
--- a/hnix.cabal
+++ b/hnix.cabal
@@ -1,6 +1,6 @@
 cabal-version:  2.2
 name:           hnix
-version:        0.14.0.8
+version:        0.15.0
 synopsis:       Haskell implementation of the Nix language
 description:    Haskell implementation of the Nix language.
 category:       System, Data, Nix
@@ -341,7 +341,9 @@
 
 library
   exposed-modules:
+    Prelude
     Nix
+    Nix.Utils
     Nix.Atoms
     Nix.Builtins
     Nix.Cache
@@ -383,7 +385,6 @@
     Nix.Type.Env
     Nix.Type.Infer
     Nix.Type.Type
-    Nix.Utils
     Nix.Utils.Fix1
     Nix.Value
     Nix.Value.Equal
@@ -392,13 +393,13 @@
     Nix.XML
   other-modules:
     Paths_hnix
+    Nix.Unused
   autogen-modules:
     Paths_hnix
   hs-source-dirs:
     src
   mixins:
       base hiding (Prelude)
-    , relude (Relude as Prelude)
     , relude
   ghc-options:
     -Wall
@@ -406,7 +407,7 @@
   build-depends:
       aeson >= 1.4.2 && < 1.6 || >= 2.0 && < 2.1
     , array >= 0.4 && < 0.6
-    , base >= 4.12 && < 4.16
+    , base >= 4.12 && < 5
     , base16-bytestring >= 0.1.1 && < 1.1
     , binary >= 0.8.5 && < 0.9
     , bytestring >= 0.10.8 && < 0.12
@@ -421,7 +422,7 @@
     , filepath >= 1.4.2 && < 1.5
     , free >= 5.1 && < 5.2
     , gitrev >= 1.1.0 && < 1.4
-    , hashable >= 1.2.5 && < 1.5
+    , hashable >= 1.2.5 && < 1.4
     , hashing >= 0.1.0 && < 0.2
     , hnix-store-core >= 0.5.0 && < 0.6
     , hnix-store-remote >= 0.5.0 && < 0.6
@@ -463,7 +464,7 @@
     , time >= 1.8.0 && < 1.9 || >= 1.9.3 && < 1.10
     , transformers >= 0.5.5 && < 0.6
     , transformers-base >= 0.4.5 && < 0.5
-    , unix >= 2.7.2 && < 2.8
+    , unix-compat >= 0.4.3 && < 0.6
     , unordered-containers >= 0.2.9 && < 0.3
     , vector >= 0.12.0 && < 0.13
     , xml >= 1.3.14 && < 1.4
@@ -477,6 +478,7 @@
     , DeriveLift
     , FlexibleContexts
     , FlexibleInstances
+    , ScopedTypeVariables
     , StandaloneDeriving
     , TypeApplications
     , TypeSynonymInstances
@@ -485,6 +487,7 @@
     , TupleSections
     , LambdaCase
     , BangPatterns
+    , ViewPatterns
   if flag(optimize)
     default-extensions:
       ApplicativeDo
@@ -531,7 +534,6 @@
     , time
   mixins:
       base hiding (Prelude)
-    , relude (Relude as Prelude)
     , relude
   default-extensions:
       OverloadedStrings
@@ -543,6 +545,7 @@
     , DeriveLift
     , FlexibleContexts
     , FlexibleInstances
+    , ScopedTypeVariables
     , StandaloneDeriving
     , TypeApplications
     , TypeSynonymInstances
@@ -551,6 +554,7 @@
     , TupleSections
     , LambdaCase
     , BangPatterns
+    , ViewPatterns
   if flag(optimize)
     default-extensions:
       ApplicativeDo
@@ -577,7 +581,6 @@
     TestCommon
   mixins:
       base hiding (Prelude)
-    , relude (Relude as Prelude)
     , relude
   hs-source-dirs:
     tests
@@ -610,7 +613,7 @@
     , serialise
     , template-haskell
     , time
-    , unix
+    , unix-compat
   default-extensions:
       OverloadedStrings
     , DeriveGeneric
@@ -621,6 +624,7 @@
     , DeriveLift
     , FlexibleContexts
     , FlexibleInstances
+    , ScopedTypeVariables
     , StandaloneDeriving
     , TypeApplications
     , TypeSynonymInstances
@@ -629,6 +633,7 @@
     , TupleSections
     , LambdaCase
     , BangPatterns
+    , ViewPatterns
   if flag(optimize)
     default-extensions:
       ApplicativeDo
@@ -647,7 +652,6 @@
     benchmarks
   mixins:
       base hiding (Prelude)
-    , relude (Relude as Prelude)
     , relude
   ghc-options:
     -Wall
@@ -673,6 +677,7 @@
     , DeriveLift
     , FlexibleContexts
     , FlexibleInstances
+    , ScopedTypeVariables
     , StandaloneDeriving
     , TypeApplications
     , TypeSynonymInstances
@@ -681,6 +686,7 @@
     , TupleSections
     , LambdaCase
     , BangPatterns
+    , ViewPatterns
   if flag(optimize)
     default-extensions:
       ApplicativeDo
diff --git a/main/Main.hs b/main/Main.hs
--- a/main/Main.hs
+++ b/main/Main.hs
@@ -1,29 +1,24 @@
-{-# LANGUAGE MultiWayIf #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE ViewPatterns #-}
-{-# LANGUAGE RecordWildCards #-}
+{-# language MultiWayIf #-}
+{-# language TypeFamilies #-}
+{-# language RecordWildCards #-}
 
 module Main ( main ) where
 
-import           Nix.Utils
+import           Relude                        as Prelude ( force )
 import           Control.Comonad                ( extract )
-import qualified Control.DeepSeq               as Deep
-import qualified Control.Exception             as Exc
+import qualified Control.Exception             as Exception
 import           GHC.Err                        ( errorWithoutStackTrace )
 import           Control.Monad.Free
 import           Control.Monad.Ref              ( MonadRef(readRef) )
 import           Control.Monad.Catch
-import           System.IO                      ( hPutStrLn, getContents )
+import           System.IO                      ( hPutStrLn )
 import qualified Data.HashMap.Lazy             as M
 import qualified Data.Map                      as Map
-import           Data.Maybe                     ( fromJust )
-import qualified Data.String                   as String
 import           Data.Time
 import qualified Data.Text.IO                  as Text
-import           Nix
+import           Text.Show.Pretty               ( ppShow )
+import           Nix                     hiding ( force )
 import           Nix.Convert
-import qualified Nix.Eval                      as Eval
 import           Nix.Fresh.Basic
 import           Nix.Json
 import           Nix.Options.Parser
@@ -35,11 +30,9 @@
 import           Nix.Value.Monad
 import           Options.Applicative     hiding ( ParserResult(..) )
 import           Prettyprinter           hiding ( list )
-import           Prettyprinter.Render.Text
+import           Prettyprinter.Render.Text      ( renderIO )
 import qualified Repl
-import           System.FilePath
-import qualified Text.Show.Pretty              as PS
-import           Nix.Utils.Fix1                 ( Fix1T )
+import           Nix.Eval
 
 main :: IO ()
 main =
@@ -50,45 +43,66 @@
     main' opts
 
 main' :: Options -> IO ()
-main' (opts@Options{..}) = runWithBasicEffectsIO opts execContentsFilesOrRepl
+main' opts@Options{..} = runWithBasicEffectsIO opts execContentsFilesOrRepl
  where
+  --  2021-07-15: NOTE: This logic should be weaved stronger through CLI options logic (OptParse-Applicative code)
+  -- As this logic is not stated in the CLI documentation, for example. So user has no knowledge of these.
+  execContentsFilesOrRepl :: StandardT (StdIdT IO) ()
   execContentsFilesOrRepl =
-    firstJust
-      -- The `--read` option: load expression from a serialized file.
-      [ readFrom <&> \path -> do
-          let file = addExtension (dropExtension path) "nixc"
-          process (Just file) =<< liftIO (readCache path)
+    fromMaybe
+      loadFromCliFilePathList
+      ( loadBinaryCacheFile <|>
+        loadLiteralExpression <|>
+        loadExpressionFromFile
+      )
+   where
+    -- | The base case: read expressions from the last CLI directive (@[FILE]@) listed on the command line.
+    loadFromCliFilePathList =
+      case filePaths of
+        []     -> runRepl
+        ["-"]  -> readExpressionFromStdin
+        _paths -> processSeveralFiles (coerce _paths)
+     where
+      -- | Fall back to running the REPL
+      runRepl = withEmptyNixContext Repl.main
 
-      -- The `--expr` option: read expression from the argument string
-      , expression <&> processText
+      readExpressionFromStdin =
+        do
+          expr <- liftIO Text.getContents
+          processExpr expr
 
-      -- The `--file` argument: read expressions from the files listed in the argument file
-      , fromFile <&> \x ->
-          -- We can start use Text as in the base case, requires changing FilePath -> Text
-          traverse_ processFile . String.lines =<< liftIO
-            (case x of
-              "-" -> getContents
-              fp -> readFile fp
-            )
-      ]
-    `orElse`
-      -- The base case: read expressions from the files listed on the command line
-      case filePaths of
-        -- With no files, fall back to running the REPL
-        [] -> withNixContext mempty Repl.main
-        ["-"] -> processText =<< liftIO Text.getContents
-        _paths -> traverse_ processFile _paths
+    processSeveralFiles :: [Path] -> StandardT (StdIdT IO) ()
+    processSeveralFiles = traverse_ processFile
+     where
+      processFile path = handleResult (pure path) =<< parseNixFileLoc path
 
-  firstJust :: [Maybe a] -> Maybe a
-  firstJust = asum
+    -- |  The `--read` option: load expression from a serialized file.
+    loadBinaryCacheFile =
+      (\ (binaryCacheFile :: Path) ->
+        do
+          let file = replaceExtension binaryCacheFile "nixc"
+          processCLIOptions (Just file) =<< liftIO (readCache binaryCacheFile)
+      ) <$> readFrom
 
-  orElse :: Maybe a -> a -> a
-  orElse = flip fromMaybe
+    -- | The `--expr` option: read expression from the argument string
+    loadLiteralExpression = processExpr <$> expression
 
-  processText text = handleResult Nothing     $   parseNixTextLoc text
+    -- | The `--file` argument: read expressions from the files listed in the argument file
+    loadExpressionFromFile =
+      -- We can start use Text as in the base case, requires changing Path -> Text
+      -- But that is a gradual process:
+      -- https://github.com/haskell-nix/hnix/issues/912
+      (processSeveralFiles . (coerce . toString <$>) . lines <=< liftIO) .
+        (\case
+          "-" -> Text.getContents
+          _fp -> readFile _fp
+        ) <$> fromFile
 
-  processFile path = handleResult (Just path) =<< parseNixFileLoc path
+  processExpr text = handleResult Nothing     $   parseNixTextLoc text
 
+  withEmptyNixContext = withNixContext mempty
+
+  --  2021-07-15: NOTE: @handleResult@ & @process@ - have atrocious size & compexity, they need to be decomposed & refactored.
   handleResult mpath =
     either
       (\ err ->
@@ -103,18 +117,18 @@
         do
           when check $
             do
-              expr' <- liftIO (reduceExpr mpath expr)
+              expr' <- liftIO $ reduceExpr mpath expr
               either
-                (\ err -> errorWithoutStackTrace $ "Type error: " <> PS.ppShow err)
-                (\ ty  -> liftIO $ putStrLn $ "Type of expression: " <> PS.ppShow
-                  (fromJust $ Map.lookup "it" (coerce ty :: Map Text [Scheme]))
+                (\ err -> errorWithoutStackTrace $ "Type error: " <> ppShow err)
+                (\ ty  -> liftIO $ putStrLn $ "Type of expression: " <>
+                  ppShow (maybeToMonoid $ Map.lookup @VarName @[Scheme] "it" $ coerce ty)
                 )
-                (HM.inferTop mempty [("it", stripAnnotation expr')])
+                (HM.inferTop mempty (one ("it", stripAnnotation expr')))
 
                 -- liftIO $ putStrLn $ runST $
                 --     runLintM opts . renderSymbolic =<< lint opts expr
 
-          catch (process mpath expr) $
+          catch (processCLIOptions mpath expr) $
             \case
               NixException frames ->
                 errorWithoutStackTrace . show =<<
@@ -124,30 +138,32 @@
                     frames
 
           when repl $
-            withNixContext mempty $
+            withEmptyNixContext $
               bool
                 Repl.main
                 (do
-                  val <- Nix.nixEvalExprLoc mpath expr
+                  val <- nixEvalExprLoc (coerce mpath) expr
                   Repl.main' $ pure val
                 )
                 evaluate
       )
 
-  process mpath expr
+  --  2021-07-15: NOTE: Logic of CLI Option processing is scattered over several functions, needs to be consolicated.
+  processCLIOptions :: Maybe Path -> NExprLoc -> StandardT (StdIdT IO) ()
+  processCLIOptions mpath expr
     | evaluate =
       if
-        | tracing                       -> evaluateExpression mpath Nix.nixTracingEvalExprLoc printer expr
-        | Just path <- reduce           -> evaluateExpression mpath (reduction path) printer expr
-        | not (null arg && null argstr) -> evaluateExpression mpath Nix.nixEvalExprLoc printer expr
-        | otherwise                     -> processResult printer =<< Nix.nixEvalExprLoc mpath expr
-    | xml                        =  fail "Rendering expression trees to XML is not yet implemented"
-    | json                       =  fail "Rendering expression trees to JSON is not implemented"
-    | verbose >= DebugInfo       =  liftIO $ putStr $ PS.ppShow $ stripAnnotation expr
-    | cache , Just path <- mpath =  liftIO $ writeCache (addExtension (dropExtension path) "nixc") expr
-    | parseOnly                  =  void $ liftIO $ Exc.evaluate $ Deep.force expr
+        | tracing                       -> evaluateExprWithEvaluator nixTracingEvalExprLoc expr
+        | Just path <- reduce           -> evaluateExprWithEvaluator (reduction path . coerce) expr
+        | null arg || null argstr       -> evaluateExprWithEvaluator nixEvalExprLoc expr
+        | otherwise                     -> processResult printer <=< nixEvalExprLoc (coerce mpath) $ expr
+    | xml                        = fail "Rendering expression trees to XML is not yet implemented"
+    | json                       = fail "Rendering expression trees to JSON is not implemented"
+    | verbose >= DebugInfo       =  liftIO . putStr . ppShow . stripAnnotation $ expr
+    | cache , Just path <- mpath =  liftIO . writeCache (replaceExtension path "nixc") $ expr
+    | parseOnly                  =  void . liftIO . Exception.evaluate . force $ expr
     | otherwise                  =
-      liftIO $
+      liftIO .
         renderIO
           stdout
           . layoutPretty (LayoutOptions $ AvailablePerLine 80 0.4)
@@ -155,19 +171,31 @@
           . stripAnnotation
           $ expr
    where
+    evaluateExprWithEvaluator evaluator = evaluateExpression (coerce mpath) evaluator printer
+
     printer
       | finder    = findAttrs <=< fromValue @(AttrSet (StdValue (StandardT (StdIdT IO))))
-      | xml       = liftIO . Text.putStrLn . stringIgnoreContext . toXML <=< normalForm
-      -- 2021-05-27: NOTE: With naive fix of the #941
-      -- This is overall a naive printer implementation, as options should interact/respect one another.
-      -- A nice question: "Should respect one another to what degree?": Go full combinator way, for which
-      -- old Nix CLI is nototrious for (and that would mean to reimplement the old Nix CLI),
-      -- OR: https://github.com/haskell-nix/hnix/issues/172 and have some sane standart/default behaviour for (most) keys.
-      | json      = liftIO . Text.putStrLn . stringIgnoreContext         <=< nvalueToJSONNixString <=< normalForm
-      | strict    = liftIO . print         . prettyNValue                <=< normalForm
-      | values    = liftIO . print         . prettyNValueProv            <=< removeEffects
-      | otherwise = liftIO . print         . prettyNValue                <=< removeEffects
+      | otherwise = printer'
      where
+      printer'
+        | xml       = go (stringIgnoreContext . toXML)                     normalForm
+        -- 2021-05-27: NOTE: With naive fix of the #941
+        -- This is overall a naive printer implementation, as options should interact/respect one another.
+        -- A nice question: "Should respect one another to what degree?": Go full combinator way, for which
+        -- old Nix CLI is nototrious for (and that would mean to reimplement the old Nix CLI),
+        -- OR: https://github.com/haskell-nix/hnix/issues/172 and have some sane standart/default behaviour for (most) keys.
+        | json      = go (stringIgnoreContext . mempty . nvalueToJSONNixString) normalForm
+        | strict    = go (show . prettyNValue)                             normalForm
+        | values    = go (show . prettyNValueProv)                         removeEffects
+        | otherwise = go (show . prettyNValue)                             removeEffects
+       where
+        go
+          :: (b -> Text)
+          -> (a -> StandardT (StdIdT IO) b)
+          -> a
+          -> StandardT (StdIdT IO) ()
+        go g f = liftIO . Text.putStrLn . g <=< f
+
       findAttrs
         :: AttrSet (StdValue (StandardT (StdIdT IO)))
         -> StandardT (StdIdT IO) ()
@@ -200,7 +228,7 @@
                     (pure . pure . Free)
                     nv
                 )
-                (sortWith fst $ M.toList s)
+                (sortWith fst $ M.toList $ M.mapKeys coerce s)
             traverse_
               (\ (k, mv) ->
                 do
@@ -212,10 +240,10 @@
                       liftIO $ Text.putStrLn path
                       when descend $
                         maybe
-                          pass
+                          stub
                           (\case
-                            NVSet s' _ -> go (path <> ".") s'
-                            _          -> pass
+                            NVSet _ s' -> go (path <> ".") s'
+                            _          -> stub
                           )
                           mv
               )
@@ -239,10 +267,10 @@
             _                              -> (True , True )
 
           forceEntry
-            :: MonadValue a (Fix1T StandardTF (StdIdT IO))
+            :: MonadValue a (StandardT (StdIdT IO))
             => Text
             -> a
-            -> Fix1T StandardTF (StdIdT IO) (Maybe a)
+            -> StandardT (StdIdT IO) (Maybe a)
           forceEntry k v =
             catch
               (pure <$> demand v)
@@ -259,24 +287,24 @@
                   pure Nothing
               )
 
-  reduction path mp x =
+  reduction path mpathToContext annExpr =
     do
       eres <-
-        Nix.withNixContext
-          mp
-          (Nix.reducingEvalExpr
-            Eval.evalContent
-            mp
-            x
+        withNixContext
+          mpathToContext
+          (reducingEvalExpr
+            evalContent
+            mpathToContext
+            annExpr
           )
       handleReduced path eres
 
   handleReduced
     :: (MonadThrow m, MonadIO m)
-    => FilePath
+    => Path
     -> (NExprLoc, Either SomeException (NValue t f m))
     -> m (NValue t f m)
-  handleReduced path (expr', eres) =
+  handleReduced (coerce -> path) (expr', eres) =
     do
       liftIO $
         do
diff --git a/main/Repl.hs b/main/Repl.hs
--- a/main/Repl.hs
+++ b/main/Repl.hs
@@ -7,22 +7,19 @@
    directory for more details.
 -}
 
-{-# LANGUAGE MultiWayIf #-}
-{-# LANGUAGE ScopedTypeVariables #-}
+{-# language MultiWayIf #-}
 
 module Repl
   ( main
   , main'
   ) where
 
-import           Nix                     hiding ( exec
-                                                , try
-                                                )
+import           Prelude                 hiding ( state )
+import           Nix                     hiding ( exec )
 import           Nix.Scope
-import           Nix.Utils
 import           Nix.Value.Monad                ( demand )
 
-import qualified Data.HashMap.Lazy
+import qualified Data.HashMap.Lazy           as M
 import           Data.Char                      ( isSpace )
 import           Data.List                      ( dropWhileEnd )
 import qualified Data.Text                   as Text
@@ -71,7 +68,7 @@
     evalStateT
       (evalRepl
         banner
-        (cmd . toText)
+        (cmd . fromString)
         options
         (pure commandPrefix)
         (pure "paste")
@@ -99,28 +96,28 @@
 
   rcFile =
     do
-      f <- liftIO $ Text.readFile ".hnixrc" `catch` handleMissing
+      f <- liftIO $ readFile ".hnixrc" `catch` handleMissing
 
       traverse_
         (\case
           (prefixedCommand : xs) | Text.head prefixedCommand == commandPrefix ->
             do
               let
-                arguments = Text.unwords xs
+                arguments = unwords xs
                 command = Text.tail prefixedCommand
               optMatcher command options arguments
-          x -> cmd $ Text.unwords x
+          x -> cmd $ unwords x
         )
-        (Text.words <$> lines f)
+        (words <$> lines f)
 
   handleMissing e
-    | Error.isDoesNotExistError e = pure ""
-    | otherwise = throwIO e
+    | Error.isDoesNotExistError e = pure mempty
+    | otherwise = throwM e
 
   -- Replicated and slightly adjusted `optMatcher` from `System.Console.Repline`
   -- which doesn't export it.
-  -- * @MonadIO m@ instead of @MonadHaskeline m@
-  -- * @putStrLn@ instead of @outputStrLn@
+  --  * @MonadIO m@ instead of @MonadHaskeline m@
+  --  * @putStrLn@ instead of @outputStrLn@
   optMatcher :: MonadIO m
              => Text
              -> Console.Options m
@@ -128,16 +125,16 @@
              -> m ()
   optMatcher s [] _ = liftIO $ Text.putStrLn $ "No such command :" <> s
   optMatcher s ((x, m) : xs) args
-    | s `Text.isPrefixOf` toText x = m $ toString args
+    | s `Text.isPrefixOf` fromString x = m $ toString args
     | otherwise = optMatcher s xs args
 
 
 -- * Types
 
 data IState t f m = IState
-  { replIt  :: Maybe NExprLoc          -- ^ Last expression entered
-  , replCtx :: AttrSet (NValue t f m)  -- ^ Value environment
-  , replCfg :: ReplConfig              -- ^ REPL configuration
+  { replIt  :: Maybe NExprLoc        -- ^ Last expression entered
+  , replCtx :: Scope (NValue t f m)  -- ^ Scope. Value environment.
+  , replCfg :: ReplConfig            -- ^ REPL configuration
   } deriving (Eq, Show)
 
 data ReplConfig = ReplConfig
@@ -159,14 +156,17 @@
 
   builtins <- evalText "builtins"
 
-  opts :: Nix.Options <- asks (view hasLens)
+  let
+    scope = coerce $
+      M.fromList $
+      ("builtins", builtins) : fmap ("input",) (maybeToList mIni)
 
+  opts :: Nix.Options <- asks $ view hasLens
+
   pure $
     IState
       Nothing
-      (Data.HashMap.Lazy.fromList $
-        ("builtins", builtins) : fmap ("input",) (maybeToList mIni)
-      )
+      scope
       defReplConfig
         { cfgStrict = strict opts
         , cfgValues = values opts
@@ -192,9 +192,9 @@
   -> Repl e t f m (Maybe (NValue t f m))
 exec update source = do
   -- Get the current interpreter state
-  st <- get
+  state <- get
 
-  when (cfgDebug $ replCfg st) $ liftIO $ print st
+  when (cfgDebug $ replCfg state) $ liftIO $ print state
 
   -- Parser ( returns AST as `NExprLoc` )
   case parseExprOrBinding source of
@@ -211,7 +211,7 @@
       -- let tyctx' = inferTop mempty [("repl", stripAnnotation expr)]
       -- liftIO $ print tyctx'
 
-      mVal <- lift $ lift $ try $ pushScope (replCtx st) (evalExprLoc expr)
+      mVal <- lift $ lift $ try $ pushScope (replCtx state) (evalExprLoc expr)
 
       either
         (\ (NixException frames) -> do
@@ -221,12 +221,12 @@
           -- Update the interpreter state
           when (update && isBinding) $ do
             -- Set `replIt` to last entered expression
-            put st { replIt = pure expr }
+            put state { replIt = pure expr }
 
             -- If the result value is a set, update our context with it
             case val of
-              NVSet xs _ -> put st { replCtx = xs <> replCtx st }
-              _          -> pass
+              NVSet _ (coerce -> scope) -> put state { replCtx = scope <> replCtx state }
+              _          -> stub
 
           pure $ pure val
         )
@@ -248,7 +248,7 @@
         (parseNixTextLoc i)
 
     toAttrSet i =
-      "{" <> i <> bool ";" mempty (Text.isSuffixOf ";" i) <> "}"
+      "{" <> i <> whenFalse ";" (Text.isSuffixOf ";" i) <> "}"
 
 cmd
   :: (MonadNix e t f m, MonadIO m)
@@ -258,7 +258,7 @@
   do
     mVal <- exec True source
     maybe
-      pass
+      stub
       printValue
       mVal
 
@@ -267,11 +267,15 @@
            -> Repl e t f m ()
 printValue val = do
   cfg <- replCfg <$> get
+  let
+    g :: MonadIO m => Doc ann0 -> m ()
+    g = liftIO . print
+
   lift $ lift $
     (if
-      | cfgStrict cfg -> liftIO . print . prettyNValue     <=< normalForm
-      | cfgValues cfg -> liftIO . print . prettyNValueProv <=< removeEffects
-      | otherwise     -> liftIO . print . prettyNValue     <=< removeEffects
+      | cfgStrict cfg -> g . prettyNValue     <=< normalForm
+      | cfgValues cfg -> g . prettyNValueProv <=< removeEffects
+      | otherwise     -> g . prettyNValue     <=< removeEffects
     ) val
 
 
@@ -281,26 +285,31 @@
 browse :: (MonadNix e t f m, MonadIO m)
        => Text
        -> Repl e t f m ()
-browse _ = do
-  st <- get
-  for_ (Data.HashMap.Lazy.toList $ replCtx st) $ \(k, v) -> do
-    liftIO $ Text.putStr $ k <> " = "
-    printValue v
+browse _ =
+  do
+    state <- get
+    traverse_
+      (\(k, v) ->
+        do
+          liftIO $ Text.putStr $ coerce k <> " = "
+          printValue v
+      )
+      (M.toList $ coerce $ replCtx state)
 
 -- | @:load@ command
 load
   :: (MonadNix e t f m, MonadIO m)
   -- This one does I String -> O String pretty fast, it is ugly to double marshall here.
-  => String
+  => Path
   -> Repl e t f m ()
-load args =
+load path =
   do
-    contents <- liftIO $
-      Text.readFile $
-       trim args
+    contents <- liftIO $ readFile $
+       trim path
     void $ exec True contents
  where
-  trim = dropWhileEnd isSpace . dropWhile isSpace
+  trim :: Path -> Path
+  trim = coerce . dropWhileEnd isSpace . dropWhile isSpace . coerce
 
 -- | @:type@ command
 typeof
@@ -308,12 +317,12 @@
   => Text
   -> Repl e t f m ()
 typeof args = do
-  st <- get
+  state <- get
   mVal <-
     maybe
       (exec False line)
       (pure . pure)
-      (Data.HashMap.Lazy.lookup line (replCtx st))
+      (M.lookup (coerce line) (coerce $ replCtx state))
 
   traverse_ printValueType mVal
 
@@ -332,12 +341,14 @@
 -- | @:set@ command
 setConfig :: (MonadNix e t f m, MonadIO m) => Text -> Repl e t f m ()
 setConfig args =
-  case Text.words args of
-    []       -> liftIO $ Text.putStrLn "No option to set specified"
-    (x:_xs)  ->
+  list
+    (liftIO $ Text.putStrLn "No option to set specified")
+    (\ (x:_xs)  ->
       case filter ((==x) . helpSetOptionName) helpSetOptions of
         [opt] -> modify (\s -> s { replCfg = helpSetOptionFunction opt (replCfg s) })
         _     -> liftIO $ Text.putStrLn "No such option"
+    )
+    $ words args
 
 
 -- * Interactive Shell
@@ -345,8 +356,7 @@
 -- | Prefix tab completer
 defaultMatcher :: MonadIO m => [(String, CompletionFunc m)]
 defaultMatcher =
-  [ (":load", Console.fileCompleter)
-  ]
+  one (":load", Console.fileCompleter)
 
 completion
   :: (MonadNix e t f m, MonadIO m)
@@ -381,9 +391,9 @@
     listFiles word
 
   -- Attributes of sets in REPL context
-  | var : subFields <- Text.split (== '.') (toText word) , not $ null subFields =
+  | var : subFields <- Text.split (== '.') (fromString word) , not $ null subFields =
     do
-      s <- get
+      state <- get
       maybe
         stub
         (\ binding ->
@@ -396,18 +406,24 @@
                     candidates
                   )
         )
-        (Data.HashMap.Lazy.lookup var (replCtx s))
+        (M.lookup (coerce var) $ coerce $ replCtx state)
 
   -- Builtins, context variables
   | otherwise =
     do
-      s <- get
-      let contextKeys = Data.HashMap.Lazy.keys (replCtx s)
-          (Just (NVSet builtins _)) = Data.HashMap.Lazy.lookup "builtins" (replCtx s)
-          shortBuiltins = Data.HashMap.Lazy.keys builtins
+      state <- get
+      let
+          scopeHashMap :: HashMap VarName (NValue t f m)
+          scopeHashMap = coerce $ replCtx state
+          contextKeys :: [VarName]
+          contextKeys = M.keys scopeHashMap
+          builtins :: AttrSet (NValue t f m)
+          (Just (NVSet _ builtins)) = M.lookup "builtins" scopeHashMap
+          shortBuiltins :: [VarName]
+          shortBuiltins = M.keys builtins
 
       pure $ listCompletion $ toString <$>
-        ["__includes"]
+        one "__includes"
           <> contextKeys
           <> shortBuiltins
 
@@ -423,7 +439,7 @@
       -> m [Text]
     algebraicComplete subFields val =
       let
-        keys = fmap ("." <>) . Data.HashMap.Lazy.keys
+        keys = fmap ("." <>) . M.keys
 
         withMap m =
           case subFields of
@@ -437,10 +453,10 @@
                    (("." <> f) <>)
                    . algebraicComplete fs <=< demand
                 )
-                (Data.HashMap.Lazy.lookup f m)
+                (M.lookup (coerce f) m)
       in
       case val of
-        NVSet xs _ -> withMap xs
+        NVSet _ xs -> withMap (M.mapKeys coerce xs)
         _          -> stub
 
 -- | HelpOption inspired by Dhall Repl
@@ -458,44 +474,44 @@
 helpOptions =
   [ HelpOption
       "help"
-      ""
+      mempty
       "Print help text"
-      (help helpOptions . toText)
+      (help helpOptions . fromString)
   , HelpOption
       "paste"
-      ""
+      mempty
       "Enter multi-line mode"
       (error "Unreachable")
   , HelpOption
       "load"
       "FILENAME"
       "Load .nix file into scope"
-      load
+      (load . fromString)
   , HelpOption
       "browse"
-      ""
+      mempty
       "Browse bindings in interpreter context"
-      (browse . toText)
+      (browse . fromString)
   , HelpOption
       "type"
       "EXPRESSION"
       "Evaluate expression or binding from context and print the type of the result value"
-      (typeof . toText)
+      (typeof . fromString)
   , HelpOption
       "quit"
-      ""
+      mempty
       "Quit interpreter"
       quit
   , HelpOption
       "set"
-      ""
+      mempty
       ("Set REPL option"
         <> Prettyprinter.line
         <> "Available options:"
         <> Prettyprinter.line
         <> renderSetOptions helpSetOptions
       )
-      (setConfig . toText)
+      (setConfig . fromString)
   ]
 
 -- | Options for :set
@@ -510,32 +526,32 @@
 helpSetOptions =
   [ HelpSetOption
       "strict"
-      ""
+      mempty
       "Enable strict evaluation of REPL expressions"
       (\x -> x { cfgStrict = True})
   , HelpSetOption
       "lazy"
-      ""
+      mempty
       "Disable strict evaluation of REPL expressions"
       (\x -> x { cfgStrict = False})
   , HelpSetOption
       "values"
-      ""
+      mempty
       "Enable printing of value provenance information"
       (\x -> x { cfgValues = True})
   , HelpSetOption
       "novalues"
-      ""
+      mempty
       "Disable printing of value provenance information"
       (\x -> x { cfgValues = False})
   , HelpSetOption
       "debug"
-      ""
+      mempty
       "Enable printing of REPL debug information"
       (\x -> x { cfgDebug = True})
   , HelpSetOption
       "nodebug"
-      ""
+      mempty
       "Disable REPL debugging"
       (\x -> x { cfgDebug = False})
   ]
diff --git a/src/Nix.hs b/src/Nix.hs
--- a/src/Nix.hs
+++ b/src/Nix.hs
@@ -1,10 +1,10 @@
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE ViewPatterns #-}
 
 module Nix
   ( module Nix.Cache
   , module Nix.Exec
-  , module Nix.Expr
+  , module Nix.Expr.Types
+  , module Nix.Expr.Shorthands
+  , module Nix.Expr.Types.Annotated
   , module Nix.Frames
   , module Nix.Render.Frame
   , module Nix.Normal
@@ -35,7 +35,9 @@
 import           Nix.Cache
 import qualified Nix.Eval                      as Eval
 import           Nix.Exec
-import           Nix.Expr
+import           Nix.Expr.Types
+import           Nix.Expr.Shorthands
+import           Nix.Expr.Types.Annotated
 import           Nix.Frames
 import           Nix.String
 import           Nix.Normal
@@ -45,7 +47,6 @@
 import           Nix.Reduce
 import           Nix.Render.Frame
 import           Nix.Thunk
-import           Nix.Utils
 import           Nix.Value
 import           Nix.Value.Monad
 import           Nix.XML
@@ -57,7 +58,7 @@
   :: (MonadNix e t f m, Has e Options, Functor g)
   => Transform g (m a)
   -> Alg g (m a)
-  -> Maybe FilePath
+  -> Maybe Path
   -> Fix g
   -> m a
 nixEval transform alg mpath = withNixContext mpath . adi transform alg
@@ -65,7 +66,7 @@
 -- | Evaluate a nix expression in the default context
 nixEvalExpr
   :: (MonadNix e t f m, Has e Options)
-  => Maybe FilePath
+  => Maybe Path
   -> NExpr
   -> m (NValue t f m)
 nixEvalExpr = nixEval id Eval.eval
@@ -74,7 +75,7 @@
 nixEvalExprLoc
   :: forall e t f m
    . (MonadNix e t f m, Has e Options)
-  => Maybe FilePath
+  => Maybe Path
   -> NExprLoc
   -> m (NValue t f m)
 nixEvalExprLoc =
@@ -89,22 +90,22 @@
 --   context.
 nixTracingEvalExprLoc
   :: (MonadNix e t f m, Has e Options, MonadIO m, Alternative m)
-  => Maybe FilePath
+  => Maybe Path
   -> NExprLoc
   -> m (NValue t f m)
 nixTracingEvalExprLoc mpath = withNixContext mpath . evalExprLoc
 
 evaluateExpression
   :: (MonadNix e t f m, Has e Options)
-  => Maybe FilePath
-  -> (Maybe FilePath -> NExprLoc -> m (NValue t f m))
+  => Maybe Path
+  -> (Maybe Path -> NExprLoc -> m (NValue t f m))
   -> (NValue t f m -> m a)
   -> NExprLoc
   -> m a
 evaluateExpression mpath evaluator handler expr =
   do
     opts :: Options <- asks $ view hasLens
-    args <-
+    (coerce -> args) <-
       (traverse . traverse)
         eval'
         $  (second parseArg <$> arg    opts)
@@ -137,23 +138,23 @@
   opts :: Options <- asks $ view hasLens
   maybe
     (h val)
-    (\ (Text.splitOn "." -> keys) -> processKeys keys val)
+    (\ (coerce . Text.splitOn "." -> keys) -> processKeys keys val)
     (attr opts)
  where
-  processKeys :: [Text] -> NValue t f m -> m a
+  processKeys :: [VarName] -> NValue t f m -> m a
   processKeys kys v =
     list
       (h v)
-      (\ (k : ks) ->
+      (\ ((k : ks) :: [VarName]) ->
         do
           v' <- demand v
           case (k, v') of
-            (Text.decimal -> Right (n,""), NVList xs) -> processKeys ks $ xs !! n
-            (_,         NVSet xs _) ->
+            (Text.decimal . coerce -> Right (n,""), NVList xs) -> processKeys ks $ xs !! n
+            (_,         NVSet _ xs) ->
               maybe
-                (errorWithoutStackTrace $ toString $ "Set does not contain key '" <> k <> "'")
+                (errorWithoutStackTrace $ "Set does not contain key ''" <> show k <> "''.")
                 (processKeys ks)
                 (M.lookup k xs)
-            (_, _) -> errorWithoutStackTrace $ toString $ "Expected a set or list for selector '" <> k <> "', but got: " <> show v
+            (_, _) -> errorWithoutStackTrace $ "Expected a set or list for selector '" <> show k <> "', but got: " <> show v
       )
       kys
diff --git a/src/Nix/Atoms.hs b/src/Nix/Atoms.hs
--- a/src/Nix/Atoms.hs
+++ b/src/Nix/Atoms.hs
@@ -1,5 +1,5 @@
-{-# LANGUAGE CPP            #-}
-{-# LANGUAGE DeriveAnyClass #-}
+{-# language CPP            #-}
+{-# language DeriveAnyClass #-}
 
 module Nix.Atoms where
 
@@ -11,6 +11,7 @@
 import           Data.Aeson.Types               ( FromJSON
                                                 , ToJSON
                                                 )
+--  2021-08-01: NOTE: Check the order efficience of NAtom constructors.
 
 -- | Atoms are values that evaluate to themselves.
 -- In other words - this is a constructors that are literals in Nix.
diff --git a/src/Nix/Builtins.hs b/src/Nix/Builtins.hs
--- a/src/Nix/Builtins.hs
+++ b/src/Nix/Builtins.hs
@@ -1,20 +1,17 @@
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE AllowAmbiguousTypes #-}
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FunctionalDependencies #-}
-{-# LANGUAGE KindSignatures #-}
-{-# LANGUAGE MonoLocalBinds #-}
-{-# LANGUAGE MultiWayIf #-}
-{-# LANGUAGE PackageImports #-}
-{-# LANGUAGE PartialTypeSignatures #-}
-{-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TemplateHaskell #-}
-{-# LANGUAGE UndecidableInstances #-}
-{-# LANGUAGE ViewPatterns #-}
-{-# LANGUAGE PackageImports #-} -- 2021-07-05: Due to hashing Haskell IT system situation, in HNix we currently ended-up with 2 hash package dependencies @{hashing, cryptonite}@
+{-# language CPP #-}
+{-# language AllowAmbiguousTypes #-}
+{-# language ConstraintKinds #-}
+{-# language FunctionalDependencies #-}
+{-# language KindSignatures #-}
+{-# language MonoLocalBinds #-}
+{-# language MultiWayIf #-}
+{-# language PartialTypeSignatures #-}
+{-# language QuasiQuotes #-}
+{-# language TemplateHaskell #-}
+{-# language UndecidableInstances #-}
+{-# language PackageImports #-} -- 2021-07-05: Due to hashing Haskell IT system situation, in HNix we currently ended-up with 2 hash package dependencies @{hashing, cryptonite}@
 
-{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
+{-# options_ghc -fno-warn-name-shadowing #-}
 
 -- | Code that implements Nix builtins. Lists the functions that are built into the Nix expression evaluator. Some built-ins (aka `derivation`), are always in the scope, so they can be accessed by the name. To keap the namespace clean, most built-ins are inside the `builtins` scope - a set that contains all what is a built-in.
 module Nix.Builtins
@@ -24,8 +21,7 @@
 where
 
 
-import           Prelude                 hiding ( traceM )
-import           Nix.Utils
+import           GHC.Exception                  ( ErrorCall(ErrorCall) )
 import           Control.Comonad                ( Comonad )
 import           Control.Monad                  ( foldM )
 import           Control.Monad.Catch            ( MonadCatch(catch) )
@@ -53,9 +49,9 @@
 import           Data.Scientific
 import qualified Data.Set                      as S
 import qualified Data.Text                     as Text
-import qualified Data.Text.Lazy                as LazyText
+import           Data.Text.Read                 ( decimal )
 import qualified Data.Text.Lazy.Builder        as Builder
-import           Data.These                     ( fromThese )
+import           Data.These                     ( fromThese, These )
 import qualified Data.Time.Clock.POSIX         as Time
 import qualified Data.Vector                   as V
 import           NeatInterpolation              ( text )
@@ -65,7 +61,6 @@
 import           Nix.Effects.Basic              ( fetchTarball )
 import           Nix.Exec
 import           Nix.Expr.Types
-import           Nix.Expr.Types.Annotated
 import qualified Nix.Eval                      as Eval
 import           Nix.Frames
 import           Nix.Json
@@ -81,11 +76,11 @@
 import           Nix.Value.Monad
 import           Nix.XML
 import           System.Nix.Base32             as Base32
-import           System.FilePath
-import           System.Posix.Files             ( isRegularFile
+import           System.PosixCompat.Files       ( isRegularFile
                                                 , isDirectory
                                                 , isSymbolicLink
                                                 )
+import qualified Text.Show
 import           Text.Regex.TDFA                ( Regex
                                                 , makeRegex
                                                 , matchOnceText
@@ -101,13 +96,13 @@
 
 -- ** Nix Builtins Haskell type level
 
-newtype Prim m a = Prim { runPrim :: m a }
+newtype Prim m a = Prim (m a)
 
 data BuiltinType = Normal | TopLevel
 data Builtin v =
   Builtin
     { _kind   :: BuiltinType
-    , mapping :: (Text, v)
+    , mapping :: (VarName, v)
     }
 
 -- *** @class ToBuiltin@ and its instances
@@ -121,7 +116,7 @@
   , ToValue a m (NValue t f m)
   )
   => ToBuiltin t f m (Prim m a) where
-  toBuiltin _ p = toValue =<< runPrim p
+  toBuiltin _ p = toValue @a @m =<< coerce p
 
 instance
   ( MonadNix e t f m
@@ -130,7 +125,7 @@
   )
   => ToBuiltin t f m (a -> b) where
   toBuiltin name f =
-    pure $ nvBuiltin name $ toBuiltin name . f <=< fromValue . Deeper
+    pure $ nvBuiltin (coerce name) $ toBuiltin name . f <=< fromValue . Deeper
 
 -- *** @WValue@ closure wrapper to have @Ord@
 
@@ -174,11 +169,28 @@
   -> NValue t f m
 mkNVBool = nvConstant . NBool
 
+data NixPathEntryType
+  = PathEntryPath
+  | PathEntryURI
+ deriving (Show, Eq)
+
+-- | @NIX_PATH@ is colon-separated, but can also contain URLs, which have a colon
+-- (i.e. @https://...@)
+uriAwareSplit :: Text -> [(Text, NixPathEntryType)]
+uriAwareSplit txt =
+  case Text.break (== ':') txt of
+    (e1, e2)
+      | Text.null e2                              -> one (e1, PathEntryPath)
+      | "://" `Text.isPrefixOf` e2      ->
+        let ((suffix, _) : path) = uriAwareSplit (Text.drop 3 e2) in
+        (e1 <> "://" <> suffix, PathEntryURI) : path
+      | otherwise                                 -> (e1, PathEntryPath) : uriAwareSplit (Text.drop 1 e2)
+
 foldNixPath
   :: forall e t f m r
    . MonadNix e t f m
   => r
-  -> (FilePath -> Maybe Text -> NixPathEntryType -> r -> m r)
+  -> (Path -> Maybe Text -> NixPathEntryType -> r -> m r)
   -> m r
 foldNixPath z f =
   do
@@ -193,36 +205,36 @@
     dataDir  <-
       maybe
         getDataDir
-        (pure . toString)
+        (pure . coerce . toString)
         mDataDir
 
     foldrM
       go
       z
       $ (fromInclude . stringIgnoreContext <$> dirs)
-        <> maybe
-            mempty
-            uriAwareSplit
-            mPath
-        <> [ fromInclude $ "nix=" <> toText dataDir <> "/nix/corepkgs" ]
+        <> uriAwareSplit `whenJust` mPath
+        <> one (fromInclude $ "nix=" <> fromString (coerce dataDir) <> "/nix/corepkgs")
  where
 
-  fromInclude x = (x, ) $
-    bool
-      PathEntryPath
-      PathEntryURI
-      ("://" `Text.isInfixOf` x)
+  fromInclude :: Text -> (Text, NixPathEntryType)
+  fromInclude x =
+    (x, ) $
+      bool
+        PathEntryPath
+        PathEntryURI
+        ("://" `Text.isInfixOf` x)
 
+  go :: (Text, NixPathEntryType) -> r -> m r
   go (x, ty) rest =
     case Text.splitOn "=" x of
-      [p] -> f (toString p) mempty ty rest
-      [n, p] -> f (toString p) (pure n) ty rest
+      [p] -> f (coerce $ toString p) mempty ty rest
+      [n, p] -> f (coerce $ toString p) (pure n) ty rest
       _ -> throwError $ ErrorCall $ "Unexpected entry in NIX_PATH: " <> show x
 
-attrsetGet :: MonadNix e t f m => Text -> AttrSet (NValue t f m) -> m (NValue t f m)
+attrsetGet :: MonadNix e t f m => VarName -> AttrSet (NValue t f m) -> m (NValue t f m)
 attrsetGet k s =
   maybe
-    (throwError $ ErrorCall $ "Attribute '" <> toString k <> "' required")
+    (throwError $ ErrorCall $ toString @Text $ "Attribute '" <> coerce k <> "' required")
     pure
     (M.lookup k s)
 
@@ -230,77 +242,82 @@
   = VersionComponentPre -- ^ The string "pre"
   | VersionComponentString !Text -- ^ A string other than "pre"
   | VersionComponentNumber !Integer -- ^ A number
-  deriving (Show, Read, Eq, Ord)
-
-versionComponentToString :: VersionComponent -> Text
-versionComponentToString =
-  \case
-    VersionComponentPre      -> "pre"
-    VersionComponentString s -> s
-    VersionComponentNumber n -> show n
+  deriving (Read, Eq, Ord)
 
--- | Based on https://github.com/NixOS/nix/blob/4ee4fda521137fed6af0446948b3877e0c5db803/src/libexpr/names.cc#L44
-versionComponentSeparators :: String
-versionComponentSeparators = ".-"
+instance Show VersionComponent where
+  show =
+    \case
+      VersionComponentPre      -> "pre"
+      VersionComponentString s -> show s
+      VersionComponentNumber n -> show n
 
 splitVersion :: Text -> [VersionComponent]
 splitVersion s =
-  case Text.uncons s of
-    Nothing -> mempty
-    Just (h, t)
+ whenJust
+   (\ (x, xs) -> if
+      | isRight eDigitsPart ->
+          either
+            (\ e -> error $ "splitVersion: did hit impossible: '" <> toText e <> "' while parsing '" <> s <> "'.")
+            (\ res ->
+              one (VersionComponentNumber $ fst res)
+              <> splitVersion (snd res)
+            )
+            eDigitsPart
 
-      | h `elem` versionComponentSeparators -> splitVersion t
+      | x `elem` separators -> splitVersion xs
 
-      | isDigit h ->
-        let (digits, rest) = Text.span isDigit s
-        in
-        VersionComponentNumber
-            (fromMaybe (error $ "splitVersion: couldn't parse " <> show digits) $ readMaybe $ toString digits) : splitVersion rest
+      | otherwise -> one charsPart <> splitVersion rest2
+  )
+  (Text.uncons s)
+ where
+  -- | Based on https://github.com/NixOS/nix/blob/4ee4fda521137fed6af0446948b3877e0c5db803/src/libexpr/names.cc#L44
+  separators :: String
+  separators = ".-"
 
-      | otherwise ->
-        let
-          (chars, rest) =
-            Text.span
-              (\c -> not $ isDigit c || c `elem` versionComponentSeparators)
-              s
-          thisComponent =
-            case chars of
-              "pre" -> VersionComponentPre
-              x     -> VersionComponentString x
-        in
-        thisComponent : splitVersion rest
+  eDigitsPart :: Either String (Integer, Text)
+  eDigitsPart = decimal @Integer $ s
 
+  (charsSpan, rest2) =
+    Text.span
+      (\c -> not $ isDigit c || c `elem` separators)
+      s
+
+  charsPart :: VersionComponent
+  charsPart =
+    case charsSpan of
+      "pre" -> VersionComponentPre
+      xs'   -> VersionComponentString xs'
+
+
 compareVersions :: Text -> Text -> Ordering
 compareVersions s1 s2 =
-  mconcat $
-    alignWith
-      f
-      (splitVersion s1)
-      (splitVersion s2)
+  fold $ (alignWith cmp `on` splitVersion) s1 s2
  where
-  z = VersionComponentString ""
-  f = uncurry compare . fromThese z z
+  cmp :: These VersionComponent VersionComponent -> Ordering
+  cmp = uncurry compare . join fromThese (VersionComponentString mempty)
 
 splitDrvName :: Text -> (Text, Text)
 splitDrvName s =
-  let
-    sep    = "-"
-    pieces = Text.splitOn sep s
-    isFirstVersionPiece p =
-      case Text.uncons p of
-        Just (h, _) -> isDigit h
-        _           -> False
-    -- Like 'break', but always puts the first item into the first result
-    -- list
-    breakAfterFirstItem :: (a -> Bool) -> [a] -> ([a], [a])
-    breakAfterFirstItem f =
-      list
-        (mempty, mempty)
-        (\ (h : t) -> let (a, b) = break f t in (h : a, b))
-    (namePieces, versionPieces) =
-      breakAfterFirstItem isFirstVersionPiece pieces
-  in
-  (Text.intercalate sep namePieces, Text.intercalate sep versionPieces)
+  both (Text.intercalate sep) (namePieces, versionPieces)
+ where
+  sep    = "-"
+  pieces :: [Text]
+  pieces = Text.splitOn sep s
+  isFirstVersionPiece :: Text -> Bool
+  isFirstVersionPiece p =
+    maybe
+      False
+      (isDigit . fst)
+      (Text.uncons p)
+  -- Like 'break', but always puts the first item into the first result
+  -- list
+  breakAfterFirstItem :: (a -> Bool) -> [a] -> ([a], [a])
+  breakAfterFirstItem f =
+    list
+      mempty
+      (\ (h : t) -> let (a, b) = break f t in (h : a, b))
+  (namePieces, versionPieces) =
+    breakAfterFirstItem isFirstVersionPiece pieces
 
 splitMatches
   :: forall e t f m
@@ -309,7 +326,7 @@
   -> [[(ByteString, (Int, Int))]]
   -> ByteString
   -> [NValue t f m]
-splitMatches _ [] haystack = [thunkStr haystack]
+splitMatches _ [] haystack = one $ thunkStr haystack
 splitMatches _ ([] : _) _ =
   fail "Fail in splitMatches: this should never happen!"
 splitMatches numDropped (((_, (start, len)) : captures) : mts) haystack =
@@ -319,7 +336,9 @@
  where
   relStart       = max 0 start - numDropped
   (before, rest) = B.splitAt relStart haystack
+  caps :: NValue t f m
   caps           = nvList (f <$> captures)
+  f :: (ByteString, (Int, b)) -> NValue t f m
   f (a, (s, _))  =
     bool
       nvNull
@@ -334,23 +353,18 @@
    . (MonadNix e t f m, FromValue a m (NValue t f m))
   => NValue t f m
   -> m (NValue t f m)
-hasKind nv =
-  do
-    v <- fromValueMay nv
-
-    toValue $
-      case v of
-        Just (_ :: a) -> True
-        _             -> False
+hasKind =
+  inHaskMay
+    (isJust @a)
 
 
-absolutePathFromValue :: MonadNix e t f m => NValue t f m -> m FilePath
+absolutePathFromValue :: MonadNix e t f m => NValue t f m -> m Path
 absolutePathFromValue =
   \case
     NVStr ns ->
       do
         let
-          path = toString $ stringIgnoreContext ns
+          path = coerce . toString $ stringIgnoreContext ns
 
         unless (isAbsolute path) $ throwError $ ErrorCall $ "string " <> show path <> " doesn't represent an absolute path"
         pure path
@@ -368,7 +382,7 @@
 
 instance Convertible e t f m => ToValue FileType m (NValue t f m) where
   toValue =
-    toValue . makeNixStringWithoutContext .
+    toValue . mkNixStringWithoutContext .
       \case
         FileTypeRegular   -> "regular" :: Text
         FileTypeDirectory -> "directory"
@@ -411,29 +425,26 @@
     [|| expr ||]
   )
 
-nixPathNix :: MonadNix e t f m => m (NValue t f m)
+nixPathNix :: forall e t f m . MonadNix e t f m => m (NValue t f m)
 nixPathNix =
   fmap
     nvList
-    (foldNixPath mempty $
-      \p mn ty rest ->
-        pure $
-          pure
-            (nvSet
-              mempty
-              (M.fromList
-                [case ty of
-                  PathEntryPath -> ("path", nvPath p)
-                  PathEntryURI  -> ( "uri", mkNvStr p)
+    $ foldNixPath mempty $
+        \p mn ty rest ->
+          pure $
+            pure
+              (nvSet
+                mempty
+                (M.fromList
+                  [case ty of
+                    PathEntryPath -> ("path", nvPath  p)
+                    PathEntryURI  -> ( "uri", nvStrWithoutContext $ fromString $ coerce p)
 
-                , ( "prefix", mkNvStr $ toString $ fromMaybe "" mn)
-                ]
+                  , ( "prefix", nvStrWithoutContext $ maybeToMonoid mn)
+                  ]
+                )
               )
-            )
-          <> rest
-    )
- where
-  mkNvStr = nvStrWithoutContext . toText
+            <> rest
 
 toStringNix :: MonadNix e t f m => NValue t f m -> m (NValue t f m)
 toStringNix = toValue <=< coerceToString callFunc DontCopyToStore CoerceAny
@@ -446,13 +457,13 @@
   -> m (NValue t f m)
 hasAttrNix x y =
   do
-    key <- fromStringNoContext =<< fromValue x
-    (aset, _) <- fromValue @(AttrSet (NValue t f m), AttrSet SourcePos) y
+    (coerce -> key) <- fromStringNoContext =<< fromValue x
+    (aset, _) <- fromValue @(AttrSet (NValue t f m), PositionSet) y
 
     toValue $ M.member key aset
 
 hasContextNix :: MonadNix e t f m => NValue t f m -> m (NValue t f m)
-hasContextNix = toValue . stringHasContext <=< fromValue
+hasContextNix = inHask stringHasContext
 
 getAttrNix
   :: forall e t f m
@@ -462,8 +473,8 @@
   -> m (NValue t f m)
 getAttrNix x y =
   do
-    key <- fromStringNoContext =<< fromValue x
-    (aset, _) <- fromValue @(AttrSet (NValue t f m), AttrSet SourcePos) y
+    (coerce -> key) <- fromStringNoContext =<< fromValue x
+    (aset, _) <- fromValue @(AttrSet (NValue t f m), PositionSet) y
 
     attrsetGet key aset
 
@@ -479,18 +490,18 @@
     y <- demand nvY
 
     case (x, y) of
-      (NVStr ns, NVSet _ apos) ->
+      (NVStr ns, NVSet apos _) ->
         maybe
           (pure nvNull)
           toValue
-          (M.lookup (stringIgnoreContext ns) apos)
+          (M.lookup @VarName (coerce $ stringIgnoreContext ns) apos)
       _xy -> throwError $ ErrorCall $ "Invalid types for builtins.unsafeGetAttrPosNix: " <> show _xy
 
 -- This function is a bit special in that it doesn't care about the contents
 -- of the list.
 lengthNix
   :: forall e t f m . MonadNix e t f m => NValue t f m -> m (NValue t f m)
-lengthNix = toValue . (length :: [NValue t f m] -> Int) <=< fromValue
+lengthNix = inHask (length :: [NValue t f m] -> Int)
 
 addNix
   :: MonadNix e t f m
@@ -587,7 +598,7 @@
 headNix =
   maybe
     (throwError $ ErrorCall "builtins.head: empty list")
-    (pure)
+    pure
   . viaNonEmpty head <=< fromValue @[NValue t f m]
 
 tailNix :: forall e t f m. MonadNix e t f m => NValue t f m -> m (NValue t f m)
@@ -603,7 +614,7 @@
     version <- fromStringNoContext =<< fromValue v
     pure $
       nvList $
-        nvStrWithoutContext . versionComponentToString <$>
+        nvStrWithoutContext . show <$>
           splitVersion version
 
 compareVersionsNix
@@ -639,7 +650,7 @@
 
     toValue @(AttrSet (NValue t f m)) $
       M.fromList
-        [ ( "name" :: Text
+        [ ( "name" :: VarName
           , mkNVStr name
           )
         , ( "version"
@@ -671,7 +682,7 @@
       mkMatch t =
         bool
           (toValue ()) -- Shorthand for Null
-          (toValue $ makeNixStringWithoutContext t)
+          (toValue $ mkNixStringWithoutContext t)
           (not $ Text.null t)
 
     case matchOnceText re s of
@@ -682,8 +693,8 @@
             traverse
               mkMatch
               (case submatches of
-                 [] -> []
-                 [a] -> [a]
+                 [] -> mempty
+                 [a] -> one a
                  _:xs -> xs -- return only the matched groups, drop the full string
               )
       _ -> pure nvNull
@@ -726,8 +737,10 @@
 attrNamesNix
   :: forall e t f m . MonadNix e t f m => NValue t f m -> m (NValue t f m)
 attrNamesNix =
-  (fmap (coerce :: CoerceDeeperToNValue t f m) . toValue . fmap makeNixStringWithoutContext . sort . M.keys)
-  <=< fromValue @(AttrSet (NValue t f m))
+    coersion . inHask @(AttrSet (NValue t f m))
+      (fmap (mkNixStringWithoutContext . coerce) . sort . M.keys)
+ where
+  coersion = fmap (coerce :: CoerceDeeperToNValue t f m)
 
 attrValuesNix
   :: forall e t f m . MonadNix e t f m => NValue t f m -> m (NValue t f m)
@@ -737,7 +750,7 @@
     toValue $
       snd <$>
         sortOn
-          (fst @Text @(NValue t f m))
+          (fst @VarName @(NValue t f m))
           (M.toList attrs)
 
 mapNix
@@ -747,13 +760,13 @@
   -> NValue t f m
   -> m (NValue t f m)
 mapNix f =
-  toValue <=<
-    traverse
-      (defer @(NValue t f m)
+  inHaskM @[NValue t f m]
+    (traverse
+      (defer
       . withFrame Debug (ErrorCall "While applying f in map:\n")
       . callFunc f
       )
-      <=< fromValue @[NValue t f m]
+    )
 
 mapAttrsNix
   :: forall e t f m
@@ -770,7 +783,7 @@
 
       applyFunToKeyVal (key, val) =
         do
-          runFunForKey <- callFunc f $ nvStrWithoutContext key
+          runFunForKey <- callFunc f $ nvStrWithoutContext (coerce key)
           callFunc runFunForKey val
 
     newVals <-
@@ -787,10 +800,11 @@
   -> NValue t f m
   -> m (NValue t f m)
 filterNix f =
-  toValue <=<
-    filterM
-      (fromValue <=< callFunc f)
-      <=< fromValue
+  inHaskM
+    (filterM fh)
+ where
+  fh :: NValue t f m -> m Bool
+  fh = fromValue <=< callFunc f
 
 catAttrsNix
   :: forall e t f m
@@ -805,7 +819,7 @@
 
     nvList . catMaybes <$>
       traverse
-        (fmap (M.lookup n) . fromValue <=< demand)
+        (fmap (M.lookup @VarName $ coerce n) . fromValue <=< demand)
         l
 
 baseNameOfNix :: MonadNix e t f m => NValue t f m -> m (NValue t f m)
@@ -815,7 +829,7 @@
     pure $
       nvStr $
         modifyNixContents
-          (toText . takeFileName . toString)
+          (fromString . coerce takeFileName . toString)
           ns
 
 bitAndNix
@@ -829,7 +843,7 @@
     a <- fromValue @Integer x
     b <- fromValue @Integer y
 
-    toValue (a .&. b)
+    toValue $ a .&. b
 
 bitOrNix
   :: forall e t f m
@@ -842,7 +856,7 @@
     a <- fromValue @Integer x
     b <- fromValue @Integer y
 
-    toValue (a .|. b)
+    toValue $ a .|. b
 
 bitXorNix
   :: forall e t f m
@@ -855,7 +869,7 @@
     a <- fromValue @Integer x
     b <- fromValue @Integer y
 
-    toValue (a `xor` b)
+    toValue $ a `xor` b
 
 builtinsBuiltinNix
   :: forall e t f m
@@ -869,16 +883,15 @@
     dir <- demand nvdir
 
     case dir of
-      NVStr ns -> pure $ nvStr $ modifyNixContents (toText . takeDirectory . toString) ns
+      NVStr ns -> pure $ nvStr $ modifyNixContents (fromString . coerce takeDirectory . toString) ns
       NVPath path -> pure $ nvPath $ takeDirectory path
       v -> throwError $ ErrorCall $ "dirOf: expected string or path, got " <> show v
 
 -- jww (2018-04-28): This should only be a string argument, and not coerced?
 unsafeDiscardStringContextNix
   :: MonadNix e t f m => NValue t f m -> m (NValue t f m)
-unsafeDiscardStringContextNix mnv = do
-  ns <- fromValue mnv
-  toValue $ makeNixStringWithoutContext $ stringIgnoreContext ns
+unsafeDiscardStringContextNix =
+  inHask (mkNixStringWithoutContext . stringIgnoreContext)
 
 -- | Evaluate `a` to WHNF to collect its topmost effect.
 seqNix
@@ -902,15 +915,18 @@
   => NValue t f m
   -> NValue t f m
   -> m (NValue t f m)
-elemNix x = toValue <=< anyMNix (valueEqM x) <=< fromValue
+elemNix x = inHaskM (anyMNix $ valueEqM x)
  where
   anyMNix :: Monad m => (a -> m Bool) -> [a] -> m Bool
-  anyMNix _ []       = pure False
-  anyMNix p (x : xs) =
-    bool
-      (anyMNix p xs)
-      (pure True)
-      =<< p x
+  anyMNix p =
+    list
+      (pure False)
+      (\ (x : xss) ->
+        bool
+          (anyMNix p xss)
+          (pure True)
+          =<< p x
+      )
 
 elemAtNix
   :: MonadNix e t f m
@@ -954,33 +970,32 @@
       do
         ss <- fromValue @[NValue t f m] =<< demand startSet
         op <- demand operator
+        let
+          go
+            :: Set (WValue t f m)
+            -> [NValue t f m]
+            -> m (Set (WValue t f m), [NValue t f m])
+          go ks []       = pure (ks, mempty)
+          go ks (t : ts) =
+            do
+              v <- demand t
+              k <- demand =<< attrsetGet "key" =<< fromValue @(AttrSet (NValue t f m)) v
 
-        toValue @[NValue t f m] =<< snd <$> go op mempty ss
- where
-  go
-    :: NValue t f m
-    -> Set (WValue t f m)
-    -> [NValue t f m]
-    -> m (Set (WValue t f m), [NValue t f m])
-  go _  ks []       = pure (ks, mempty)
-  go op ks (t : ts) =
-    do
-      v <- demand t
-      k <- demand =<< attrsetGet "key" =<< fromValue @(AttrSet (NValue t f m)) v
+              bool
+                (do
+                  checkComparable k $
+                    list
+                      k
+                      (\ (WValue j:_) -> j)
+                      (S.toList ks)
 
-      bool
-        (do
-          ys <- fromValue @[NValue t f m] =<< callFunc op v
-          checkComparable k
-            (case S.toList ks of
-              []           -> k
-              WValue j : _ -> j
-            )
-          (t :) <<$>> go op (S.insert (WValue k) ks) (ts <> ys)
-        )
-        (go op ks ts)
-        (S.member (WValue k) ks)
+                  (<<$>>) (v :) . go (S.insert (WValue k) ks) . (<>) ts =<< fromValue @[NValue t f m] =<< callFunc op v
+                )
+                (go ks ts)
+                (S.member (WValue k) ks)
 
+        toValue @[NValue t f m] =<< snd <$> go mempty ss
+
 -- | Takes:
 -- 1. List of strings to match.
 -- 2. List of strings to replace corresponding match occurance. (arg 1 & 2 lists matched by index)
@@ -1021,7 +1036,7 @@
         maybePrefixMatch :: Maybe (Text, NixString, Text)
         maybePrefixMatch = formMatchReplaceTailInfo <$> find ((`Text.isPrefixOf` input) . fst) fromKeysToValsMap
          where
-          formMatchReplaceTailInfo = (\(m, r) -> (m, r, Text.drop (Text.length m) input))
+          formMatchReplaceTailInfo (m, r) = (m, r, Text.drop (Text.length m) input)
 
           fromKeysToValsMap = zip (stringIgnoreContext <$> fromKeys) toVals
 
@@ -1034,7 +1049,7 @@
 
         --  2021-02-18: NOTE: rly?: toStrict . toLazyText
         --  Maybe `text-builder`, `text-show`?
-        finish ctx output = makeNixString (LazyText.toStrict $ Builder.toLazyText output) ctx
+        finish ctx output = mkNixString (toStrict $ Builder.toLazyText output) ctx
 
         replace (key, replacementNS, unprocessedInput) = replaceWithNixBug unprocessedInput updatedOutput
 
@@ -1080,11 +1095,12 @@
   -> m (NValue t f m)
 removeAttrsNix set v =
   do
-    (m, p) <- fromValue @(AttrSet (NValue t f m), AttrSet SourcePos) set
+    (m, p) <- fromValue @(AttrSet (NValue t f m), PositionSet) set
     (nsToRemove :: [NixString]) <- fromValue $ Deeper v
-    toRemove <- traverse fromStringNoContext nsToRemove
+    (coerce -> toRemove) <- traverse fromStringNoContext nsToRemove
     toValue (go m toRemove, go p toRemove)
  where
+  go :: forall k v . (Eq k, Hashable k) => HashMap k v -> [k] -> HashMap k v
   go = foldl' (flip M.delete)
 
 intersectAttrsNix
@@ -1095,8 +1111,8 @@
   -> m (NValue t f m)
 intersectAttrsNix set1 set2 =
   do
-    (s1, p1) <- fromValue @(AttrSet (NValue t f m), AttrSet SourcePos) set1
-    (s2, p2) <- fromValue @(AttrSet (NValue t f m), AttrSet SourcePos) set2
+    (s1, p1) <- fromValue @(AttrSet (NValue t f m), PositionSet) set1
+    (s2, p2) <- fromValue @(AttrSet (NValue t f m), PositionSet) set2
 
     pure $ nvSet (p2 `M.intersection` p1) (s2 `M.intersection` s1)
 
@@ -1110,7 +1126,7 @@
         toValue @(AttrSet (NValue t f m)) $ mkNVBool <$>
           case p of
             Param name     -> one (name, False)
-            ParamSet s _ _ -> isJust <$> M.fromList s
+            ParamSet _ _ pset -> isJust <$> M.fromList pset
       _v -> throwError $ ErrorCall $ "builtins.functionArgs: expected function, got " <> show _v
 
 toFileNix
@@ -1123,18 +1139,18 @@
     name' <- fromStringNoContext =<< fromValue name
     s'    <- fromValue s
     mres  <-
-      (toFile_ `on` toString)
-        name'
+      toFile_
+        (coerce $ toString name')
         (stringIgnoreContext s')
 
     let
-      t  = toText $ unStorePath mres
-      sc = StringContext t DirectPath
+      storepath  = coerce (fromString @Text) mres
+      sc = StringContext storepath DirectPath
 
-    toValue $ makeNixStringWithSingletonContext t sc
+    toValue $ mkNixStringWithSingletonContext storepath sc
 
 toPathNix :: MonadNix e t f m => NValue t f m -> m (NValue t f m)
-toPathNix = toValue @Path <=< fromValue @Path
+toPathNix = inHask @Path id
 
 pathExistsNix :: MonadNix e t f m => NValue t f m -> m (NValue t f m)
 pathExistsNix nvpath =
@@ -1143,7 +1159,7 @@
     toValue =<<
       case path of
         NVPath p  -> doesPathExist p
-        NVStr  ns -> doesPathExist $ toString $ stringIgnoreContext ns
+        NVStr  ns -> doesPathExist $ coerce $ toString $ stringIgnoreContext ns
         _v -> throwError $ ErrorCall $ "builtins.pathExists: expected path, got " <> show _v
 
 isAttrsNix
@@ -1258,8 +1274,8 @@
   -> m (NValue t f m)
 scopedImportNix asetArg pathArg =
   do
-    s        <- fromValue @(AttrSet (NValue t f m)) asetArg
-    (Path p) <- fromValue pathArg
+    (coerce -> scope) <- fromValue @(AttrSet (NValue t f m)) asetArg
+    p <- fromValue pathArg
 
     path  <- pathToDefaultNix @t @f @m p
     path' <-
@@ -1270,7 +1286,7 @@
         )
         (\ res ->
           do
-            (Path p') <- fromValue =<< demand res
+            p' <- fromValue @Path =<< demand res
 
             traceM $ "Current file being evaluated is: " <> show p'
             pure $ takeDirectory p' </> path
@@ -1279,36 +1295,34 @@
 
     clearScopes @(NValue t f m)
       $ withNixContext (pure path')
-      $ pushScope s
+      $ pushScope scope
       $ importPath @t @f @m path'
 
 getEnvNix :: MonadNix e t f m => NValue t f m -> m (NValue t f m)
 getEnvNix v =
-  do
-    s <- fromStringNoContext =<< fromValue v
-    mres <- getEnvVar s
-
-    toValue $ makeNixStringWithoutContext $
-      fromMaybe mempty mres
+  (toValue . mkNixStringWithoutContext . maybeToMonoid) =<< getEnvVar =<< fromStringNoContext =<< fromValue v
 
 sortNix
-  :: MonadNix e t f m
+  :: forall e t f m
+  . MonadNix e t f m
   => NValue t f m
   -> NValue t f m
   -> m (NValue t f m)
-sortNix comp = toValue <=< sortByM (cmp comp) <=< fromValue
+sortNix comp =
+  inHaskM (sortByM cmp)
  where
-  cmp f a b =
-    do
-      isLessThan <- (`callFunc` b) =<< callFunc f a
-      bool
-        (do
-          isGreaterThan <- (`callFunc` a) =<< callFunc f b
-          fromValue isGreaterThan <&>
-            bool EQ GT
-        )
-        (pure LT)
-        =<< fromValue isLessThan
+  cmp :: NValue t f m -> NValue t f m -> m Ordering
+  cmp a b =
+    bool
+      (fmap
+         (bool EQ GT)
+         (compare b a)
+      )
+      (pure LT)
+      =<< compare a b
+   where
+    compare :: NValue t f m -> NValue t f m -> m Bool
+    compare a2 a1 = fromValue =<< (`callFunc` a1) =<< callFunc comp a2
 
 lessThanNix
   :: MonadNix e t f m
@@ -1343,10 +1357,11 @@
   -> NValue t f m
   -> m (NValue t f m)
 concatWith f =
-  toValue . concat <=<
-    traverse
-      (fromValue @[NValue t f m] <=< f)
-      <=< fromValue @[NValue t f m]
+  toValue .
+    concat <=<
+      traverse
+        (fromValue @[NValue t f m] <=< f)
+        <=< fromValue @[NValue t f m]
 
 -- | Nix function of Haskell:
 -- > concat :: [[a]] -> [a]
@@ -1377,7 +1392,7 @@
         (\ nvattrset ->
           do
             a <- fromValue @(AttrSet (NValue t f m)) =<< demand nvattrset
-            name <- fromStringNoContext =<< fromValue =<< demand =<< attrsetGet "name" a
+            (coerce -> name) <- fromStringNoContext =<< fromValue =<< demand =<< attrsetGet "name" a
             val  <- attrsetGet "value" a
 
             pure (name, val)
@@ -1413,17 +1428,17 @@
         mkHash s = hash $ encodeUtf8 s
 
 
-placeHolderNix :: MonadNix e t f m => NValue t f m -> m (NValue t f m)
+placeHolderNix :: forall t f m e . MonadNix e t f m => NValue t f m -> m (NValue t f m)
 placeHolderNix p =
   do
     t <- fromStringNoContext =<< fromValue p
     h <-
-      runPrim $
-        (hashStringNix `on` makeNixStringWithoutContext)
+      coerce @(Prim m NixString) @(m NixString) $
+        (hashStringNix `on` mkNixStringWithoutContext)
           "sha256"
           ("nix-output:" <> t)
     toValue
-      $ makeNixStringWithoutContext
+      $ mkNixStringWithoutContext
       $ Text.cons '/'
       $ Base32.encode
       -- Please, stop Text -> Bytestring here after migration to Text
@@ -1440,7 +1455,7 @@
       bytes :: NixString -> ByteString
       bytes = encodeUtf8 . body
 
-      body h = stringIgnoreContext h
+      body = stringIgnoreContext
 
 readFileNix :: MonadNix e t f m => NValue t f m -> m (NValue t f m)
 readFileNix = toValue <=< Nix.Render.readFile <=< absolutePathFromValue <=< demand
@@ -1459,7 +1474,7 @@
     case (aset, filePath) of
       (NVList x, NVStr ns) ->
         do
-          mres <- findPath @t @f @m x $ toString $ stringIgnoreContext ns
+          mres <- findPath @t @f @m x $ coerce $ toString $ stringIgnoreContext ns
 
           pure $ nvPath mres
 
@@ -1475,6 +1490,7 @@
     items          <- listDirectory path
 
     let
+      detectFileTypes :: Path -> m (VarName, FileType)
       detectFileTypes item =
         do
           s <- getSymbolicLinkStatus $ path </> item
@@ -1486,7 +1502,7 @@
                 | isSymbolicLink s -> FileTypeSymlink
                 | otherwise        -> FileTypeUnknown
 
-          pure (toText item, t)
+          pure (coerce @Text @VarName $ toText item, t) -- function indeed binds filepaths as keys (VarNames) in Nix attrset.
 
     itemsWithTypes <-
       traverse
@@ -1563,7 +1579,7 @@
           NVBuiltin _ _ -> "lambda"
           _             -> error "Pattern synonyms obscure complete patterns"
 
-    toValue $ makeNixStringWithoutContext detectType
+    toValue $ mkNixStringWithoutContext detectType
 
 tryEvalNix
   :: forall e t f m . MonadNix e t f m => NValue t f m -> m (NValue t f m)
@@ -1623,7 +1639,7 @@
   :: forall e t f m . MonadNix e t f m => NValue t f m -> m (NValue t f m)
 fetchurlNix =
   (\case
-    NVSet s _ -> go (M.lookup "sha256" s) =<< demand =<< attrsetGet "url" s
+    NVSet _ s -> go (M.lookup "sha256" s) =<< demand =<< attrsetGet "url" s
     v@NVStr{} -> go Nothing v
     v -> throwError $ ErrorCall $ "builtins.fetchurl: Expected URI or set, got " <> show v
   ) <=< demand
@@ -1697,7 +1713,7 @@
     case v' of
       (NVStr ns) -> do
         let context = getNixLikeContext $ toNixLikeContext $ getContext ns
-        valued :: AttrSet (NValue t f m) <- sequenceA $ toValue <$> context
+        valued :: AttrSet (NValue t f m) <- traverseToValue context
         pure $ nvSet mempty valued
       x -> throwError $ ErrorCall $ "Invalid type for builtins.getContext: " <> show x
 
@@ -1713,7 +1729,7 @@
     y <- demand ty
 
     case (x, y) of
-      (NVStr ns, NVSet attrs _) ->
+      (NVStr ns, NVSet _ attrs) ->
         do
           newContextValues <- traverse getPathNOuts attrs
 
@@ -1727,7 +1743,7 @@
       x <- demand tx
 
       case x of
-        NVSet attrs _ ->
+        NVSet _ attrs->
           do
             -- TODO: Fail for unexpected keys.
 
@@ -1759,7 +1775,7 @@
         _x -> throwError $ ErrorCall $ "Invalid types for context value in builtins.appendContext: " <> show _x
 
   addContext ns newContextValues =
-    makeNixString
+    mkNixString
       (stringIgnoreContext ns)
       (fromNixLikeContext $
         NixLikeContext $
@@ -1772,18 +1788,18 @@
             )
       )
 
+nixVersionNix :: MonadNix e t f m => m (NValue t f m)
+nixVersionNix = toValue $ mkNixStringWithoutContext "2.3"
 
+langVersionNix :: MonadNix e t f m => m (NValue t f m)
+langVersionNix = toValue (5 :: Int)
+
 -- ** @builtinsList@
 
 builtinsList :: forall e t f m . MonadNix e t f m => m [Builtin (NValue t f m)]
-builtinsList = sequence
-  [ do
-      version <- toValue (makeNixStringWithoutContext "2.3")
-      pure $ Builtin Normal ("nixVersion", version)
-  , do
-      version <- toValue (5 :: Int)
-      pure $ Builtin Normal ("langVersion", version)
-
+builtinsList = sequenceA
+  [ add0 Normal   "nixVersion"       nixVersionNix
+  , add0 Normal   "langVersion"      langVersionNix
   , add  TopLevel "abort"            throwNix -- for now
   , add2 Normal   "add"              addNix
   , add2 Normal   "addErrorContext"  addErrorContextNix
@@ -1888,42 +1904,46 @@
   , add  Normal   "valueSize"        getRecursiveSizeNix
   ]
  where
+
+  arity0 :: a -> Prim m a
+  arity0 = Prim . pure
+
   arity1 :: (a -> b) -> (a -> Prim m b)
-  arity1 f = Prim . pure . f
+  arity1 g = arity0 . g
 
   arity2 :: (a -> b -> c) -> (a -> b -> Prim m c)
-  arity2 f = ((Prim . pure) .) . f
+  arity2 f = arity1 . f
 
-  mkBuiltin :: BuiltinType -> Text -> m (NValue t f m) -> m (Builtin (NValue t f m))
+  mkBuiltin :: BuiltinType -> VarName -> m (NValue t f m) -> m (Builtin (NValue t f m))
   mkBuiltin t n v = wrap t n <$> mkThunk n v
    where
-    wrap :: BuiltinType -> Text -> v -> Builtin v
+    wrap :: BuiltinType -> VarName -> v -> Builtin v
     wrap t n f = Builtin t (n, f)
 
-    mkThunk :: Text -> m (NValue t f m) -> m (NValue t f m)
+    mkThunk :: VarName -> m (NValue t f m) -> m (NValue t f m)
     mkThunk n = defer . withFrame Info (ErrorCall $ "While calling builtin " <> toString n <> "\n")
 
   hAdd
-    :: ( Text
+    :: ( VarName
       -> fun
       -> m (NValue t f m)
       )
     -> BuiltinType
-    -> Text
+    -> VarName
     -> fun
     -> m (Builtin (NValue t f m))
   hAdd f t n v = mkBuiltin t n $ f n v
 
   add0
     :: BuiltinType
-    -> Text
+    -> VarName
     -> m (NValue t f m)
     -> m (Builtin (NValue t f m))
   add0 = hAdd (\ _ x -> x)
 
   add
     :: BuiltinType
-    -> Text
+    -> VarName
     -> ( NValue t f m
       -> m (NValue t f m)
       )
@@ -1932,7 +1952,7 @@
 
   add2
     :: BuiltinType
-    -> Text
+    -> VarName
     -> ( NValue t f m
       -> NValue t f m
       -> m (NValue t f m)
@@ -1942,7 +1962,7 @@
 
   add3
     :: BuiltinType
-    -> Text
+    -> VarName
     -> ( NValue t f m
       -> NValue t f m
       -> NValue t f m
@@ -1954,10 +1974,10 @@
   add'
     :: ToBuiltin t f m a
     => BuiltinType
-    -> Text
+    -> VarName
     -> a
     -> m (Builtin (NValue t f m))
-  add' = hAdd toBuiltin
+  add' = hAdd (toBuiltin . coerce)
 
 
 -- * Exported
@@ -1966,7 +1986,7 @@
 withNixContext
   :: forall e t f m r
    . (MonadNix e t f m, Has e Options)
-  => Maybe FilePath
+  => Maybe Path
   -> m r
   -> m r
 withNixContext mpath action =
@@ -1977,7 +1997,7 @@
       i = nvList $ nvStrWithoutContext . toText <$> include opts
 
     pushScope
-      (one ("__includes", i))
+      (coerce $ M.fromList $ one ("__includes", i))
       (pushScopes
         base $
         maybe
@@ -1986,29 +2006,35 @@
             do
               traceM $ "Setting __cur_file = " <> show path
               let ref = nvPath path
-              pushScope (one ("__cur_file", ref)) act
+              pushScope (coerce $ M.fromList $ one ("__cur_file", ref)) act
           )
           mpath
           action
       )
 
 builtins
-  :: ( MonadNix e t f m
+  :: forall e t f m
+  . ( MonadNix e t f m
      , Scoped (NValue t f m) m
      )
   => m (Scopes m (NValue t f m))
 builtins =
   do
     ref <- defer $ nvSet mempty <$> buildMap
-    lst <- ([("builtins", ref)] <>) <$> topLevelBuiltins
-    pushScope (M.fromList lst) currentScopes
+    lst <- (one ("builtins", ref) <>) <$> topLevelBuiltins
+    pushScope (coerce (M.fromList lst)) currentScopes
  where
-  buildMap         =  fmap (M.fromList . fmap mapping) builtinsList
+  buildMap :: m (HashMap VarName (NValue t f m))
+  buildMap         =  M.fromList . (mapping <$>) <$> builtinsList
+
+  topLevelBuiltins :: m [(VarName, NValue t f m)]
   topLevelBuiltins = mapping <<$>> fullBuiltinsList
 
+  fullBuiltinsList :: m [Builtin (NValue t f m)]
   fullBuiltinsList = nameBuiltins <<$>> builtinsList
    where
+    nameBuiltins :: Builtin v -> Builtin v
     nameBuiltins b@(Builtin TopLevel _) = b
     nameBuiltins (Builtin Normal nB) =
-      Builtin TopLevel $ first ("__" <>) nB
+      Builtin TopLevel $ first (coerce @Text . ("__" <>) . coerce @VarName) nB
 
diff --git a/src/Nix/Cache.hs b/src/Nix/Cache.hs
--- a/src/Nix/Cache.hs
+++ b/src/Nix/Cache.hs
@@ -1,9 +1,9 @@
-{-# LANGUAGE CPP #-}
+{-# language CPP #-}
 
 -- | Reading and writing Nix cache files
 module Nix.Cache where
 
-import qualified Data.ByteString.Lazy          as BS
+import qualified Data.ByteString.Lazy          as BSL
 import           Nix.Expr.Types.Annotated
 
 #if defined (__linux__)
@@ -17,7 +17,7 @@
 #endif
 import qualified Codec.Serialise               as S
 
-readCache :: FilePath -> IO NExprLoc
+readCache :: Path -> IO NExprLoc
 readCache path = do
 #if USE_COMPACT
   eres <- C.unsafeReadCompact path
@@ -26,17 +26,17 @@
     (\ expr -> pure $ C.getCompact expr)
     eres
 #else
-  eres <- S.deserialiseOrFail <$> BS.readFile path
+  eres <- S.deserialiseOrFail <$> BSL.readFile (coerce path)
   either
     (\ err  -> fail $ "Error reading cache file: " <> show err)
     pure
     eres
 #endif
 
-writeCache :: FilePath -> NExprLoc -> IO ()
+writeCache :: Path -> NExprLoc -> IO ()
 writeCache path expr =
 #ifdef USE_COMPACT
   C.writeCompact path =<< C.compact expr
 #else
-  BS.writeFile path (S.serialise expr)
+  BSL.writeFile (coerce path) (S.serialise expr)
 #endif
diff --git a/src/Nix/Cited.hs b/src/Nix/Cited.hs
--- a/src/Nix/Cited.hs
+++ b/src/Nix/Cited.hs
@@ -1,7 +1,7 @@
-{-# LANGUAGE DeriveAnyClass #-}
-{-# LANGUAGE TemplateHaskell #-}
+{-# language DeriveAnyClass #-}
+{-# language TemplateHaskell #-}
 
-{-# OPTIONS_GHC -Wno-missing-signatures #-}
+{-# options_ghc -Wno-missing-signatures #-}
 
 module Nix.Cited where
 
diff --git a/src/Nix/Cited/Basic.hs b/src/Nix/Cited/Basic.hs
--- a/src/Nix/Cited/Basic.hs
+++ b/src/Nix/Cited/Basic.hs
@@ -1,12 +1,9 @@
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE UndecidableInstances #-}
-{-# LANGUAGE ViewPatterns #-}
-{-# LANGUAGE PatternSynonyms    #-}
+{-# language GeneralizedNewtypeDeriving #-}
+{-# language UndecidableInstances #-}
+{-# language PatternSynonyms    #-}
 
 module Nix.Cited.Basic where
 
-import           Prelude                 hiding ( force )
 import           Control.Comonad                ( Comonad )
 import           Control.Comonad.Env            ( ComonadEnv )
 import           Control.Monad.Catch     hiding ( catchJust )
@@ -14,11 +11,10 @@
 import           Nix.Eval                      as Eval
                                                 ( EvalFrame(EvaluatingExpr,ForcingExpr) )
 import           Nix.Exec
-import           Nix.Expr
+import           Nix.Expr.Types.Annotated
 import           Nix.Frames
 import           Nix.Options
 import           Nix.Thunk
-import           Nix.Utils
 import           Nix.Value
 
 
@@ -94,11 +90,11 @@
             -- Gather the current evaluation context at the time of thunk
             -- creation, and record it along with the thunk.
             let
-              go (fromException -> Just (EvaluatingExpr scope (AnnE s e))) =
-                let e' = AnnFP s (Nothing <$ e) in
-                [Provenance scope e']
+              go :: SomeException -> [Provenance m (NValue u f m)]
+              go (fromException -> Just (EvaluatingExpr scope (Ann s e))) =
+                one $ Provenance scope $ AnnF s (Nothing <$ e)
               go _ = mempty
-              ps = concatMap (go . frame) frames
+              ps = foldMap (go . frame) frames
 
             cite ps t
         )
@@ -182,6 +178,6 @@
 displayProvenance =
   list
     id
-    (\ (Provenance scope e@(AnnFP s _) : _) ->
+    (\ (Provenance scope e@(AnnF s _) : _) ->
       withFrame Info $ ForcingExpr scope $ wrapExprLoc s e
     )
diff --git a/src/Nix/Context.hs b/src/Nix/Context.hs
--- a/src/Nix/Context.hs
+++ b/src/Nix/Context.hs
@@ -7,8 +7,8 @@
 import           Nix.Expr.Types.Annotated       ( SrcSpan
                                                 , nullSpan
                                                 )
-import           Nix.Utils                      ( Has(..) )
 
+--  2021-07-18: NOTE: It should be Options -> Scopes -> Frames -> Source(span)
 data Context m t = Context
     { scopes  :: Scopes m t
     , source  :: SrcSpan
diff --git a/src/Nix/Convert.hs b/src/Nix/Convert.hs
--- a/src/Nix/Convert.hs
+++ b/src/Nix/Convert.hs
@@ -1,11 +1,10 @@
-{-# LANGUAGE AllowAmbiguousTypes #-}
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE IncoherentInstances #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE UndecidableInstances #-}
+{-# language AllowAmbiguousTypes #-}
+{-# language ConstraintKinds #-}
+{-# language IncoherentInstances #-}
+{-# language TypeFamilies #-}
+{-# language UndecidableInstances #-}
 
-{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
+{-# options_ghc -fno-warn-name-shadowing #-}
 
 -- | Although there are a lot of instances in this file, really it's just a
 --   combinatorial explosion of the following combinations:
@@ -16,7 +15,6 @@
 
 module Nix.Convert where
 
-import           Prelude                 hiding ( force )
 import           Control.Monad.Free
 import qualified Data.HashMap.Lazy             as M
 import           Nix.Atoms
@@ -28,7 +26,6 @@
 import           Nix.Value
 import           Nix.Value.Monad
 import           Nix.Thunk                      ( MonadThunk(force) )
-import           Nix.Utils
 
 newtype Deeper a = Deeper a
   deriving (Typeable, Functor, Foldable, Traversable)
@@ -50,13 +47,39 @@
 -}
 
 
+type Convertible e t f m
+  = (Framed e m, MonadDataErrorContext t f m, MonadThunk t m (NValue t f m))
+
+-- | Transform Nix -> Hask. Run function. Convert Hask -> Nix.
+inHask :: forall a1 a2 v b m . (Monad m, FromValue a1 m v, ToValue a2 m b) => (a1 -> a2) -> v -> m b
+inHask f = toValue . f <=< fromValue
+
+inHaskM :: forall a1 a2 v b m . (Monad m, FromValue a1 m v, ToValue a2 m b) => (a1 -> m a2) -> v -> m b
+inHaskM f = toValue <=< f <=< fromValue
+
+-- | Maybe transform Nix -> Hask. Run function. Convert Hask -> Nix.
+inHaskMay :: forall a1 a2 v b m . (Monad m, FromValue a1 m v, ToValue a2 m b) => (Maybe a1 -> a2) -> v -> m b
+inHaskMay f = toValue . f <=< fromValueMay
+
+
 -- * FromValue
 
 class FromValue a m v where
   fromValue    :: v -> m a
   fromValueMay :: v -> m (Maybe a)
 
+traverseFromM
+  :: ( Applicative m
+     , Traversable t
+     , FromValue b m a
+     )
+  => t a
+  -> m (Maybe (t b))
+traverseFromM = traverseM fromValueMay
 
+traverseToValue :: ((Traversable t, Applicative f, ToValue a f b) => t a -> f (t b))
+traverseToValue = traverse toValue
+
 -- Please, hide these helper function from export, to be sure they get optimized away.
 fromMayToValue
   :: forall t f m a e
@@ -67,12 +90,10 @@
   -> NValue' t f m (NValue t f m)
   -> m a
 fromMayToValue t v =
-  do
-    v' <- fromValueMay v
-    maybe
-      (throwError $ Expectation @t @f @m t (Free v))
-      pure
-      v'
+  maybe
+    (throwError $ Expectation @t @f @m t (Free v))
+    pure
+    =<< fromValueMay v
 
 fromMayToDeeperValue
   :: forall t f m a e m1
@@ -83,15 +104,10 @@
   -> Deeper (NValue' t f m (NValue t f m))
   -> m (m1 a)
 fromMayToDeeperValue t v =
-  do
-    v' <- fromValueMay v
-    maybe
-      (throwError $ Expectation @t @f @m t $ Free $ (coerce :: CoerceDeeperToNValue' t f m) v)
-      pure
-      v'
-
-type Convertible e t f m
-  = (Framed e m, MonadDataErrorContext t f m, MonadThunk t m (NValue t f m))
+  maybe
+    (throwError $ Expectation @t @f @m t $ Free $ (coerce :: CoerceDeeperToNValue' t f m) v)
+    pure
+    =<< fromValueMay v
 
 instance ( Convertible e t f m
          , MonadValue (NValue t f m) m
@@ -135,7 +151,7 @@
   fromValueMay =
     pure .
       \case
-        NVConstant' NNull -> pass
+        NVConstant' NNull -> stub
         _                 -> mempty
 
   fromValue = fromMayToValue TNull
@@ -195,17 +211,18 @@
     \case
       NVStr' ns -> pure $ pure ns
       NVPath' p ->
-        fmap
-          (pure . (\s -> makeNixStringWithSingletonContext s (StringContext s DirectPath)) . toText . unStorePath)
-          (addPath p)
-      NVSet' s _ ->
+        (\path -> pure $ mkNixStringWithSingletonContext path (StringContext path DirectPath)) . fromString . coerce <$>
+          addPath p
+      NVSet' _ s ->
         maybe
           stub
           fromValueMay
           (M.lookup "outPath" s)
       _ -> stub
 
-  fromValue = fromMayToValue $ TString NoContext
+  --  2021-07-18: NOTE: There may be cases where conversion wrongly marks the content to have a context.
+  --  See: https://github.com/haskell-nix/hnix/pull/958#issuecomment-881949183 thread.
+  fromValue = fromMayToValue $ TString HasContext
 
 instance Convertible e t f m
   => FromValue ByteString m (NValue' t f m (NValue t f m)) where
@@ -213,15 +230,22 @@
   fromValueMay =
     pure .
       \case
-        NVStr' ns -> encodeUtf8 <$> getStringNoContext  ns
+        NVStr' ns -> encodeUtf8 <$> getStringNoContext ns
         _         -> mempty
 
-  fromValue = fromMayToValue $ TString NoContext
+  fromValue = fromMayToValue $ TString mempty
 
+instance Convertible e t f m
+  => FromValue Text m (NValue' t f m (NValue t f m)) where
 
-newtype Path = Path { getPath :: FilePath }
-    deriving Show
+  fromValueMay =
+    pure .
+      \case
+        NVStr' ns -> getStringNoContext ns
+        _         -> mempty
 
+  fromValue = fromMayToValue $ TString mempty
+
 instance ( Convertible e t f m
          , MonadValue (NValue t f m) m
          )
@@ -229,14 +253,14 @@
 
   fromValueMay =
     \case
-      NVPath' p  -> pure $ pure $ Path p
-      NVStr'  ns -> pure $ Path . toString <$> getStringNoContext  ns
-      NVSet' s _ ->
+      NVPath' p  -> pure $ pure $ coerce p
+      NVStr'  ns -> pure $ coerce . toString <$> getStringNoContext  ns
+      NVSet' _ s ->
         maybe
-          (pure Nothing)
+          stub
           (fromValueMay @Path)
           (M.lookup "outPath" s)
-      _ -> pure Nothing
+      _ -> stub
 
   fromValue = fromMayToValue TPath
 
@@ -257,7 +281,7 @@
   => FromValue [a] m (Deeper (NValue' t f m (NValue t f m))) where
   fromValueMay =
     \case
-      Deeper (NVList' l) -> sequence <$> traverse fromValueMay l
+      Deeper (NVList' l) -> traverseFromM l
       _                  -> stub
 
 
@@ -269,7 +293,7 @@
   fromValueMay =
     pure .
       \case
-        NVSet' s _ -> pure s
+        NVSet' _ s -> pure s
         _          -> mempty
 
   fromValue = fromMayToValue TSet
@@ -281,19 +305,19 @@
 
   fromValueMay =
     \case
-      Deeper (NVSet' s _) -> sequence <$> traverse fromValueMay s
+      Deeper (NVSet' _ s) -> traverseFromM s
       _                   -> stub
 
   fromValue = fromMayToDeeperValue TSet
 
 instance Convertible e t f m
-  => FromValue (AttrSet (NValue t f m), AttrSet SourcePos) m
+  => FromValue (AttrSet (NValue t f m), PositionSet) m
               (NValue' t f m (NValue t f m)) where
 
   fromValueMay =
     pure .
       \case
-        NVSet' s p -> pure (s, p)
+        NVSet' p s -> pure (s, p)
         _          -> mempty
 
   fromValue = fromMayToValue TSet
@@ -301,12 +325,12 @@
 instance ( Convertible e t f m
          , FromValue a m (NValue t f m)
          )
-  => FromValue (AttrSet a, AttrSet SourcePos) m
+  => FromValue (AttrSet a, PositionSet) m
               (Deeper (NValue' t f m (NValue t f m))) where
 
   fromValueMay =
     \case
-      Deeper (NVSet' s p) -> fmap (, p) . sequence <$> traverse fromValueMay s
+      Deeper (NVSet' p s) -> (, p) <<$>> traverseFromM s
       _                   -> stub
 
   fromValue = fromMayToDeeperValue TSet
@@ -325,13 +349,15 @@
 class ToValue a m v where
   toValue :: a -> m v
 
-instance (Convertible e t f m, ToValue a m (NValue' t f m (NValue t f m)))
+instance (Convertible e t f m
+  , ToValue a m (NValue' t f m (NValue t f m))
+  )
   => ToValue a m (NValue t f m) where
   toValue v = Free <$> toValue v
 
 instance ( Convertible e t f m
-         , ToValue a m (Deeper (NValue' t f m (NValue t f m)))
-         )
+  , ToValue a m (Deeper (NValue' t f m (NValue t f m)))
+  )
   => ToValue a m (Deeper (NValue t f m)) where
   toValue v = Free <<$>> toValue v
 
@@ -361,24 +387,27 @@
 
 instance Convertible e t f m
   => ToValue ByteString m (NValue' t f m (NValue t f m)) where
-  toValue = pure . nvStr' . makeNixStringWithoutContext . decodeUtf8
+  toValue = pure . nvStr' . mkNixStringWithoutContext . decodeUtf8
 
 instance Convertible e t f m
+  => ToValue Text m (NValue' t f m (NValue t f m)) where
+  toValue = pure . nvStr' . mkNixStringWithoutContext
+
+instance Convertible e t f m
   => ToValue Path m (NValue' t f m (NValue t f m)) where
-  toValue = pure . nvPath' . getPath
+  toValue = pure . nvPath' . coerce
 
 instance Convertible e t f m
   => ToValue StorePath m (NValue' t f m (NValue t f m)) where
-  toValue = toValue . Path . unStorePath
+  toValue = toValue @Path . coerce
 
-instance ( Convertible e t f m
-         )
+instance Convertible e t f m
   => ToValue SourcePos m (NValue' t f m (NValue t f m)) where
   toValue (SourcePos f l c) = do
-    f' <- toValue $ makeNixStringWithoutContext $ toText f
+    f' <- toValue $ mkNixStringWithoutContext $ fromString f
     l' <- toValue $ unPos l
     c' <- toValue $ unPos c
-    let pos = M.fromList [("file" :: Text, f'), ("line", l'), ("column", c')]
+    let pos = M.fromList [("file" :: VarName, f'), ("line", l'), ("column", c')]
     pure $ nvSet' mempty pos
 
 -- | With 'ToValue', we can always act recursively
@@ -386,9 +415,11 @@
   => ToValue [NValue t f m] m (NValue' t f m (NValue t f m)) where
   toValue = pure . nvList'
 
-instance (Convertible e t f m, ToValue a m (NValue t f m))
+instance (Convertible e t f m
+  , ToValue a m (NValue t f m)
+  )
   => ToValue [a] m (Deeper (NValue' t f m (NValue t f m))) where
-  toValue l = Deeper . nvList' <$> traverse toValue l
+  toValue l = Deeper . nvList' <$> traverseToValue l
 
 instance Convertible e t f m
   => ToValue (AttrSet (NValue t f m)) m (NValue' t f m (NValue t f m)) where
@@ -398,20 +429,20 @@
   => ToValue (AttrSet a) m (Deeper (NValue' t f m (NValue t f m))) where
   toValue s =
     liftA2 (\ v s -> Deeper $ nvSet' s v)
-      (traverse toValue s)
+      (traverseToValue s)
       stub
 
 instance Convertible e t f m
-  => ToValue (AttrSet (NValue t f m), AttrSet SourcePos) m
+  => ToValue (AttrSet (NValue t f m), PositionSet) m
             (NValue' t f m (NValue t f m)) where
   toValue (s, p) = pure $ nvSet' p s
 
 instance (Convertible e t f m, ToValue a m (NValue t f m))
-  => ToValue (AttrSet a, AttrSet SourcePos) m
+  => ToValue (AttrSet a, PositionSet) m
             (Deeper (NValue' t f m (NValue t f m))) where
   toValue (s, p) =
     liftA2 (\ v s -> Deeper $ nvSet' s v)
-      (traverse toValue s)
+      (traverseToValue s)
       (pure p)
 
 instance Convertible e t f m
@@ -427,9 +458,9 @@
     allOutputs <- g nlcvAllOutputs
     outputs <- do
       let
-        outputs = makeNixStringWithoutContext <$> nlcvOutputs nlcv
+        outputs = mkNixStringWithoutContext <$> nlcvOutputs nlcv
 
-      ts :: [NValue t f m] <- traverse toValue outputs
+      ts :: [NValue t f m] <- traverseToValue outputs
       list
         (pure Nothing)
         (fmap pure . toValue)
diff --git a/src/Nix/Effects.hs b/src/Nix/Effects.hs
--- a/src/Nix/Effects.hs
+++ b/src/Nix/Effects.hs
@@ -1,24 +1,21 @@
-{-# LANGUAGE AllowAmbiguousTypes #-}
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE DefaultSignatures #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE DataKinds #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE UndecidableInstances #-}
-{-# LANGUAGE PackageImports #-} -- 2021-07-05: Due to hashing Haskell IT system situation, in HNix we currently ended-up with 2 hash package dependencies @{hashing, cryptonite}@
+{-# language AllowAmbiguousTypes #-}
+{-# language CPP #-}
+{-# language DefaultSignatures #-}
+{-# language TypeFamilies #-}
+{-# language DataKinds #-}
+{-# language GeneralizedNewtypeDeriving #-}
+{-# language UndecidableInstances #-}
+{-# language PackageImports #-} -- 2021-07-05: Due to hashing Haskell IT system situation, in HNix we currently ended-up with 2 hash package dependencies @{hashing, cryptonite}@
 
-{-# OPTIONS_GHC -Wno-orphans #-}
+{-# options_ghc -Wno-orphans #-}
 
 
 module Nix.Effects where
 
-import           Prelude                 hiding ( traceM
-                                                , putStr
-                                                , putStrLn
+import           Prelude                 hiding ( putStrLn
                                                 , print
                                                 )
-import qualified Prelude
-import           Nix.Utils
+import           GHC.Exception                  ( ErrorCall(ErrorCall) )
 import qualified Data.HashSet                  as HS
 import qualified Data.Text                     as Text
 import           Network.HTTP.Client     hiding ( path, Proxy )
@@ -26,15 +23,13 @@
 import           Network.HTTP.Types
 import qualified "cryptonite" Crypto.Hash      as Hash
 import           Nix.Utils.Fix1
-import           Nix.Expr
+import           Nix.Expr.Types.Annotated
 import           Nix.Frames              hiding ( Proxy )
 import           Nix.Parser
 import           Nix.Render
 import           Nix.Value
 import qualified Paths_hnix
 import           System.Exit
-import qualified System.Environment            as Env
-import           System.FilePath                ( takeFileName )
 import qualified System.Info
 import           System.Process
 
@@ -42,7 +37,7 @@
 import qualified System.Nix.StorePath          as Store
 
 -- | A path into the nix store
-newtype StorePath = StorePath { unStorePath :: FilePath }
+newtype StorePath = StorePath Path
 
 
 -- All of the following type classes defer to the underlying 'm'.
@@ -62,16 +57,15 @@
   )
   => MonadEffects t f m where
 
-  -- | Determine the absolute path of relative path in the current context
-  makeAbsolutePath :: FilePath -> m FilePath
-  findEnvPath :: String -> m FilePath
+  -- | Determine the absolute path in the current context.
+  toAbsolutePath :: Path -> m Path
+  findEnvPath :: String -> m Path
 
-  -- | Having an explicit list of sets corresponding to the NIX_PATH
-  -- and a file path try to find an existing path
-  findPath :: [NValue t f m] -> FilePath -> m FilePath
+  -- | Having an explicit list of sets corresponding to the @NIX_PATH@ and a file path try to find an existing path.
+  findPath :: [NValue t f m] -> Path -> m Path
 
-  importPath :: FilePath -> m (NValue t f m)
-  pathToDefaultNix :: FilePath -> m FilePath
+  importPath :: Path -> m (NValue t f m)
+  pathToDefaultNix :: Path -> m Path
 
   derivationStrict :: NValue t f m -> m (NValue t f m)
 
@@ -108,7 +102,7 @@
 #ifdef MIN_VERSION_ghc_datasize
     recursiveSize
 #else
-    \_ -> pure 0
+    const $ pure 0
 #endif
 
 deriving
@@ -140,9 +134,9 @@
   exec' = \case
     []            -> pure $ Left $ ErrorCall "exec: missing program"
     (prog : args) -> do
-      (exitCode, out, _) <- liftIO $ readProcessWithExitCode (toString prog) (toString <$> args) ""
+      (exitCode, out, _) <- liftIO $ readProcessWithExitCode (toString prog) (toString <$> args) mempty
       let
-        t    = Text.strip $ toText out
+        t    = Text.strip $ fromString out
         emsg = "program[" <> prog <> "] args=" <> show args
       case exitCode of
         ExitSuccess ->
@@ -191,7 +185,7 @@
         readProcessWithExitCode
           "nix-instantiate"
           ["--eval", "--expr", toString expr]
-          ""
+          mempty
 
       pure $
         case exitCode of
@@ -199,7 +193,7 @@
             either
               (\ e -> Left $ ErrorCall $ "Error parsing output of nix-instantiate: " <> show e)
               pure
-              (parseNixTextLoc $ toText out)
+              (parseNixTextLoc $ fromString out)
           status -> Left $ ErrorCall $ "nix-instantiate failed: " <> show status <> ": " <> err
 
 deriving
@@ -235,12 +229,12 @@
 -- ** Instances
 
 instance MonadEnv IO where
-  getEnvVar            = (<<$>>) toText . Env.lookupEnv . toString
+  getEnvVar            = (<<$>>) fromString . lookupEnv . toString
 
-  getCurrentSystemOS   = pure $ toText System.Info.os
+  getCurrentSystemOS   = pure $ fromString System.Info.os
 
   -- Invert the conversion done by GHC_CONVERT_CPU in GHC's aclocal.m4
-  getCurrentSystemArch = pure $ toText $ case System.Info.arch of
+  getCurrentSystemArch = pure $ fromString $ case System.Info.arch of
     "i386" -> "i686"
     arch   -> arch
 
@@ -260,15 +254,15 @@
 class
   Monad m
   => MonadPaths m where
-  getDataDir :: m FilePath
-  default getDataDir :: (MonadTrans t, MonadPaths m', m ~ t m') => m FilePath
+  getDataDir :: m Path
+  default getDataDir :: (MonadTrans t, MonadPaths m', m ~ t m') => m Path
   getDataDir = lift getDataDir
 
 
 -- ** Instances
 
 instance MonadPaths IO where
-  getDataDir = Paths_hnix.getDataDir
+  getDataDir = coerce Paths_hnix.getDataDir
 
 deriving
   instance
@@ -328,7 +322,7 @@
 -- * @class MonadPutStr m@
 
 class
-  Monad m
+  (Monad m, MonadIO m)
   => MonadPutStr m where
 
   --TODO: Should this be used *only* when the Nix to be evaluated invokes a
@@ -336,7 +330,7 @@
   --  2021-04-01: Due to trace operation here, leaving it as String.
   putStr :: String -> m ()
   default putStr :: (MonadTrans t, MonadPutStr m', m ~ t m') => String -> m ()
-  putStr = lift . putStr
+  putStr = lift . Prelude.putStr
 
 
 -- ** Instances
@@ -358,7 +352,7 @@
 -- ** Functions
 
 putStrLn :: MonadPutStr m => String -> m ()
-putStrLn = putStr . (<> "\n")
+putStrLn = Nix.Effects.putStr . (<> "\n")
 
 print :: (MonadPutStr m, Show a) => a -> m ()
 print = putStrLn . show
@@ -370,7 +364,7 @@
 type RecursiveFlag = Bool
 type RepairFlag = Bool
 type StorePathName = Text
-type FilePathFilter m = FilePath -> m Bool
+type PathFilter m = Path -> m Bool
 type StorePathSet = HS.HashSet StorePath
 
 -- ** @class MonadStore m@
@@ -382,8 +376,8 @@
   -- | Copy the contents of a local path to the store.  The resulting store
   -- path is returned.  Note: This does not support yet support the expected
   -- `filter` function that allows excluding some files.
-  addToStore :: StorePathName -> FilePath -> RecursiveFlag -> RepairFlag -> m (Either ErrorCall StorePath)
-  default addToStore :: (MonadTrans t, MonadStore m', m ~ t m') => StorePathName -> FilePath -> RecursiveFlag -> RepairFlag -> m (Either ErrorCall StorePath)
+  addToStore :: StorePathName -> Path -> RecursiveFlag -> RepairFlag -> m (Either ErrorCall StorePath)
+  default addToStore :: (MonadTrans t, MonadStore m', m ~ t m') => StorePathName -> Path -> RecursiveFlag -> RepairFlag -> m (Either ErrorCall StorePath)
   addToStore a b c d = lift $ addToStore a b c d
 
   -- | Like addToStore, but the contents written to the output path is a
@@ -403,10 +397,10 @@
       (\ pathName ->
         do
           -- TODO: redesign the filter parameter
-          res <- Store.Remote.runStore $ Store.Remote.addToStore @Hash.SHA256 pathName path recursive (const False) repair
+          res <- Store.Remote.runStore $ Store.Remote.addToStore @Hash.SHA256 pathName (coerce path) recursive (const False) repair
           either
             Left -- err
-            (pure . StorePath . decodeUtf8 . Store.storePathToRawFilePath) -- store path
+            (pure . StorePath . coerce . decodeUtf8 @FilePath @ByteString . Store.storePathToRawFilePath) -- store path
             <$> parseStoreResult "addToStore" res
       )
       (Store.makeStorePathName name)
@@ -416,7 +410,7 @@
       res <- Store.Remote.runStore $ Store.Remote.addTextToStore name text references repair
       either
         Left -- err
-        (pure . StorePath . decodeUtf8 . Store.storePathToRawFilePath) -- path
+        (pure . StorePath . coerce . decodeUtf8 @FilePath @ByteString . Store.storePathToRawFilePath) -- path
         <$> parseStoreResult "addTextToStore" res
 
 
@@ -438,12 +432,12 @@
     pure
     =<< addTextToStore' a b c d
 
-addPath :: (Framed e m, MonadStore m) => FilePath -> m StorePath
+addPath :: (Framed e m, MonadStore m) => Path -> m StorePath
 addPath p =
   either
     throwError
     pure
-    =<< addToStore (toText $ takeFileName p) p True False
+    =<< addToStore (fromString $ coerce takeFileName p) p True False
 
-toFile_ :: (Framed e m, MonadStore m) => FilePath -> String -> m StorePath
-toFile_ p contents = addTextToStore (toText p) (toText contents) mempty False
+toFile_ :: (Framed e m, MonadStore m) => Path -> Text -> m StorePath
+toFile_ p contents = addTextToStore (toText p) contents mempty False
diff --git a/src/Nix/Effects/Basic.hs b/src/Nix/Effects/Basic.hs
--- a/src/Nix/Effects/Basic.hs
+++ b/src/Nix/Effects/Basic.hs
@@ -1,27 +1,24 @@
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE ViewPatterns #-}
+{-# language CPP #-}
 
 module Nix.Effects.Basic where
 
-import           Prelude                 hiding ( traceM
-                                                , head
+import           Prelude                 hiding ( head
                                                 )
 import           Relude.Unsafe                  ( head )
-import           Nix.Utils
+import           GHC.Exception                  ( ErrorCall(ErrorCall) )
 import           Control.Monad                  ( foldM )
 import qualified Data.HashMap.Lazy             as M
 import           Data.List.Split                ( splitOn )
 import qualified Data.Text                     as Text
 import           Prettyprinter                  ( fillSep )
-import           System.FilePath
 import           Nix.Convert
 import           Nix.Effects
 import           Nix.Exec                       ( MonadNix
                                                 , evalExprLoc
                                                 , nixInstantiateExpr
                                                 )
-import           Nix.Expr
+import           Nix.Expr.Types
+import           Nix.Expr.Types.Annotated
 import           Nix.Frames
 import           Nix.Parser
 import           Nix.Render
@@ -34,137 +31,134 @@
 import           GHC.DataSize
 #endif
 
-defaultMakeAbsolutePath :: MonadNix e t f m => FilePath -> m FilePath
-defaultMakeAbsolutePath origPath = do
-  origPathExpanded <- expandHomePath origPath
-  absPath          <-
-    bool
-      (do
-        cwd <- do
-          mres <- lookupVar "__cur_file"
-          maybe
-            getCurrentDirectory
-            (
-              (\case
-                NVPath s -> pure $ takeDirectory s
-                val -> throwError $ ErrorCall $ "when resolving relative path, __cur_file is in scope, but is not a path; it is: " <> show val
-              ) <=< demand
+
+
+
+defaultToAbsolutePath :: forall e t f m . MonadNix e t f m => Path -> m Path
+defaultToAbsolutePath origPath =
+  do
+    origPathExpanded <- expandHomePath origPath
+    fmap
+      removeDotDotIndirections
+      . canonicalizePath
+        =<< bool
+            (fmap
+              (<///> origPathExpanded)
+              $ maybe
+                  getCurrentDirectory
+                  ( (\case
+                      NVPath s -> pure $ takeDirectory s
+                      val -> throwError $ ErrorCall $ "when resolving relative path, __cur_file is in scope, but is not a path; it is: " <> show val
+                    ) <=< demand
+                  )
+                  =<< lookupVar "__cur_file"
             )
-            mres
-        pure $ cwd <///> origPathExpanded
-      )
-      (pure origPathExpanded)
-      (isAbsolute origPathExpanded)
-  removeDotDotIndirections <$> canonicalizePath absPath
+            (pure origPathExpanded)
+            (isAbsolute origPathExpanded)
 
-expandHomePath :: MonadFile m => FilePath -> m FilePath
-expandHomePath ('~' : xs) = (<> xs) <$> getHomeDirectory
+expandHomePath :: MonadFile m => Path -> m Path
+expandHomePath (coerce -> ('~' : xs)) = (<> coerce xs) <$> getHomeDirectory
 expandHomePath p          = pure p
 
 -- | Incorrectly normalize paths by rewriting patterns like @a/b/..@ to @a@.
 --   This is incorrect on POSIX systems, because if @b@ is a symlink, its
 --   parent may be a different directory from @a@. See the discussion at
 --   https://hackage.haskell.org/package/directory-1.3.1.5/docs/System-Directory.html#v:canonicalizePath
-removeDotDotIndirections :: FilePath -> FilePath
-removeDotDotIndirections = intercalate "/" . go mempty . splitOn "/"
+removeDotDotIndirections :: Path -> Path
+removeDotDotIndirections = coerce . intercalate "/" . go mempty . splitOn "/" . coerce
  where
   go s       []            = reverse s
   go (_ : s) (".." : rest) = go s rest
   go s       (this : rest) = go (this : s) rest
 
 infixr 9 <///>
-(<///>) :: FilePath -> FilePath -> FilePath
-x <///> y | isAbsolute y || "." `isPrefixOf` y = x </> y
-          | otherwise                          = joinByLargestOverlap x y
+(<///>) :: Path -> Path -> Path
+x <///> y
+  | isAbsolute y || "." `isPrefixOf` coerce y = x </> y
+  | otherwise                          = joinByLargestOverlap x y
  where
+  joinByLargestOverlap :: Path -> Path -> Path
   joinByLargestOverlap (splitDirectories -> xs) (splitDirectories -> ys) =
     joinPath $ head
       [ xs <> drop (length tx) ys | tx <- tails xs, tx `elem` inits ys ]
 
-defaultFindEnvPath :: MonadNix e t f m => String -> m FilePath
-defaultFindEnvPath = findEnvPathM
-
-findEnvPathM :: forall e t f m . MonadNix e t f m => FilePath -> m FilePath
-findEnvPathM name = do
-  mres <- lookupVar "__nixPath"
+defaultFindEnvPath :: MonadNix e t f m => String -> m Path
+defaultFindEnvPath = findEnvPathM . coerce
 
+findEnvPathM :: forall e t f m . MonadNix e t f m => Path -> m Path
+findEnvPathM name =
   maybe
     (fail "impossible")
-    (
-      (\ nv ->
-        do
-          (l :: [NValue t f m]) <- fromValue nv
-          findPathBy nixFilePath l name
-      ) <=< demand
+    (\ v ->
+      do
+        l <- fromValue @[NValue t f m] =<< demand v
+        findPathBy nixFilePath l name
     )
-    mres
+    =<< lookupVar "__nixPath"
 
  where
-  nixFilePath :: MonadEffects t f m => FilePath -> m (Maybe FilePath)
-  nixFilePath path = do
-    absPath <- makeAbsolutePath @t @f path
-    isDir   <- doesDirectoryExist absPath
-    absFile <-
-      bool
-        (pure absPath)
-        (makeAbsolutePath @t @f $ absPath </> "default.nix")
-        isDir
-    exists <- doesFileExist absFile
-    pure $
-      bool
-        mempty
-        (pure absFile)
-        exists
+  nixFilePath :: MonadEffects t f m => Path -> m (Maybe Path)
+  nixFilePath path =
+    do
+      absPath <- toAbsolutePath @t @f path
+      isDir   <- doesDirectoryExist absPath
+      absFile <-
+        bool
+          (pure absPath)
+          (toAbsolutePath @t @f $ absPath </> "default.nix")
+          isDir
 
+      (pure absFile `whenTrue`) <$> doesFileExist absFile
+
 findPathBy
   :: forall e t f m
    . MonadNix e t f m
-  => (FilePath -> m (Maybe FilePath))
+  => (Path -> m (Maybe Path))
   -> [NValue t f m]
-  -> FilePath
-  -> m FilePath
-findPathBy finder ls name = do
-  mpath <- foldM go mempty ls
+  -> Path
+  -> m Path
+findPathBy finder ls name =
   maybe
-    (throwError $ ErrorCall $ "file '" <> name <> "' was not found in the Nix search path (add it's using $NIX_PATH or -I)")
+    (throwError $ ErrorCall $ "file ''" <> coerce name <> "'' was not found in the Nix search path (add it's using $NIX_PATH or -I)")
     pure
-    mpath
+    =<< foldM fun mempty ls
  where
-  go :: Maybe FilePath -> NValue t f m -> m (Maybe FilePath)
-  go mp =
+  fun
+    :: MonadNix e t f m
+    => Maybe Path
+    -> NValue t f m
+    -> m (Maybe Path)
+  fun =
     maybe
       (\ nv ->
         do
-          (s :: HashMap Text (NValue t f m)) <- fromValue =<< demand nv
+          (s :: HashMap VarName (NValue t f m)) <- fromValue =<< demand nv
           p <- resolvePath s
-          nvpath <- demand p
-          (Path path) <- fromValue nvpath
+          path <- fromValue =<< demand p
 
           maybe
             (tryPath path mempty)
             (\ nv' ->
               do
-                mns <- fromValueMay =<< demand nv'
+                mns <- fromValueMay @NixString =<< demand nv'
                 tryPath path $
-                  case mns of
-                    Just (nsPfx :: NixString) ->
+                  whenJust
+                    (\ nsPfx ->
                       let pfx = stringIgnoreContext nsPfx in
-                      bool
-                        mempty
-                        (pure (toString pfx))
-                        (not $ Text.null pfx)
-                    _ -> mempty
+                      pure $ coerce $ toString pfx `whenFalse` Text.null pfx
+                    )
+                    mns
             )
             (M.lookup "prefix" s)
       )
       (const . pure . pure)
-      mp
 
-  tryPath :: FilePath -> Maybe FilePath -> m (Maybe FilePath)
+  tryPath :: Path -> Maybe Path -> m (Maybe Path)
   tryPath p (Just n) | n' : ns <- splitDirectories name, n == n' =
     finder $ p <///> joinPath ns
   tryPath p _ = finder $ p <///> name
 
+  resolvePath :: HashMap VarName (NValue t f m) -> m (NValue t f m)
   resolvePath s =
     maybe
       (maybe
@@ -176,22 +170,29 @@
       (M.lookup "path" s)
 
 fetchTarball
-  :: forall e t f m . MonadNix e t f m => NValue t f m -> m (NValue t f m)
+  :: forall e t f m
+   . MonadNix e t f m
+  => NValue t f m
+  -> m (NValue t f m)
 fetchTarball =
   \case
-    NVSet s _ ->
+    NVSet _ s ->
       maybe
         (throwError $ ErrorCall "builtins.fetchTarball: Missing url attribute")
-        (go (M.lookup "sha256" s) <=< demand)
+        (fetchFromString (M.lookup "sha256" s) <=< demand)
         (M.lookup "url" s)
-    v@NVStr{} -> go Nothing v
+    v@NVStr{} -> fetchFromString Nothing v
     v -> throwError $ ErrorCall $ "builtins.fetchTarball: Expected URI or set, got " <> show v
   <=< demand
  where
-  go :: Maybe (NValue t f m) -> NValue t f m -> m (NValue t f m)
-  go msha = \case
-    NVStr ns -> fetch (stringIgnoreContext ns) msha
-    v -> throwError $ ErrorCall $ "builtins.fetchTarball: Expected URI or string, got " <> show v
+  fetchFromString
+    :: Maybe (NValue t f m)
+    -> NValue t f m
+    -> m (NValue t f m)
+  fetchFromString msha =
+    \case
+      NVStr ns -> fetch (stringIgnoreContext ns) msha
+      v -> throwError $ ErrorCall $ "builtins.fetchTarball: Expected URI or string, got " <> show v
 
 {- jww (2018-04-11): This should be written using pipes in another module
     fetch :: Text -> Maybe (NThunk m) -> m (NValue t f m)
@@ -206,68 +207,73 @@
 -}
 
   fetch :: Text -> Maybe (NValue t f m) -> m (NValue t f m)
-  fetch uri Nothing =
-    nixInstantiateExpr $ "builtins.fetchTarball \"" <> uri <> "\""
-  fetch url (Just t) =
-      (\nv -> do
-        nsSha <- fromValue nv
+  fetch uri =
+    maybe
+      (nixInstantiateExpr $
+        "builtins.fetchTarball \"" <> uri <> "\""
+      )
+      (\ v ->
+        do
+          nsSha <- fromValue =<< demand v
 
-        let sha = stringIgnoreContext nsSha
+          let sha = stringIgnoreContext nsSha
 
-        nixInstantiateExpr
-          $ "builtins.fetchTarball { " <> "url    = \"" <> url <> "\"; " <> "sha256 = \"" <> sha <> "\"; }"
-      ) =<< demand t
+          nixInstantiateExpr $
+            "builtins.fetchTarball { " <> "url    = \"" <> uri <> "\"; " <> "sha256 = \"" <> sha <> "\"; }"
+      )
 
-defaultFindPath :: MonadNix e t f m => [NValue t f m] -> FilePath -> m FilePath
+defaultFindPath :: MonadNix e t f m => [NValue t f m] -> Path -> m Path
 defaultFindPath = findPathM
 
 findPathM
   :: forall e t f m
    . MonadNix e t f m
   => [NValue t f m]
-  -> FilePath
-  -> m FilePath
+  -> Path
+  -> m Path
 findPathM = findPathBy existingPath
  where
-  existingPath :: MonadEffects t f m => FilePath -> m (Maybe FilePath)
+  existingPath :: MonadEffects t f m => Path -> m (Maybe Path)
   existingPath path =
     do
-      apath  <- makeAbsolutePath @t @f path
+      apath  <- toAbsolutePath @t @f path
       doesExist <- doesPathExist apath
       pure $ pure apath `whenTrue` doesExist
 
 defaultImportPath
-  :: (MonadNix e t f m, MonadState (HashMap FilePath NExprLoc, b) m)
-  => FilePath
+  :: (MonadNix e t f m, MonadState (HashMap Path NExprLoc, b) m)
+  => Path
   -> m (NValue t f m)
-defaultImportPath path = do
-  traceM $ "Importing file " <> path
-  withFrame Info (ErrorCall $ "While importing file " <> show path) $ do
-    imports <- gets fst
-    evalExprLoc =<<
-      maybe
-        (do
-          eres <- parseNixFileLoc path
-          either
-            (\ err -> throwError $ ErrorCall . show $ fillSep ["Parse during import failed:", err])
-            (\ expr ->
-              do
-                modify (first (M.insert path expr))
-                pure expr
+defaultImportPath path =
+  do
+    traceM $ "Importing file " <> coerce path
+    withFrame
+      Info
+      (ErrorCall $ "While importing file " <> show path)
+      $ evalExprLoc =<<
+          (maybe
+            (either
+              (\ err -> throwError $ ErrorCall . show $ fillSep ["Parse during import failed:", err])
+              (\ expr ->
+                do
+                  modify $ first $ M.insert path expr
+                  pure expr
+              )
+              =<< parseNixFileLoc path
             )
-            eres
-        )
-        pure  -- return expr
-        (M.lookup path imports)
+            pure  -- return expr
+            . M.lookup path
+          ) =<< gets fst
 
-defaultPathToDefaultNix :: MonadNix e t f m => FilePath -> m FilePath
+defaultPathToDefaultNix :: MonadNix e t f m => Path -> m Path
 defaultPathToDefaultNix = pathToDefaultNixFile
 
 -- Given a path, determine the nix file to load
-pathToDefaultNixFile :: MonadFile m => FilePath -> m FilePath
-pathToDefaultNixFile p = do
-  isDir <- doesDirectoryExist p
-  pure $ p </> "default.nix" `whenTrue` isDir
+pathToDefaultNixFile :: MonadFile m => Path -> m Path
+pathToDefaultNixFile p =
+  do
+    isDir <- doesDirectoryExist p
+    pure $ p </> "default.nix" `whenTrue` isDir
 
 defaultTraceEffect :: MonadPutStr m => String -> m ()
 defaultTraceEffect = Nix.Effects.putStrLn
diff --git a/src/Nix/Effects/Derivation.hs b/src/Nix/Effects/Derivation.hs
--- a/src/Nix/Effects/Derivation.hs
+++ b/src/Nix/Effects/Derivation.hs
@@ -1,12 +1,12 @@
-{-# LANGUAGE DataKinds #-}
-{-# LANGUAGE NamedFieldPuns #-}
-{-# LANGUAGE RecordWildCards #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE PackageImports #-} -- 2021-07-05: Due to hashing Haskell IT system situation, in HNix we currently ended-up with 2 hash package dependencies @{hashing, cryptonite}@
+{-# language DataKinds #-}
+{-# language NamedFieldPuns #-}
+{-# language RecordWildCards #-}
+{-# language PackageImports #-} -- 2021-07-05: Due to hashing Haskell IT system situation, in HNix we currently ended-up with 2 hash package dependencies @{hashing, cryptonite}@
 
 module Nix.Effects.Derivation ( defaultDerivationStrict ) where
 
-import           Nix.Utils
+import           Prelude                 hiding ( readFile )
+import           GHC.Exception                  ( ErrorCall(ErrorCall) )
 import           Data.Char                      ( isAscii
                                                 , isAlphaNum
                                                 )
@@ -24,6 +24,7 @@
 import qualified "cryptonite" Crypto.Hash      as Hash -- 2021-07-05: Attrocity of Haskell hashing situation, in HNix we ended-up with 2 hash package dependencies @{hashing, cryptonite}@
 
 import           Nix.Atoms
+import           Nix.Expr.Types          hiding ( Recursive )
 import           Nix.Convert
 import           Nix.Effects
 import           Nix.Exec                       ( MonadNix
@@ -40,9 +41,9 @@
 import qualified System.Nix.ReadonlyStore      as Store
 import qualified System.Nix.Hash               as Store
 import qualified System.Nix.StorePath          as Store
-import Prelude hiding (readFile)
 
 
+--  2021-07-17: NOTE: Derivation consists of @"keys"@ @"vals"@ (of text), so underlining type boundary currently stops here.
 data Derivation = Derivation
   { name :: Text
   , outputs :: Map Text Text
@@ -75,11 +76,11 @@
   let (inputSrcs, inputDrvs) = inputs
   references <- Set.fromList <$> traverse parsePath (Set.toList $ inputSrcs <> Set.fromList (Map.keys inputDrvs))
   path <- addTextToStore (Text.append name ".drv") (unparseDrv drv) (S.fromList $ Set.toList references) False
-  parsePath $ toText $ unStorePath path
+  parsePath $ fromString $ coerce path
 
 -- | Traverse the graph of inputDrvs to replace fixed output derivations with their fixed output hash.
 -- this avoids propagating changes to their .drv when the output hash stays the same.
-hashDerivationModulo :: (MonadNix e t f m, MonadState (b, AttrSet Text) m) => Derivation -> m (Hash.Digest Hash.SHA256)
+hashDerivationModulo :: (MonadNix e t f m, MonadState (b, KeyMap Text) m) => Derivation -> m (Hash.Digest Hash.SHA256)
 hashDerivationModulo
   Derivation
     { mFixed = Just (Store.SomeDigest (digest :: Hash.Digest hashType))
@@ -91,7 +92,7 @@
       Hash.hash @ByteString @Hash.SHA256 $
         encodeUtf8 $
           "fixed:out"
-          <> (if hashMode == Recursive then ":r" else "")
+          <> (if hashMode == Recursive then ":r" else mempty)
           <> ":" <> (Store.algoName @hashType)
           <> ":" <> Store.encodeDigestWith Store.Base16 digest
           <> ":" <> path
@@ -110,7 +111,7 @@
           (\(path, outs) ->
             maybe
               (do
-                drv' <- readDerivation $ toString path
+                drv' <- readDerivation $ coerce $ toString path
                 hash <- Store.encodeDigestWith Store.Base16 <$> hashDerivationModulo drv'
                 pure (hash, outs)
               )
@@ -144,7 +145,7 @@
       ]
   where
     produceOutputInfo (outputName, outputPath) =
-      let prefix = if hashMode == Recursive then "r:" else "" in
+      let prefix = if hashMode == Recursive then "r:" else mempty in
       parens $ (s <$>) $ ([outputName, outputPath] <>) $
         maybe
           [mempty, mempty]
@@ -168,13 +169,13 @@
     escape '\t' = "\\t"
     escape c = one c
 
-readDerivation :: (Framed e m, MonadFile m) => FilePath -> m Derivation
+readDerivation :: (Framed e m, MonadFile m) => Path -> m Derivation
 readDerivation path = do
-  content <- decodeUtf8 <$> readFile path
+  content <- readFile path
   either
     (\ err -> throwError $ ErrorCall $ "Failed to parse " <> show path <> ":\n" <> show err)
     pure
-    (parse derivationParser path content)
+    (parse derivationParser (coerce path) content)
 
 derivationParser :: Parsec () Text Derivation
 derivationParser = do
@@ -199,13 +200,13 @@
 
   let outputs = Map.fromList $ (\(a, b, _, _) -> (a, b)) <$> fullOutputs
   let (mFixed, hashMode) = parseFixed fullOutputs
-  let name = "" -- FIXME (extract from file path ?)
-  let useJson = ["__json"] == Map.keys env
+  let name = mempty -- FIXME (extract from file path ?)
+  let useJson = one "__json" == Map.keys env
 
   pure $ Derivation {inputs = (inputSrcs, inputDrvs), ..}
  where
   s :: Parsec () Text Text
-  s = fmap toText $ string "\"" *> manyTill (escaped <|> regular) (string "\"")
+  s = fmap fromString $ string "\"" *> manyTill (escaped <|> regular) (string "\"")
   escaped = char '\\' *>
     (   '\n' <$ string "n"
     <|> '\r' <$ string "r"
@@ -218,12 +219,13 @@
     string o *> sepBy p (string ",") <* string c
 
   parens :: Parsec () Text a -> Parsec () Text [a]
-  parens p = wrap "(" ")" p
-  serializeList p = wrap "[" "]" p
+  parens = wrap "(" ")"
+  serializeList :: Parsec () Text a -> Parsec () Text [a]
+  serializeList = wrap "[" "]"
 
   parseFixed :: [(Text, Text, Text, Text)] -> (Maybe Store.SomeNamedDigest, HashMode)
   parseFixed fullOutputs = case fullOutputs of
-    [("out", _path, rht, hash)] | rht /= "" && hash /= "" ->
+    [("out", _path, rht, hash)] | rht /= mempty && hash /= mempty ->
       let
         (hashType, hashMode) = case Text.splitOn ":" rht of
           ["r", ht] -> (ht, Recursive)
@@ -238,9 +240,9 @@
     _ -> (Nothing, Flat)
 
 
-defaultDerivationStrict :: forall e t f m b. (MonadNix e t f m, MonadState (b, AttrSet Text) m) => NValue t f m -> m (NValue t f m)
+defaultDerivationStrict :: forall e t f m b. (MonadNix e t f m, MonadState (b, KeyMap Text) m) => NValue t f m -> m (NValue t f m)
 defaultDerivationStrict val = do
-    s <- fromValue @(AttrSet (NValue t f m)) val
+    s <- M.mapKeys coerce <$> fromValue @(AttrSet (NValue t f m)) val
     (drv, ctx) <- runWithStringContextT' $ buildDerivationWithContext s
     drvName <- makeStorePathName $ name drv
     let
@@ -266,47 +268,50 @@
               ifNotJsonModEnv
                 (\ baseEnv ->
                   foldl'
-                    (\m k -> Map.insert k "" m)
+                    (\m k -> Map.insert k mempty m)
                     baseEnv
                     (Map.keys $ outputs drv)
                 )
           }
-        outputs' <- sequence $ Map.mapWithKey (\o _ -> makeOutputPath o hash drvName) $ outputs drv
+        outputs' <- sequenceA $ Map.mapWithKey (\o _ -> makeOutputPath o hash drvName) $ outputs drv
         pure $ drv
           { inputs
           , outputs = outputs'
-          , env = ifNotJsonModEnv $ (outputs' <>)
+          , env = ifNotJsonModEnv (outputs' <>)
           }
 
-    drvPath <- pathToText <$> writeDerivation drv'
+    (coerce @Text @VarName -> drvPath) <- pathToText <$> writeDerivation drv'
 
     -- Memoize here, as it may be our last chance in case of readonly stores.
     drvHash <- Store.encodeDigestWith Store.Base16 <$> hashDerivationModulo drv'
-    modify $ second $ MS.insert drvPath drvHash
+    modify $ second $ MS.insert (coerce drvPath) drvHash
 
     let
       outputsWithContext =
         Map.mapWithKey
-          (\out path -> makeNixStringWithSingletonContext path $ StringContext drvPath $ DerivationOutput out)
+          (\out (coerce -> path) -> mkNixStringWithSingletonContext path $ StringContext drvPath $ DerivationOutput out)
           (outputs drv')
-      drvPathWithContext = makeNixStringWithSingletonContext drvPath $ StringContext drvPath AllOutputs
+      drvPathWithContext = mkNixStringWithSingletonContext drvPath $ StringContext drvPath AllOutputs
       attrSet = nvStr <$> M.fromList (("drvPath", drvPathWithContext) : Map.toList outputsWithContext)
     -- TODO: Add location information for all the entries.
     --              here --v
-    pure $ nvSet mempty attrSet
+    pure $ nvSet mempty (M.mapKeys coerce attrSet)
 
   where
 
     pathToText = decodeUtf8 . Store.storePathToRawFilePath
 
     makeOutputPath o h n = do
-      name <- makeStorePathName $ Store.unStorePathName n <> if o == "out" then "" else "-" <> o
+      name <- makeStorePathName $ Store.unStorePathName n <> if o == "out" then mempty else "-" <> o
       pure $ pathToText $ Store.makeStorePath "/nix/store" ("output:" <> encodeUtf8 o) h name
 
-    toStorePaths ctx = foldl (flip addToInputs) (mempty, mempty) ctx
+    toStorePaths :: HashSet StringContext -> (Set Text, Map Text [Text])
+    toStorePaths = foldl (flip addToInputs) mempty
+
+    addToInputs :: Bifunctor p => StringContext -> p (Set Text) (Map Text [Text])  -> p (Set Text) (Map Text [Text])
     addToInputs (StringContext path kind) = case kind of
-      DirectPath -> first (Set.insert path)
-      DerivationOutput o -> second (Map.insertWith (<>) path [o])
+      DirectPath -> first (Set.insert (coerce path))
+      DerivationOutput o -> second (Map.insertWith (<>) (coerce path) $ one o)
       AllOutputs ->
         -- TODO: recursive lookup. See prim_derivationStrict
         -- XXX: When is this really used ?
@@ -316,7 +321,7 @@
 -- | Build a derivation in a context collecting string contexts.
 -- This is complex from a typing standpoint, but it allows to perform the
 -- full computation without worrying too much about all the string's contexts.
-buildDerivationWithContext :: forall e t f m. (MonadNix e t f m) => AttrSet (NValue t f m) -> WithStringContextT m Derivation
+buildDerivationWithContext :: forall e t f m. (MonadNix e t f m) => KeyMap (NValue t f m) -> WithStringContextT m Derivation
 buildDerivationWithContext drvAttrs = do
     -- Parse name first, so we can add an informative frame
     drvName     <- getAttr   "name"                      $ assertDrvStoreName <=< extractNixString
@@ -330,13 +335,13 @@
       platform    <- getAttr   "system"                    $ assertNonNull <=< extractNoCtx
       mHash       <- getAttrOr "outputHash"        mempty  $ (pure . pure) <=< extractNoCtx
       hashMode    <- getAttrOr "outputHashMode"    Flat    $ parseHashMode <=< extractNoCtx
-      outputs     <- getAttrOr "outputs"           ["out"] $ traverse (extractNoCtx <=< fromValue')
+      outputs     <- getAttrOr "outputs"       (one "out") $ traverse (extractNoCtx <=< fromValue')
 
       mFixedOutput <-
         maybe
           (pure Nothing)
           (\ hash -> do
-            when (outputs /= ["out"]) $ lift $ throwError $ ErrorCall "Multiple outputs are not supported for fixed-output derivations"
+            when (outputs /= one "out") $ lift $ throwError $ ErrorCall "Multiple outputs are not supported for fixed-output derivations"
             hashType <- getAttr "outputHashAlgo" extractNoCtx
             digest <- lift $ either (throwError . ErrorCall) pure $ Store.mkNamedDigest hashType hash
             pure $ pure digest)
@@ -362,7 +367,7 @@
 
       env <- if useJson
         then do
-          jsonString :: NixString <- lift $ nvalueToJSONNixString $ nvSet mempty $
+          jsonString :: NixString <- lift $ nvalueToJSONNixString $ nvSet mempty $ M.mapKeys coerce $
             deleteKeys [ "args", "__ignoreNulls", "__structuredAttrs" ] attrs
           rawString :: Text <- extractNixString jsonString
           pure $ one ("__json", rawString)
@@ -374,7 +379,7 @@
         , name = drvName
         , outputs = Map.fromList $ (, mempty) <$> outputs
         , mFixed = mFixedOutput
-        , inputs = (mempty, mempty) -- stub for now
+        , inputs = mempty -- stub for now
         }
   where
 
@@ -386,7 +391,7 @@
     withFrame' :: (Framed e m, Exception s) => NixLevel -> s -> WithStringContextT m a -> WithStringContextT m a
     withFrame' level f = join . lift . withFrame level f . pure
 
-    -- shortcuts to get the (forced) value of an AttrSet field
+    -- shortcuts to get the (forced) value of an KeyMap field
 
     getAttrOr' :: forall v a. (MonadNix e t f m, FromValue v m (NValue' t f m (NValue t f m)))
       => Text -> m a -> (v -> WithStringContextT m a) -> WithStringContextT m a
@@ -395,7 +400,7 @@
       Just v  -> withFrame' Info (ErrorCall $ "While evaluating attribute '" <> show n <> "'") $
                    f =<< fromValue' v
 
-    getAttrOr n d f = getAttrOr' n (pure d) f
+    getAttrOr n = getAttrOr' n . pure
 
     getAttr n = getAttrOr' n (throwError $ ErrorCall $ "Required attribute '" <> show n <> "' not found.")
 
@@ -431,6 +436,6 @@
 
     -- Other helpers
 
-    deleteKeys :: [Text] -> AttrSet a -> AttrSet a
+    deleteKeys :: [Text] -> KeyMap a -> KeyMap a
     deleteKeys keys attrSet = foldl' (flip M.delete) attrSet keys
 
diff --git a/src/Nix/Eval.hs b/src/Nix/Eval.hs
--- a/src/Nix/Eval.hs
+++ b/src/Nix/Eval.hs
@@ -1,38 +1,39 @@
-{-# LANGUAGE AllowAmbiguousTypes #-}
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE RankNTypes #-}
-{-# LANGUAGE ScopedTypeVariables #-}
+{-# language AllowAmbiguousTypes #-}
+{-# language ConstraintKinds #-}
+{-# language RankNTypes #-}
 
 
 
 module Nix.Eval where
 
+import           Relude.Extra                   ( set )
 import           Control.Monad                  ( foldM )
 import           Control.Monad.Fix              ( MonadFix )
+import           GHC.Exception                  ( ErrorCall(ErrorCall) )
 import           Data.Semialign.Indexed         ( ialignWith )
 import qualified Data.HashMap.Lazy             as M
 import           Data.List                      ( partition )
 import           Data.These                     ( These(..) )
 import           Nix.Atoms
 import           Nix.Convert
-import           Nix.Expr
+import           Nix.Expr.Types
+import           Nix.Expr.Types.Annotated
 import           Nix.Expr.Strings               ( runAntiquoted )
 import           Nix.Frames
 import           Nix.String
 import           Nix.Scope
-import           Nix.Utils
 import           Nix.Value.Monad
 
 class (Show v, Monad m) => MonadEval v m where
-  freeVariable    :: Text -> m v
-  synHole         :: Text -> m v
-  attrMissing     :: NonEmpty Text -> Maybe v -> m v
-  evaledSym       :: Text -> v -> m v
+  freeVariable    :: VarName -> m v
+  synHole         :: VarName -> m v
+  attrMissing     :: NonEmpty VarName -> Maybe v -> m v
+  evaledSym       :: VarName -> v -> m v
   evalCurPos      :: m v
   evalConstant    :: NAtom -> m v
   evalString      :: NString (m v) -> m v
-  evalLiteralPath :: FilePath -> m v
-  evalEnvPath     :: FilePath -> m v
+  evalLiteralPath :: Path -> m v
+  evalEnvPath     :: Path -> m v
   evalUnary       :: NUnaryOp -> v -> m v
   evalBinary      :: NBinaryOp -> v -> m v -> m v
   -- ^ The second argument is an action because operators such as boolean &&
@@ -60,9 +61,9 @@
   evalListElem   :: [m v] -> Int -> m v -> m v
   evalList       :: [v] -> m v
   evalSetElem    :: AttrSet (m v) -> Text -> m v -> m v
-  evalSet        :: AttrSet v -> AttrSet SourcePos -> m v
+  evalSet        :: AttrSet v -> PositionSet -> m v
   evalRecSetElem :: AttrSet (m v) -> Text -> m v -> m v
-  evalRecSet     :: AttrSet v -> AttrSet SourcePos -> m v
+  evalRecSet     :: AttrSet v -> PositionSet -> m v
   evalLetElem    :: Text -> m v -> m v
   evalLet        :: m v -> m v
 -}
@@ -76,8 +77,8 @@
   , ToValue Bool m v
   , ToValue [v] m v
   , FromValue NixString m v
-  , ToValue (AttrSet v, AttrSet SourcePos) m v
-  , FromValue (AttrSet v, AttrSet SourcePos) m v
+  , ToValue (AttrSet v, PositionSet) m v
+  , FromValue (AttrSet v, PositionSet) m v
   )
 
 data EvalFrame m v
@@ -129,12 +130,12 @@
     lav <- larg
     evalBinary op lav rarg
 
-eval (NSelect aset attr alt ) =
+eval (NSelect alt aset attr) =
   do
     let useAltOrReportMissing (s, ks) = fromMaybe (attrMissing ks $ pure s) alt
 
     eAttr <- evalSelect aset attr
-    either useAltOrReportMissing id eAttr
+    either useAltOrReportMissing id (coerce eAttr)
 
 eval (NHasAttr aset attr) =
   do
@@ -144,23 +145,17 @@
 eval (NList l           ) =
   do
     scope <- currentScopes
-    lst <- traverse (defer @v @m . withScopes @v scope) l
-    toValue lst
-
-eval (NSet NNonRecursive binds) =
-  do
-    attrSet <- evalBinds False $ desugarBinds (eval . NSet NNonRecursive) binds
-    toValue attrSet
+    toValue =<< traverse (defer @v @m . withScopes @v scope) l
 
-eval (NSet NRecursive binds) =
+eval (NSet r binds) =
   do
-    attrSet <- evalBinds True $ desugarBinds (eval . NSet NNonRecursive) binds
+    attrSet <- evalBinds (r == Recursive) $ desugarBinds (eval . NSet mempty) binds
     toValue attrSet
 
 eval (NLet binds body    ) =
   do
     (attrSet, _) <- evalBinds True binds
-    pushScope attrSet body
+    pushScope (coerce attrSet) body
 
 eval (NIf cond t f       ) =
   do
@@ -179,78 +174,97 @@
   -- needs to be used when evaluating the body and default arguments, hence we
   -- defer here so the present scope is restored when the parameters and body
   -- are forced during application.
-  scope <- currentScopes :: m (Scopes m v)
+  curScope <- currentScopes
   let
-    withScope = withScopes scope
-    withScopeInform = withScope . inform
+    withCurScope = withScopes curScope
 
-  evalAbs
-    params
-    (\arg k ->
-      withScope $
+    fun :: m v -> (AttrSet (m v) -> m v -> m r) -> m r
+    fun arg k =
+      withCurScope $
         do
-          args <- buildArgument params arg
+          (coerce -> newScopeToAdd) <- buildArgument params arg
           pushScope
-            args $
+            newScopeToAdd $
             k
-              (withScopeInform <$> args)
+              (coerce $ withCurScope . inform <$> newScopeToAdd)
               body
-    )
 
+  evalAbs
+    params
+    fun
+
 eval (NSynHole name) = synHole name
 
 -- | If you know that the 'scope' action will result in an 'AttrSet v', then
 --   this implementation may be used as an implementation for 'evalWith'.
 evalWithAttrSet :: forall v m . MonadNixEval v m => m v -> m v -> m v
 evalWithAttrSet aset body = do
-  scope <- currentScopes :: m (Scopes m v)
+  scopes <- currentScopes :: m (Scopes m v)
   -- The scope is deliberately wrapped in a thunk here, since it is demanded
   -- each time a name is looked up within the weak scope, and we want to be
   -- sure the action it evaluates is to force a thunk, so its value is only
   -- computed once.
-  deferredAset <- defer $ withScopes scope aset
-  let attrSet = fst <$> (fromValue @(AttrSet v, AttrSet SourcePos) =<< demand deferredAset)
+  deferredAset <- defer $ withScopes scopes aset
+  let weakscope = coerce . fst <$> (fromValue @(AttrSet v, PositionSet) =<< demand deferredAset)
 
-  pushWeakScope attrSet body
+  pushWeakScope weakscope body
 
 attrSetAlter
   :: forall v m
    . MonadNixEval v m
-  => [Text]
+  => [VarName]
   -> SourcePos
   -> AttrSet (m v)
-  -> AttrSet SourcePos
+  -> PositionSet
   -> m v
-  -> m (AttrSet (m v), AttrSet SourcePos)
-attrSetAlter [] _ _ _ _ = evalError @v $ ErrorCall "invalid selector with no components"
-attrSetAlter (k : ks) pos m p val =
-  bool
-    go
-    (maybe
-      (recurse mempty mempty)
-      (\x ->
-        do
-          (st, sp) <- fromValue @(AttrSet v, AttrSet SourcePos) =<< x
-          recurse (demand <$> st) sp
-      )
-      (M.lookup k m)
-    )
-    (not $ null ks)
+  -> m (AttrSet (m v), PositionSet)
+attrSetAlter ks' pos m' p' val =
+  swap <$> go p' m' ks'
  where
-  go = pure (M.insert k val m, M.insert k pos p)
-
-  recurse st sp =
-    (\(st', _) ->
-      (M.insert
-        k
-        (toValue @(AttrSet v, AttrSet SourcePos) =<< (, mempty) <$> sequence st')
-        m
-      , M.insert k pos p
+  -- This `go` does traverse in disquise. Notice how it traverses `ks`.
+  go
+    :: PositionSet
+    -> AttrSet (m v)
+    -> [VarName]
+    -> m (PositionSet, AttrSet (m v))
+  go _ _ [] = evalError @v $ ErrorCall "invalid selector with no components"
+  go p m (k : ks) =
+    bool
+      (pure $ insertVal val)
+      (maybe
+        (recurse mempty mempty)
+        (\x ->
+          do
+            --  2021-10-12: NOTE: swapping sourcewide into (PositionSet, AttrSet) would optimize code and remove this `swap`
+            (swap -> (sp, st)) <- fromValue @(AttrSet v, PositionSet) =<< x
+            recurse sp $ demand <$> st
+        )
+        ((`M.lookup` m) k)
       )
-    ) <$> attrSetAlter ks pos st sp val
+      (not $ null ks)
+   where
+    insertVal :: m v -> (PositionSet, AttrSet (m v))
+    insertVal v =
+      ( insertPos
+      , insertV v
+      )
+     where
+      insertV v' = M.insert k v' m
+      insertPos = M.insert k pos p
 
+    recurse
+      :: PositionSet
+      -> AttrSet (m v)
+      -> m ( PositionSet
+          , AttrSet (m v)
+          )
+    recurse p'' m'' =
+      fmap
+        (insertVal . (=<<) (toValue @(AttrSet v, PositionSet)) . fmap (,mempty) . sequenceA . snd)
+        (go p'' m'' ks)
+
 desugarBinds :: forall r . ([Binding r] -> r) -> [Binding r] -> [Binding r]
-desugarBinds embed binds = evalState (traverse (go <=< collect) binds) mempty
+desugarBinds embed binds = evalState (traverse (findBinding <=< collect) binds) mempty
  where
   collect
     :: Binding r
@@ -261,76 +275,79 @@
     do
       m <- get
       put $
-        M.insert
-          x
-          (maybe
-            (p, [bindValAt p])
-            (\ (q, v) -> (q, bindValAt q : v))
-            (M.lookup x m)
+        join
+          (M.insert
+            x
+            . maybe
+              (p, one $ bindValAt p)
+              (\ (sp, bnd) -> (sp, one (bindValAt sp) <> bnd))
+              . M.lookup x
           )
           m
       pure $ Left x
    where
-    bindValAt pos = NamedVar (y :| ys) val pos
+    bindValAt = NamedVar (y :| ys) val
   collect x = pure $ pure x
 
-  go
+  findBinding
     :: Either VarName (Binding r)
     -> State (HashMap VarName (SourcePos, [Binding r])) (Binding r)
-  go =
+  findBinding =
     either
-      (\ x -> do
-        maybeValue <- gets $ M.lookup x
+      (\ x ->
         maybe
           (error $ "No binding " <> show x)
-          (\ (p, v) -> pure $ NamedVar (StaticKey x :| mempty) (embed v) p)
-          maybeValue
+          (\ (p, v) -> pure $ NamedVar (one $ StaticKey x) (embed v) p)
+          =<< gets (M.lookup x)
       )
       pure
 
 evalBinds
   :: forall v m
    . MonadNixEval v m
+--  2021-07-19: NOTE: Recutsivity data type
   => Bool
   -> [Binding (m v)]
-  -> m (AttrSet v, AttrSet SourcePos)
-evalBinds recursive binds =
+--  2021-07-19: NOTE: AttrSet is a Scope
+  -> m (AttrSet v, PositionSet)
+evalBinds isRecursive binds =
   do
     scope <- currentScopes :: m (Scopes m v)
 
-    buildResult scope . concat =<< traverse (applyBindToAdt scope) (moveOverridesLast binds)
+    buildResult scope . fold =<< (`traverse` moveOverridesLast binds) (applyBindToAdt scope)
 
  where
   buildResult
     :: Scopes m v
-    -> [([Text], SourcePos, m v)]
-    -> m (AttrSet v, AttrSet SourcePos)
-  buildResult scope bindings =
+    -> [([VarName], SourcePos, m v)]
+    -> m (AttrSet v, PositionSet)
+  buildResult scopes bindings =
     do
-      (s, p) <- foldM insert (mempty, mempty) bindings
+      (coerce -> scope, p) <- foldM insert mempty bindings
       res <-
         bool
-          (traverse mkThunk s)
-          (loebM $ encapsulate <$> s)
-          recursive
+          (traverse mkThunk)
+          (loebM . fmap encapsulate)
+          isRecursive
+          scope
 
-      pure (res, p)
+      pure (coerce res, p)
 
    where
     insert (m, p) (path, pos, value) = attrSetAlter path pos m p value
 
-    mkThunk = defer . withScopes scope
+    mkThunk = defer . withScopes scopes
 
     encapsulate f attrs = mkThunk $ pushScope attrs f
 
-  applyBindToAdt :: Scopes m v -> Binding (m v) -> m [([Text], SourcePos, m v)]
+  applyBindToAdt :: Scopes m v -> Binding (m v) -> m [([VarName], SourcePos, m v)]
   applyBindToAdt _ (NamedVar (StaticKey "__overrides" :| []) finalValue pos) =
     do
       (o', p') <- fromValue =<< finalValue
       -- jww (2018-05-09): What to do with the key position here?
       pure $
         (\ (k, v) ->
-          ( [k]
+          ( one k
           , fromMaybe pos $ M.lookup k p'
           , demand v
           )
@@ -341,19 +358,19 @@
       -- When there are no path segments, e.g. `${null} = 5;`, we don't
       -- bind anything
       ([], _, _) -> mempty
-      result     -> [result]
+      result     -> one result
     ) <$> processAttrSetKeys pathExpr
 
    where
-    processAttrSetKeys :: NAttrPath (m v) -> m ([Text], SourcePos, m v)
+    processAttrSetKeys :: NAttrPath (m v) -> m ([VarName], SourcePos, m v)
     processAttrSetKeys (h :| t) =
       maybe
         -- Empty attrset - return a stub.
-        (pure ( mempty, nullPos, toValue @(AttrSet v, AttrSet SourcePos) (mempty, mempty)) )
+        (pure (mempty, nullPos, toValue @(AttrSet v, PositionSet) mempty) )
         (\ k ->
           list
             -- No more keys in the attrset - return the result
-            (pure ( [k], pos, finalValue ) )
+            (pure ( one k, pos, finalValue ) )
             -- There are unprocessed keys in attrset - recurse appending the results
             (\ (x : xs) ->
               do
@@ -364,37 +381,28 @@
         )
         =<< evalSetterKeyName h
 
-  applyBindToAdt scope (Inherit ms names pos) =
-    catMaybes <$>
-      traverse
-        processScope
-        names
+  applyBindToAdt scopes (Inherit ms names pos) =
+    pure $ processScope <$> names
    where
     processScope
-      :: NKeyName (m v)
-      -> m (Maybe ([Text], SourcePos, m v))
-    processScope nkeyname =
-      (\ mkey ->
-        do
-          key <- mkey
-          pure
-            ([key]
-            , pos
-            , maybe
-                (attrMissing (key :| mempty) Nothing)
-                demand
-                =<< maybe
-                    (withScopes scope $ lookupVar key)
-                    (\ s ->
-                      do
-                        (attrset, _) <- fromValue @(AttrSet v, AttrSet SourcePos) =<< s
+      :: VarName
+      -> ([VarName], SourcePos, m v)
+    processScope var =
+      ( one var
+      , pos
+      , maybe
+          (attrMissing (one var) Nothing)
+          demand
+          =<< maybe
+              (withScopes scopes $ lookupVar var)
+              (\ s ->
+                do
+                  (coerce -> scope, _) <- fromValue @(AttrSet v, PositionSet) =<< s
 
-                        clearScopes @v $ pushScope attrset $ lookupVar key
-                    )
-                    ms
-            )
-      ) <$>
-        evalSetterKeyName nkeyname
+                  clearScopes $ pushScope @v scope $ lookupVar var
+              )
+              ms
+      )
 
   moveOverridesLast = uncurry (<>) . partition
     (\case
@@ -407,30 +415,33 @@
    . MonadNixEval v m
   => m v
   -> NAttrPath (m v)
-  -> m (Either (v, NonEmpty Text) (m v))
+  -> m (Either (v, NonEmpty VarName) (m v))
 evalSelect aset attr =
   do
     s    <- aset
     path <- traverse evalGetterKeyName attr
 
-    extract s path
+    extract path s
 
  where
-  extract x path@(k :| ks) =
-    do
-      x' <- fromValueMay x
-
-      case x' of
-        Nothing -> pure $ Left (x, path)
-        Just (s :: AttrSet v, p :: AttrSet SourcePos)
-          | Just t <- M.lookup k s ->
-            do
-              list
-                (pure . pure)
-                (\ (y : ys) -> ((`extract` (y :| ys)) =<<))
-                ks
-                $ demand t
-          | otherwise -> Left . (, path) <$> toValue (s, p)
+  extract :: NonEmpty VarName -> v -> m (Either (v, NonEmpty VarName) (m v))
+  extract path@(k :| ks) x =
+    maybe
+      left
+      (maybe
+        left
+        (list
+          (pure . pure)
+          (\ (y : ys) -> (extract (y :| ys) =<<))
+          ks
+          . demand
+        )
+        . M.lookup k . fst
+      )
+      =<< fromValueMay @(AttrSet v, PositionSet) x
+   where
+    left :: m (Either (v, NonEmpty VarName) b)
+    left = pure $ Left (x, path)
 
 -- | Evaluate a component of an attribute path in a context where we are
 -- *retrieving* a value
@@ -438,7 +449,7 @@
   :: forall v m
    . (MonadEval v m, FromValue NixString m v)
   => NKeyName (m v)
-  -> m Text
+  -> m VarName
 evalGetterKeyName =
   maybe
     (evalError @v $ ErrorCall "value is null while a string was expected")
@@ -450,33 +461,29 @@
 evalSetterKeyName
   :: (MonadEval v m, FromValue NixString m v)
   => NKeyName (m v)
-  -> m (Maybe Text)
+  -> m (Maybe VarName)
 evalSetterKeyName =
   \case
     StaticKey k -> pure $ pure k
     DynamicKey k ->
-      maybe
-        mempty
-        (pure . stringIgnoreContext)
-        <$> runAntiquoted "\n" assembleString (fromValueMay =<<) k
+      (coerce . stringIgnoreContext) <<$>>
+        runAntiquoted "\n" assembleString (fromValueMay =<<) k
 
 assembleString
   :: forall v m
    . (MonadEval v m, FromValue NixString m v)
   => NString (m v)
   -> m (Maybe NixString)
-assembleString =
-  fromParts .
-    \case
-      Indented   _ parts -> parts
-      DoubleQuoted parts -> parts
+assembleString = fromParts . stringParts
  where
-  fromParts xs = (mconcat <$>) . sequence <$> traverse go xs
+  fromParts :: [Antiquoted Text (m v)] -> m (Maybe NixString)
+  fromParts xs = fold <<$>> traverseM fun xs
 
-  go =
+  fun :: Antiquoted Text (m v) -> m (Maybe NixString)
+  fun =
     runAntiquoted
       "\n"
-      (pure . pure . makeNixStringWithoutContext)
+      (pure . pure . mkNixStringWithoutContext)
       (fromValueMay =<<)
 
 buildArgument
@@ -484,43 +491,42 @@
 buildArgument params arg =
   do
     scope <- currentScopes :: m (Scopes m v)
-    let argThunk = defer $ withScopes scope arg
+    let
+      argThunk = defer $ withScopes scope arg
     case params of
-      Param name -> M.singleton name <$> argThunk
-      ParamSet s isVariadic m ->
+      Param name -> one . (name,) <$> argThunk
+      ParamSet mname variadic pset ->
         do
-          (args, _) <- fromValue @(AttrSet v, AttrSet SourcePos) =<< arg
+          (args, _) <- fromValue @(AttrSet v, PositionSet) =<< arg
           let
             inject =
               maybe
                 id
-                (\ n -> M.insert n $ const argThunk) -- why insert into const?
-                m
-          loebM
-            (inject $
-                M.mapMaybe
-                  id
-                  (ialignWith
-                    (assemble scope isVariadic)
+                (`M.insert` const argThunk) -- why insert into const? Thunk value getting magic point?
+                mname
+          loebM $
+            inject $
+              M.mapMaybe
+                id
+                $ ialignWith
+                    (assemble scope variadic)
                     args
-                    $ M.fromList s
-                  )
-            )
+                    $ M.fromList pset
  where
   assemble
     :: Scopes m v
-    -> Bool
-    -> Text
+    -> Variadic
+    -> VarName
     -> These v (Maybe (m v))
     -> Maybe (AttrSet v -> m v)
-  assemble scope isVariadic k =
-    \case
-      That Nothing -> pure $ const $ evalError @v $ ErrorCall $ "Missing value for parameter: " <> show k
-      That (Just f) -> pure $ \args -> defer $ withScopes scope $ pushScope args f
-      This _
-        | isVariadic -> Nothing
-        | otherwise  -> pure $ const $ evalError @v $ ErrorCall $ "Unexpected parameter: " <> show k
-      These x _ -> pure $ const $ pure x
+  assemble _ Variadic _ (This _) = Nothing
+  assemble scope _ k t =
+    pure $
+      case t of
+        That Nothing -> const $ evalError @v $ ErrorCall $ "Missing value for parameter: ''" <> show k
+        That (Just f) -> coerce $ defer . withScopes scope . (`pushScope` f)
+        This _ -> const $ evalError @v $ ErrorCall $ "Unexpected parameter: " <> show k
+        These x _ -> const $ pure x
 
 -- | Add source positions to @NExprLoc@.
 --
@@ -533,7 +539,7 @@
 -- > -> NExprLoc -> m a
 addSourcePositions
   :: (MonadReader e m, Has e SrcSpan) => Transform NExprLocF (m a)
-addSourcePositions f (v@(AnnE ann _) :: NExprLoc) =
+addSourcePositions f (v@(Ann ann _) :: NExprLoc) =
   local (set hasLens ann) $ f v
 
 addStackFrames
@@ -571,4 +577,4 @@
   :: MonadNixEval v m
   => AnnF ann NExprF (m v)
   -> m v
-evalContent = eval . stripAnn
+evalContent = eval . stripAnnF
diff --git a/src/Nix/Exec.hs b/src/Nix/Exec.hs
--- a/src/Nix/Exec.hs
+++ b/src/Nix/Exec.hs
@@ -1,14 +1,13 @@
-{-# LANGUAGE AllowAmbiguousTypes #-}
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE PartialTypeSignatures #-}
-{-# LANGUAGE RankNTypes #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE UndecidableInstances #-}
+{-# language AllowAmbiguousTypes #-}
+{-# language CPP #-}
+{-# language ConstraintKinds #-}
+{-# language PartialTypeSignatures #-}
+{-# language RankNTypes #-}
+{-# language TypeFamilies #-}
+{-# language UndecidableInstances #-}
 
-{-# OPTIONS_GHC -Wno-orphans #-}
-{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
+{-# options_ghc -Wno-orphans #-}
+{-# options_ghc -fno-warn-name-shadowing #-}
 
 
 module Nix.Exec where
@@ -17,7 +16,7 @@
                                                 , putStrLn
                                                 , print
                                                 )
-
+import           GHC.Exception                  ( ErrorCall(ErrorCall) )
 import           Control.Monad.Catch     hiding ( catchJust )
 import           Control.Monad.Fix
 import           Data.Fix
@@ -29,7 +28,8 @@
 import           Nix.Convert
 import           Nix.Effects
 import           Nix.Eval                      as Eval
-import           Nix.Expr
+import           Nix.Expr.Types
+import           Nix.Expr.Types.Annotated
 import           Nix.Frames
 import           Nix.Options
 import           Nix.Pretty
@@ -38,7 +38,6 @@
 import           Nix.String
 import           Nix.String.Coerce
 import           Nix.Thunk
-import           Nix.Utils
 import           Nix.Value
 import           Nix.Value.Equal
 import           Nix.Value.Monad
@@ -72,7 +71,7 @@
 nvPathP
   :: MonadCited t f m
   => Provenance m (NValue t f m)
-  -> FilePath
+  -> Path
   -> NValue t f m
 nvPathP p x = addProvenance p $ nvPath x
 
@@ -86,7 +85,7 @@
 nvSetP
   :: MonadCited t f m
   => Provenance m (NValue t f m)
-  -> AttrSet SourcePos
+  -> PositionSet
   -> AttrSet (NValue t f m)
   -> NValue t f m
 nvSetP p x s = addProvenance p $ nvSet x s
@@ -102,7 +101,7 @@
 nvBuiltinP
   :: MonadCited t f m
   => Provenance m (NValue t f m)
-  -> Text
+  -> VarName
   -> (NValue t f m -> m (NValue t f m))
   -> NValue t f m
 nvBuiltinP p name f = addProvenance p $ nvBuiltin name f
@@ -140,21 +139,21 @@
 currentPos = asks $ view hasLens
 
 wrapExprLoc :: SrcSpan -> NExprLocF r -> NExprLoc
-wrapExprLoc span x = Fix $ Fix (NSym_ span "<?>") <$ x
+wrapExprLoc span x = Fix $ NSymAnn span "<?>" <$ x
 {-# inline wrapExprLoc #-}
 
 --  2021-01-07: NOTE: This instance belongs to be beside MonadEval type class.
 -- Currently instance is stuck in orphanage between the requirements to be MonadEval, aka Eval stage, and emposed requirement to be MonadNix (Execution stage). MonadNix constraint tries to put the cart before horse and seems superflous, since Eval in Nix also needs and can throw exceptions. It is between `nverr` and `evalError`.
 instance MonadNix e t f m => MonadEval (NValue t f m) m where
   freeVariable var =
-    nverr @e @t @f $ ErrorCall $ "Undefined variable '" <> toString var <> "'"
+    nverr @e @t @f $ ErrorCall $ toString @Text $ "Undefined variable '" <> coerce var <> "'"
 
   synHole name = do
     span  <- currentPos
     scope <- currentScopes
     evalError @(NValue t f m) $ SynHole $
       SynHoleInfo
-        { _synHoleInfo_expr  = Fix $ NSynHole_ span name
+        { _synHoleInfo_expr  = NSynHoleAnn span name
         , _synHoleInfo_scope = scope
         }
 
@@ -168,13 +167,13 @@
         )
         ms
        where
-        attr = Text.intercalate "." $ NE.toList ks
+        attr = Text.intercalate "." $ NE.toList $ coerce ks
 
   evalCurPos = do
     scope                  <- currentScopes
     span@(SrcSpan delta _) <- currentPos
     addProvenance @_ @_ @(NValue t f m)
-      (Provenance scope $ NSym_ span "__curPos") <$>
+      (Provenance scope $ NSymAnnF span (coerce @Text "__curPos")) <$>
         toValue delta
 
   evaledSym name val = do
@@ -182,13 +181,13 @@
     span  <- currentPos
     pure $
       addProvenance @_ @_ @(NValue t f m)
-        (Provenance scope $ NSym_ span name)
+        (Provenance scope $ NSymAnnF span name)
         val
 
   evalConstant c = do
     scope <- currentScopes
     span  <- currentPos
-    pure $ nvConstantP (Provenance scope $ NConstant_ span c) c
+    pure $ join (nvConstantP . Provenance scope . NConstantAnnF span) c
 
   evalString =
     maybe
@@ -198,10 +197,11 @@
           scope <- currentScopes
           span  <- currentPos
           pure $
-            nvStrP
-              (Provenance
-                scope
-                (NStr_ span $ DoubleQuoted [Plain $ stringIgnoreContext ns])
+            join
+              (nvStrP
+                . Provenance
+                  scope
+                  . NStrAnnF span . DoubleQuoted . one . Plain . stringIgnoreContext
               )
               ns
       )
@@ -210,14 +210,24 @@
   evalLiteralPath p = do
     scope <- currentScopes
     span  <- currentPos
-    nvPathP (Provenance scope $ NLiteralPath_ span p) <$>
-      makeAbsolutePath @t @f @m p
+    let
+      evalPath :: Path -> m (NValue t f m)
+      evalPath p1 =
+        fmap
+          (g p1)
+          (f p1)
+       where
+        g :: Path -> Path -> NValue t f m
+        g = nvPathP . Provenance scope . NLiteralPathAnnF span
+        f :: Path -> m Path
+        f = toAbsolutePath @t @f @m
+    evalPath p
 
   evalEnvPath p = do
     scope <- currentScopes
     span  <- currentPos
-    nvPathP (Provenance scope $ NEnvPath_ span p) <$>
-      findEnvPath @t @f @m p
+    nvPathP (Provenance scope $ NEnvPathAnnF span p) <$>
+      findEnvPath @t @f @m (coerce p)
 
   evalUnary op arg = do
     scope <- currentScopes
@@ -232,24 +242,24 @@
   evalWith c b = do
     scope <- currentScopes
     span  <- currentPos
-    let f = join $ addProvenance . Provenance scope . NWith_ span Nothing . pure
+    let f = join $ addProvenance . Provenance scope . NWithAnnF span Nothing . pure
     f <$> evalWithAttrSet c b
 
-  evalIf c t f = do
+  evalIf c tVal fVal = do
     scope <- currentScopes
     span  <- currentPos
-    b <- fromValue c
+    bl <- fromValue c
 
     let
-      fun x y z = addProvenance (Provenance scope $ NIf_ span (pure c) x y) z
+      fun x y = addProvenance (Provenance scope $ NIfAnnF span (pure c) x y)
       -- Note: join acts as \ f x -> f x x
-      false = join (fun Nothing . pure) <$> f
-      true = join (flip fun Nothing . pure) <$> t
+      falseVal = join (fun Nothing . pure) <$> fVal
+      trueVal = join (flip fun Nothing . pure) <$> tVal
 
     bool
-      false
-      true
-      b
+      falseVal
+      trueVal
+      bl
 
   evalAssert c body =
     do
@@ -259,25 +269,26 @@
         (nverr $ Assertion span c)
         (do
           scope <- currentScopes
-          let f = join (addProvenance . Provenance scope . NAssert_ span (pure c) . pure)
-          f <$> body
+          join (addProvenance . Provenance scope . NAssertAnnF span (pure c) . pure) <$> body
         )
         b
 
   evalApp f x = do
     scope <- currentScopes
     span  <- currentPos
-    addProvenance (Provenance scope $ NBinary_ span NApp (pure f) Nothing) <$>
+    addProvenance (Provenance scope $ NBinaryAnnF span NApp (pure f) Nothing) <$>
       (callFunc f =<< defer x)
 
   evalAbs p k = do
+    let
+      fk = flip k
     scope <- currentScopes
     span  <- currentPos
     pure $
       nvClosureP
-        (Provenance scope $ NAbs_ span (Nothing <$ p) Nothing)
+        (Provenance scope $ NAbsAnnF span (Nothing <$ p) Nothing)
         (void p)
-        (\arg -> snd <$> k (pure arg) (\_ b -> ((), ) <$> b))
+        $ fmap snd . fk (const $ fmap ((), )) . pure
 
   evalError = throwError
 
@@ -299,8 +310,8 @@
       NVBuiltin name f    ->
         do
           span <- currentPos
-          withFrame Info ((Calling @m @(NValue t f m)) name span) $ f arg -- Is this cool?
-      (NVSet m _) | Just f <- M.lookup "__functor" m ->
+          withFrame Info ((Calling @m @(NValue t f m)) (coerce name) span) $ f arg -- Is this cool?
+      (NVSet _ m) | Just f <- M.lookup "__functor" m ->
         (`callFunc` arg) =<< (`callFunc` fun') =<< demand f
       _x -> throwError $ ErrorCall $ "Attempt to call non-function: " <> show _x
 
@@ -311,7 +322,7 @@
   -> NUnaryOp
   -> NValue t f m
   -> m (NValue t f m)
-execUnaryOp scope span op arg = do
+execUnaryOp scope span op arg =
   case arg of
     NVConstant c ->
       case (op, c) of
@@ -323,7 +334,7 @@
     _x ->
       throwError $ ErrorCall $ "argument to unary operator must evaluate to an atomic type: " <> show _x
  where
-  unaryOp = pure . nvConstantP (Provenance scope $ NUnary_ span op $ pure arg)
+  unaryOp = pure . nvConstantP (Provenance scope $ NUnaryAnnF span op $ pure arg)
 
 execBinaryOp
   :: forall e t f m
@@ -351,6 +362,7 @@
 
  where
 
+  helperEq :: (Bool -> Bool) -> m (NValue t f m)
   helperEq flag =
     do
       rval <- rarg
@@ -375,7 +387,7 @@
   toBoolOp r b =
     pure $
       nvConstantP
-        (Provenance scope $ NBinary_ span op (pure lval) r)
+        (Provenance scope $ NBinaryAnnF span op (pure lval) r)
         (NBool b)
 
 execBinaryOpForced
@@ -403,9 +415,9 @@
 
   NUpdate ->
     case (lval, rval) of
-      (NVSet ls lp, NVSet rs rp) -> pure $ nvSetP prov (rp <> lp) (rs <> ls)
-      (NVSet ls lp, NVConstant NNull) -> pure $ nvSetP prov lp ls
-      (NVConstant NNull, NVSet rs rp) -> pure $ nvSetP prov rp rs
+      (NVSet lp ls, NVSet rp rs) -> pure $ nvSetP prov (rp <> lp) (rs <> ls)
+      (NVSet lp ls, NVConstant NNull) -> pure $ nvSetP prov lp ls
+      (NVConstant NNull, NVSet rp rs) -> pure $ nvSetP prov rp rs
       _ -> unsupportedTypes
 
   NPlus ->
@@ -421,10 +433,10 @@
           (throwError $ ErrorCall "A string that refers to a store path cannot be appended to a path.") -- data/nix/src/libexpr/eval.cc:1412
           (\ rs2 ->
             nvPathP prov <$>
-              makeAbsolutePath @t @f (ls <> toString rs2)
+              toAbsolutePath @t @f (ls <> coerce (toString rs2))
           )
           (getStringNoContext rs)
-      (NVPath ls, NVPath rs) -> nvPathP prov <$> makeAbsolutePath @t @f (ls <> rs)
+      (NVPath ls, NVPath rs) -> nvPathP prov <$> toAbsolutePath @t @f (ls <> rs)
 
       (ls@NVSet{}, NVStr rs) ->
         (\ls2 -> nvStrP prov (ls2 <> rs)) <$>
@@ -443,7 +455,7 @@
 
  where
   prov :: Provenance m (NValue t f m)
-  prov = Provenance scope $ NBinary_ span op (pure lval) (pure rval)
+  prov = Provenance scope $ NBinaryAnnF span op (pure lval) (pure rval)
 
   toBool = pure . nvConstantP prov . NBool
   compare :: (forall a. Ord a => a -> a -> Bool) -> m (NValue t f m)
@@ -452,8 +464,8 @@
     (NVStr l, NVStr r) -> toBool $ l `op` r
     _ -> unsupportedTypes
 
-  toInt = pure . nvConstantP prov . NInt
-  toFloat = pure . nvConstantP prov . NFloat
+  nvInt = pure . nvConstantP prov . NInt
+  nvFloat = pure . nvConstantP prov . NFloat
 
   numBinOp :: (forall a. Num a => a -> a -> a) -> m (NValue t f m)
   numBinOp op = numBinOp' op op
@@ -462,13 +474,12 @@
     :: (Integer -> Integer -> Integer)
     -> (Float -> Float -> Float)
     -> m (NValue t f m)
-
   numBinOp' intOp floatOp = case (lval, rval) of
     (NVConstant l, NVConstant r) -> case (l, r) of
-      (NInt   li, NInt   ri) -> toInt $ li `intOp` ri
-      (NInt   li, NFloat rf) -> toFloat $ fromInteger li `floatOp` rf
-      (NFloat lf, NInt   ri) -> toFloat $ lf `floatOp` fromInteger ri
-      (NFloat lf, NFloat rf) -> toFloat $ lf `floatOp` rf
+      (NInt   li, NInt   ri) -> nvInt $ li `intOp` ri
+      (NInt   li, NFloat rf) -> nvFloat $ fromInteger li `floatOp` rf
+      (NFloat lf, NInt   ri) -> nvFloat $ lf `floatOp` fromInteger ri
+      (NFloat lf, NFloat rf) -> nvFloat $ lf `floatOp` rf
       _ -> unsupportedTypes
     _ -> unsupportedTypes
 
@@ -502,7 +513,7 @@
   depth <- ask
   guard $ depth < 2000
   local succ $ do
-    v'@(AnnFP span x) <- sequence v
+    v'@(AnnF span x) <- sequenceA v
     pure $ do
       opts :: Options <- asks $ view hasLens
       let
diff --git a/src/Nix/Expr/Shorthands.hs b/src/Nix/Expr/Shorthands.hs
--- a/src/Nix/Expr/Shorthands.hs
+++ b/src/Nix/Expr/Shorthands.hs
@@ -1,251 +1,447 @@
 
 -- | A bunch of shorthands for making nix expressions.
 --
--- Functions with an @F@ suffix return a more general type without the outer
--- 'Fix' wrapper.
+-- Functions with an @F@ suffix return a more general type (base functor @F a@) without the outer
+-- 'Fix' wrapper that creates @a@.
 module Nix.Expr.Shorthands where
 
 import           Data.Fix
 import           Nix.Atoms
 import           Nix.Expr.Types
-import           Text.Megaparsec.Pos            ( SourcePos )
 
--- | Make an integer literal expression.
-mkInt :: Integer -> NExpr
-mkInt = Fix . mkIntF
+-- * Basic expression builders
 
+-- | Put @NAtom@ as expression
+mkConst :: NAtom -> NExpr
+mkConst = Fix . NConstant
 
-mkIntF :: Integer -> NExprF a
-mkIntF = NConstant . NInt
+-- | Put null.
+mkNull :: NExpr
+mkNull = Fix mkNullF
 
--- | Make an floating point literal expression.
+-- | Put boolean.
+mkBool :: Bool -> NExpr
+mkBool = Fix . mkBoolF
+
+-- | Put integer.
+mkInt :: Integer -> NExpr
+mkInt = Fix . mkIntF
+
+-- | Put floating point number.
 mkFloat :: Float -> NExpr
 mkFloat = Fix . mkFloatF
 
-mkFloatF :: Float -> NExprF a
-mkFloatF = NConstant . NFloat
-
--- | Make a regular (double-quoted) string.
+-- | Put a regular (double-quoted) string.
 mkStr :: Text -> NExpr
-mkStr = Fix . NStr . DoubleQuoted . \case
-  "" -> mempty
-  x  -> [Plain x]
+mkStr = Fix . NStr . DoubleQuoted .
+  whenText
+    mempty
+    (one . Plain)
 
--- | Make an indented string.
+-- | Put an indented string.
 mkIndentedStr :: Int -> Text -> NExpr
-mkIndentedStr w = Fix . NStr . Indented w . \case
-  "" -> mempty
-  x  -> [Plain x]
+mkIndentedStr w = Fix . NStr . Indented w .
+  whenText
+    mempty
+    (one . Plain)
 
--- | Make a path. Use 'True' if the path should be read from the
--- environment, else 'False'.
+-- | Put a path. Use @True@ if the path should be read from the environment, else use @False@.
 mkPath :: Bool -> FilePath -> NExpr
 mkPath b = Fix . mkPathF b
 
-mkPathF :: Bool -> FilePath -> NExprF a
-mkPathF False = NLiteralPath
-mkPathF True  = NEnvPath
-
--- | Make a path expression which pulls from the NIX_PATH env variable.
+-- | Put a path expression which pulls from the @NIX_PATH@ @env@ variable.
 mkEnvPath :: FilePath -> NExpr
 mkEnvPath = Fix . mkEnvPathF
 
-mkEnvPathF :: FilePath -> NExprF a
-mkEnvPathF = mkPathF True
-
--- | Make a path expression which references a relative path.
+-- | Put a path which references a relative path.
 mkRelPath :: FilePath -> NExpr
 mkRelPath = Fix . mkRelPathF
 
-mkRelPathF :: FilePath -> NExprF a
-mkRelPathF = mkPathF False
-
--- | Make a variable (symbol)
+-- | Put a variable (symbol).
 mkSym :: Text -> NExpr
 mkSym = Fix . mkSymF
 
-mkSymF :: Text -> NExprF a
-mkSymF = NSym
-
+-- | Put syntactic hole.
 mkSynHole :: Text -> NExpr
 mkSynHole = Fix . mkSynHoleF
 
-mkSynHoleF :: Text -> NExprF a
-mkSynHoleF = NSynHole
-
 mkSelector :: Text -> NAttrPath NExpr
-mkSelector = (:| mempty) . StaticKey
+mkSelector = one . StaticKey . coerce
 
-mkBool :: Bool -> NExpr
-mkBool = Fix . mkBoolF
+-- | Put an unary operator.
+--  @since 0.15.0
+mkOp :: NUnaryOp -> NExpr -> NExpr
+mkOp op = Fix . NUnary op
 
-mkBoolF :: Bool -> NExprF a
-mkBoolF = NConstant . NBool
+-- | Logical negation: @not@.
+mkNot :: NExpr -> NExpr
+mkNot = mkOp NNot
 
-mkNull :: NExpr
-mkNull = Fix mkNullF
+-- | Number negation: @-@.
+--
+-- Negation in the language works with integers and floating point.
+--  @since 0.15.0
+mkNeg :: NExpr -> NExpr
+mkNeg = mkOp NNeg
 
-mkNullF :: NExprF a
-mkNullF = NConstant NNull
+-- | Put a binary operator.
+--  @since 0.15.0
+mkOp2 :: NBinaryOp -> NExpr -> NExpr -> NExpr
+mkOp2 op a = Fix . NBinary op a
 
-mkOper :: NUnaryOp -> NExpr -> NExpr
-mkOper op = Fix . NUnary op
+-- | > { x }
+--  @since 0.15.0
+mkParamSet :: [(Text, Maybe NExpr)] -> Params NExpr
+mkParamSet pset = mkGeneralParamSet Nothing pset False
 
-mkOper2 :: NBinaryOp -> NExpr -> NExpr -> NExpr
-mkOper2 op a = Fix . NBinary op a
+-- | > { x, ... }
+--  @since 0.15.0
+mkVariadicParamSet :: [(Text, Maybe NExpr)] -> Params NExpr
+mkVariadicParamSet pset = mkGeneralParamSet Nothing pset True
 
-mkParamset :: [(Text, Maybe NExpr)] -> Bool -> Params NExpr
-mkParamset params variadic = ParamSet params variadic mempty
+-- | > s@{ x }
+--  @since 0.15.0
+mkNamedParamSet :: Text -> [(Text, Maybe NExpr)] -> Params NExpr
+mkNamedParamSet name pset = mkGeneralParamSet (pure name) pset False
 
+-- | > s@{ x, ... }
+--  @since 0.15.0
+mkNamedVariadicParamSet :: Text -> [(Text, Maybe NExpr)] -> Params NExpr
+mkNamedVariadicParamSet name params = mkGeneralParamSet (pure name) params True
+
+-- | Args:
+--
+-- 1. Maybe name:
+--
+-- > Nothing  ->   {}
+-- > Just "s" -> s@{}
+--
+-- 2. key:expr pairs
+--
+-- 3. Is variadic or not:
+--
+-- > True  -> {...}
+-- > False -> {}
+--  @since 0.15.0
+mkGeneralParamSet :: Maybe Text -> [(Text, Maybe NExpr)] -> Bool -> Params NExpr
+mkGeneralParamSet mname params variadic = ParamSet (coerce mname) (Variadic `whenTrue` variadic) (coerce params)
+
+-- | > rec { .. }
 mkRecSet :: [Binding NExpr] -> NExpr
-mkRecSet = Fix . NSet NRecursive
+mkRecSet = mkSet Recursive
 
+-- | Put a non-recursive set.
+--
+-- > { .. }
 mkNonRecSet :: [Binding NExpr] -> NExpr
-mkNonRecSet = Fix . NSet NNonRecursive
+mkNonRecSet = mkSet mempty
 
-mkLets :: [Binding NExpr] -> NExpr -> NExpr
-mkLets bindings = Fix . NLet bindings
+-- | General set builder function.
+mkSet :: Recursivity -> [Binding NExpr] -> NExpr
+mkSet r = Fix . NSet r
 
+-- | Empty set.
+--
+-- Monoid. Use @//@ operation (shorthand $//) to extend the set.
+--  @since 0.15.0
+emptySet :: NExpr
+emptySet = mkNonRecSet mempty
+
+-- | Put a list.
 mkList :: [NExpr] -> NExpr
 mkList = Fix . NList
 
+--  @since 0.15.0
+emptyList :: NExpr
+emptyList = mkList mempty
+
+-- | Wrap in a @let@.
+--
+-- (Evaluate the second argument after introducing the bindings.)
+--
+-- +------------------------+-----------------+
+-- | Haskell                | Nix             |
+-- +========================+=================+
+-- | @mkLets bindings expr@ | @let bindings;@ |
+-- |                        | @in expr@       |
+-- +------------------------+-----------------+
+mkLets :: [Binding NExpr] -> NExpr -> NExpr
+mkLets bindings = Fix . NLet bindings
+
+-- | Create a @whith@:
+-- 1st expr - what to bring into the scope.
+-- 2nd - expression that recieves the scope extention.
+--
+-- +--------------------+-------------------+
+-- | Haskell            | Nix               |
+-- +====================+===================+
+-- | @mkWith body main@ | @with body; expr@ |
+-- +--------------------+-------------------+
 mkWith :: NExpr -> NExpr -> NExpr
 mkWith e = Fix . NWith e
 
+-- | Create an @assert@:
+-- 1st expr - asserting itself, must return @true@.
+-- 2nd - main expression to evaluated after assertion.
+--
+-- +-----------------------+----------------------+
+-- | Haskell               | Nix                  |
+-- +=======================+======================+
+-- | @mkAssert check eval@ | @assert check; eval@ |
+-- +-----------------------+----------------------+
 mkAssert :: NExpr -> NExpr -> NExpr
-mkAssert e = Fix . NWith e
+mkAssert e = Fix . NAssert e
 
+-- | Put:
+--
+-- > if expr1
+-- >   then expr2
+-- >   else expr3
 mkIf :: NExpr -> NExpr -> NExpr -> NExpr
 mkIf e1 e2 = Fix . NIf e1 e2
 
+-- | Lambda function, analog of Haskell's @\\ x -> x@:
+--
+-- +-----------------------+-----------+
+-- | Haskell               | Nix       |
+-- +=======================+===========+
+-- | @ mkFunction x expr @ | @x: expr@ |
+-- +-----------------------+-----------+
 mkFunction :: Params NExpr -> NExpr -> NExpr
 mkFunction params = Fix . NAbs params
 
-{-
-mkDot :: NExpr -> Text -> NExpr
-mkDot e key = mkDots e [key]
+-- | General dot-reference with optional alternative if the jey does not exist.
+--  @since 0.15.0
+getRefOrDefault :: Maybe NExpr -> NExpr -> Text -> NExpr
+getRefOrDefault alt obj = Fix . NSelect alt obj . mkSelector
 
--- | Create a dotted expression using only text.
-mkDots :: NExpr -> [Text] -> NExpr
-mkDots e [] = e
-mkDots (Fix (NSelect e keys' x)) keys =
-  -- Special case: if the expression in the first argument is already
-  -- a dotted expression, just extend it.
-  Fix (NSelect e (keys' <> fmap (`StaticKey` Nothing) keys) x)
-mkDots e keys = Fix $ NSelect e (fmap (`StaticKey` Nothing) keys) Nothing
--}
+-- ** Base functor builders for basic expressions builders *sic
 
--- | An `inherit` clause without an expression to pull from.
-inherit :: [NKeyName e] -> SourcePos -> Binding e
-inherit = Inherit Nothing
+-- | Unfixed @mkNull@.
+mkNullF :: NExprF a
+mkNullF = NConstant NNull
 
+-- | Unfixed @mkBool@.
+mkBoolF :: Bool -> NExprF a
+mkBoolF = NConstant . NBool
+
+-- | Unfixed @mkInt@.
+mkIntF :: Integer -> NExprF a
+mkIntF = NConstant . NInt
+
+-- | Unfixed @mkFloat@.
+mkFloatF :: Float -> NExprF a
+mkFloatF = NConstant . NFloat
+
+-- | Unfixed @mkPath@.
+mkPathF :: Bool -> FilePath -> NExprF a
+mkPathF False = NLiteralPath . coerce
+mkPathF True  = NEnvPath . coerce
+
+-- | Unfixed @mkEnvPath@.
+mkEnvPathF :: FilePath -> NExprF a
+mkEnvPathF = mkPathF True
+
+-- | Unfixed @mkRelPath@.
+mkRelPathF :: FilePath -> NExprF a
+mkRelPathF = mkPathF False
+
+-- | Unfixed @mkSym@.
+mkSymF :: Text -> NExprF a
+mkSymF = NSym . coerce
+
+-- | Unfixed @mkSynHole@.
+mkSynHoleF :: Text -> NExprF a
+mkSynHoleF = NSynHole . coerce
+
+
+-- * Other
+-- (org this better/make a better name for section(s))
+
 -- | An `inherit` clause with an expression to pull from.
-inheritFrom :: e -> [NKeyName e] -> SourcePos -> Binding e
-inheritFrom expr = Inherit (pure expr)
+--
+-- +------------------------+--------------------+------------+
+-- | Hask                   | Nix                | pseudocode |
+-- +========================+====================+============+
+-- | @inheritFrom x [a, b]@ | @inherit (x) a b;@ | @a = x.a;@ |
+-- |                        |                    | @b = x.b;@ |
+-- +------------------------+--------------------+------------+
+inheritFrom :: e -> [VarName] -> Binding e
+inheritFrom expr ks = Inherit (pure expr) ks nullPos
 
--- | Shorthand for producing a binding of a name to an expression: @=
-bindTo :: Text -> NExpr -> Binding NExpr
-bindTo name x = NamedVar (mkSelector name) x nullPos
+-- | An `inherit` clause without an expression to pull from.
+--
+-- +----------------------+----------------+------------------+
+-- | Hask                 | Nix            | pseudocode       |
+-- +======================+================+==================+
+-- | @inheritFrom [a, b]@ | @inherit a b;@ | @a = outside.a;@ |
+-- |                      |                | @b = outside.b;@ |
+-- +----------------------+----------------+------------------+
+inherit :: [VarName] -> Binding e
+inherit ks = Inherit Nothing ks nullPos
 
--- | Infix version of @bindTo@: @=@
+-- | Nix @=@ (bind operator).
 ($=) :: Text -> NExpr -> Binding NExpr
 ($=) = bindTo
-
 infixr 2 $=
 
+-- | Shorthand for producing a binding of a name to an expression: Nix's @=@.
+bindTo :: Text -> NExpr -> Binding NExpr
+bindTo name x = NamedVar (mkSelector name) x nullPos
+
 -- | Append a list of bindings to a set or let expression.
--- For example, adding `[a = 1, b = 2]` to `let c = 3; in 4` produces
--- `let a = 1; b = 2; c = 3; in 4`.
+-- For example:
+-- adding      `[a = 1, b = 2]`
+-- to       `let               c = 3; in 4`
+-- produces `let a = 1; b = 2; c = 3; in 4`.
 appendBindings :: [Binding NExpr] -> NExpr -> NExpr
-appendBindings newBindings (Fix e) = case e of
-  NLet bindings e'    -> Fix $ NLet (bindings <> newBindings) e'
-  NSet recur bindings -> Fix $ NSet recur (bindings <> newBindings)
-  _                   -> error "Can only append bindings to a set or a let"
+appendBindings newBindings (Fix e) =
+  case e of
+    NLet bindings e'    -> mkLets (bindings <> newBindings) e'
+    NSet recur bindings -> Fix $ NSet recur (bindings <> newBindings)
+    _                   -> error "Can only append bindings to a set or a let"
 
--- | Applies a transformation to the body of a nix function.
+-- | Applies a transformation to the body of a Nix function.
 modifyFunctionBody :: (NExpr -> NExpr) -> NExpr -> NExpr
-modifyFunctionBody f (Fix (NAbs params body)) = Fix $ NAbs params $ f body
+modifyFunctionBody transform (Fix (NAbs params body)) = mkFunction params $ transform body
 modifyFunctionBody _ _ = error "Not a function"
 
--- | A let statement with multiple assignments.
+-- | A @let@ statement with multiple assignments.
 letsE :: [(Text, NExpr)] -> NExpr -> NExpr
-letsE pairs = Fix . NLet (uncurry bindTo <$> pairs)
+letsE pairs = mkLets $ uncurry ($=) <$> pairs
 
 -- | Wrapper for a single-variable @let@.
 letE :: Text -> NExpr -> NExpr -> NExpr
-letE varName varExpr = letsE [(varName, varExpr)]
+letE varName varExpr = letsE $ one (varName, varExpr)
 
--- | Make an attribute set (non-recursive).
+-- | Make a non-recursive attribute set.
 attrsE :: [(Text, NExpr)] -> NExpr
-attrsE pairs = Fix $ NSet NNonRecursive $ uncurry bindTo <$> pairs
+attrsE pairs = mkNonRecSet $ uncurry ($=) <$> pairs
 
--- | Make an attribute set (recursive).
+-- | Make a recursive attribute set.
 recAttrsE :: [(Text, NExpr)] -> NExpr
-recAttrsE pairs = Fix $ NSet NRecursive $ uncurry bindTo <$> pairs
+recAttrsE pairs = mkRecSet $ uncurry ($=) <$> pairs
 
--- | Logical negation.
-mkNot :: NExpr -> NExpr
-mkNot = Fix . NUnary NNot
 
--- -- | Dot-reference into an attribute set.
--- (!.) :: NExpr -> Text -> NExpr
--- (!.) = mkDot
--- infixl 8 !.
-
 -- * Nix binary operators
 
--- | Nix binary operator builder.
-mkBinop :: NBinaryOp -> NExpr -> NExpr -> NExpr
-mkBinop op e1 e2 = Fix $ NBinary op e1 e2
-
 (@@), ($==), ($!=), ($<), ($<=), ($>), ($>=), ($&&), ($||), ($->), ($//), ($+), ($-), ($*), ($/), ($++)
   :: NExpr -> NExpr -> NExpr
+--  2021-07-10: NOTE: Probably the presedence of some operators is still needs to be tweaked.
+
+-- | Dot-reference into an attribute set: @attrSet.k@
+(@.) :: NExpr -> Text -> NExpr
+(@.) = getRefOrDefault Nothing
+infix 9 @.
+
+-- | Dot-reference into an attribute set with alternative if the key does not exist.
+--
+-- > s.x or y
+--  @since 0.15.0
+(@.<|>) :: NExpr -> Text -> NExpr -> NExpr
+(@.<|>) obj name alt = getRefOrDefault (pure alt ) obj name
+infix 9 @.<|>
+
 -- | Function application (@' '@ in @f x@)
-(@@) = mkBinop NApp
-infixl 1 @@
--- | Equality: @==@
-($==) = mkBinop NEq
--- | Inequality: @!=@
-($!=) = mkBinop NNEq
--- | Less than: @<@
-($<)  = mkBinop NLt
--- | Less than OR equal: @<=@
-($<=) = mkBinop NLte
+(@@) = mkOp2 NApp
+infixl 8 @@
+
+-- | List concatenation: @++@
+($++) = mkOp2 NConcat
+infixr 7 $++
+
+-- | Multiplication: @*@
+($*)  = mkOp2 NMult
+infixl 6 $*
+
+-- | Division: @/@
+($/)  = mkOp2 NDiv
+infixl 6 $/
+
+-- | Addition: @+@
+($+)  = mkOp2 NPlus
+infixl 5 $+
+
+-- | Subtraction: @-@
+($-)  = mkOp2 NMinus
+infixl 5 $-
+
+-- | Extend/override the left attr set, with the right one: @//@
+($//) = mkOp2 NUpdate
+infixr 5 $//
+
 -- | Greater than: @>@
-($>)  = mkBinop NGt
+($>)  = mkOp2 NGt
+infix 4 $>
+
 -- | Greater than OR equal: @>=@
-($>=) = mkBinop NGte
+infix 4 $>=
+($>=) = mkOp2 NGte
+
+-- | Less than OR equal: @<=@
+($<=) = mkOp2 NLte
+infix 4 $<=
+
+-- | Less than: @<@
+($<)  = mkOp2 NLt
+infix 4 $<
+
+-- | Equality: @==@
+($==) = mkOp2 NEq
+infix 3 $==
+
+-- | Inequality: @!=@
+($!=) = mkOp2 NNEq
+infix 3 $!=
+
 -- | AND: @&&@
-($&&) = mkBinop NAnd
+($&&) = mkOp2 NAnd
+infixl 2 $&&
+
 -- | OR: @||@
-($||) = mkBinop NOr
--- | Logical implication: @->@
-($->) = mkBinop NImpl
--- | Extend/override the left attr set, with the right one: @//@
-($//) = mkBinop NUpdate
--- | Addition: @+@
-($+)  = mkBinop NPlus
--- | Subtraction: @-@
-($-)  = mkBinop NMinus
--- | Multiplication: @*@
-($*)  = mkBinop NMult
--- | Division: @/@
-($/)  = mkBinop NDiv
--- | List concatenation: @++@
-($++) = mkBinop NConcat
+($||) = mkOp2 NOr
+infixl 2 $||
 
+-- | Logical implication: @->@
+($->) = mkOp2 NImpl
+infix 1 $->
 
--- | Lambda function.
--- > x ==> x
---Haskell:
--- > \\ x -> x
---Nix:
--- > x: x
+-- | Lambda function, analog of Haskell's @\\ x -> x@:
+--
+-- +---------------+-----------+
+-- | Haskell       | Nix       |
+-- +===============+===========+
+-- | @x ==> expr @ | @x: expr@ |
+-- +---------------+-----------+
 (==>) :: Params NExpr -> NExpr -> NExpr
 (==>) = mkFunction
 infixr 1 ==>
 
--- | Dot-reference into an attribute set: @attrSet.k@
-(@.) :: NExpr -> Text -> NExpr
-(@.) obj name = Fix $ NSelect obj (StaticKey name :| mempty) Nothing
-infixl 2 @.
+
+-- * Under deprecation
+
+-- NOTE: Remove after 2023-07
+-- | __@Deprecated@__: Please, use `mkOp`
+-- Put an unary operator.
+mkOper :: NUnaryOp -> NExpr -> NExpr
+mkOper op = Fix . NUnary op
+
+-- NOTE: Remove after 2023-07
+-- | __@Deprecated@__: Please, use `mkOp2`
+-- | Put a binary operator.
+mkOper2 :: NBinaryOp -> NExpr -> NExpr -> NExpr
+mkOper2 op a = Fix . NBinary op a
+
+-- NOTE: Remove after 2023-07
+-- | __@Deprecated@__: Please, use `mkOp2`
+-- | Nix binary operator builder.
+mkBinop :: NBinaryOp -> NExpr -> NExpr -> NExpr
+mkBinop = mkOp2
+
+-- NOTE: Remove after 2023-07
+-- | __@Deprecated@__: Please, use:
+--   * `mkParamSet` is for closed sets;
+--   * `mkVariadicSet` is for variadic;
+--   * `mkGeneralParamSet` a general constructor.
+mkParamset :: [(Text, Maybe NExpr)] -> Bool -> Params NExpr
+mkParamset params variadic = ParamSet Nothing (Variadic `whenTrue` variadic) (coerce params)
diff --git a/src/Nix/Expr/Strings.hs b/src/Nix/Expr/Strings.hs
--- a/src/Nix/Expr/Strings.hs
+++ b/src/Nix/Expr/Strings.hs
@@ -2,7 +2,6 @@
 -- | Functions for manipulating nix strings.
 module Nix.Expr.Strings where
 
-import           Nix.Utils
 import           Relude.Unsafe                 as Unsafe
 -- Please, switch things to NonEmpty
 import           Data.List                      ( dropWhileEnd
@@ -10,7 +9,7 @@
                                                 , lookup
                                                 )
 import qualified Data.Text                     as T
-import           Nix.Expr
+import           Nix.Expr.Types
 
 -- | Merge adjacent @Plain@ values with @<>@.
 mergePlain :: [Antiquoted Text r] -> [Antiquoted Text r]
@@ -22,8 +21,8 @@
 
 -- | Remove 'Plain' values equal to 'mempty', as they don't have any
 -- informational content.
-removePlainEmpty :: [Antiquoted Text r] -> [Antiquoted Text r]
-removePlainEmpty = filter f where
+removeEmptyPlains :: [Antiquoted Text r] -> [Antiquoted Text r]
+removeEmptyPlains = filter f where
   f (Plain x) = x /= mempty
   f _         = True
 
@@ -42,17 +41,18 @@
 -- | Split a stream representing a string with antiquotes on line breaks.
 splitLines :: [Antiquoted Text r] -> [[Antiquoted Text r]]
 splitLines = uncurry (flip (:)) . go where
-  go (Plain t : xs) = (Plain l :) <$> foldr f (go xs) ls   where
+  go (Plain t : xs) = (Plain l :) <$> foldr f (go xs) ls
+   where
     (l : ls) = T.split (== '\n') t
     f prefix (finished, current) = ((Plain prefix : current) : finished, mempty)
   go (Antiquoted a   : xs) = (Antiquoted a :) <$> go xs
   go (EscapedNewline : xs) = (EscapedNewline :) <$> go xs
-  go []                    = (mempty, mempty)
+  go []                    = mempty
 
 -- | Join a stream of strings containing antiquotes again. This is the inverse
 -- of 'splitLines'.
 unsplitLines :: [[Antiquoted Text r]] -> [Antiquoted Text r]
-unsplitLines = intercalate [Plain "\n"]
+unsplitLines = intercalate $ one $ Plain "\n"
 
 -- | Form an indented string by stripping spaces equal to the minimal indent.
 stripIndent :: [Antiquoted Text r] -> NString r
@@ -60,7 +60,7 @@
 stripIndent xs =
   Indented
     minIndent
-    (removePlainEmpty $
+    (removeEmptyPlains $
       mergePlain $
         (snd <$>) $
           dropWhileEnd
@@ -111,7 +111,7 @@
   [('\n', 'n'), ('\r', 'r'), ('\t', 't'), ('\\', '\\'), ('$', '$'), ('"', '"')]
 
 fromEscapeCode :: Char -> Maybe Char
-fromEscapeCode = (`lookup` fmap swap escapeCodes)
+fromEscapeCode = (`lookup` (swap <$> escapeCodes))
 
 toEscapeCode :: Char -> Maybe Char
 toEscapeCode = (`lookup` escapeCodes)
diff --git a/src/Nix/Expr/Types.hs b/src/Nix/Expr/Types.hs
--- a/src/Nix/Expr/Types.hs
+++ b/src/Nix/Expr/Types.hs
@@ -1,40 +1,38 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE DeriveAnyClass #-}
-{-# LANGUAGE FunctionalDependencies #-}
-{-# LANGUAGE RankNTypes #-}
-{-# LANGUAGE TemplateHaskell #-}
-{-# LANGUAGE TypeFamilies #-}
+{-# language ConstraintKinds #-}
+{-# language CPP #-}
+{-# language DeriveAnyClass #-}
+{-# language FunctionalDependencies #-}
+{-# language RankNTypes #-}
+{-# language TemplateHaskell #-}
+{-# language TypeFamilies #-}
 
-{-# OPTIONS_GHC -Wno-orphans #-}
-{-# OPTIONS_GHC -Wno-missing-signatures #-}
+{-# options_ghc -Wno-orphans #-}
+{-# options_ghc -Wno-missing-signatures #-}
 
 -- | The Nix expression type and supporting types.
 --
--- For a brief introduction of the Nix expression language, see
--- <https://nixos.org/nix/manual/#ch-expression-language>.
+-- [Brief introduction of the Nix expression language.](https://nixos.org/nix/manual/#ch-expression-language)
 --
 -- This module is a beginning of a deep embedding (term) of a Nix language into Haskell.
--- Shallow/deep embedding brief:
--- <https://web.archive.org/web/20201112031804/https://alessandrovermeulen.me/2013/07/13/the-difference-between-shallow-and-deep-embedding/>
+-- [Brief on shallow & deep embedding.](https://web.archive.org/web/20201112031804/https://alessandrovermeulen.me/2013/07/13/the-difference-between-shallow-and-deep-embedding/)
 --
--- (additiona info for dev): Big use of TemplateHaskell in the module requires proper (top-down) organization of declarations
+-- (additiona info for dev): Big use of TemplateHaskell in the module requires proper (top-down) organization of declarations.
 module Nix.Expr.Types where
 
 import qualified Codec.Serialise                as Serialise
 import           Codec.Serialise                ( Serialise )
-import           Control.DeepSeq
+import           Control.DeepSeq                ( NFData1(..) )
 import           Data.Aeson
-import           Data.Aeson.TH
 import qualified Data.Binary                   as Binary
 import           Data.Binary                    ( Binary )
 import           Data.Data
-import           Data.Fix
+import           Data.Fix                       ( Fix(Fix) )
 import           Data.Functor.Classes
 import           Data.Hashable.Lifted
+import qualified Data.HashMap.Lazy             as MapL
 import qualified Data.List.NonEmpty            as NE
 import qualified Text.Show
-import           Data.Traversable
+import           Data.Traversable               ( fmapDefault, foldMapDefault )
 import           GHC.Generics
 import qualified Language.Haskell.TH.Syntax    as TH
 import           Lens.Family2
@@ -44,22 +42,31 @@
                                                 , mkPos
                                                 , unPos
                                                 )
-import           Text.Show.Deriving
-import           Text.Read.Deriving
-import           Data.Eq.Deriving
-import           Data.Ord.Deriving
+import           Text.Show.Deriving             ( deriveShow1, deriveShow2 )
+import           Text.Read.Deriving             ( deriveRead1, deriveRead2 )
+import           Data.Eq.Deriving               ( deriveEq1  , deriveEq2   )
+import           Data.Ord.Deriving              ( deriveOrd1 , deriveOrd2  )
+import           Data.Aeson.TH                  ( deriveJSON2 )
 import qualified Type.Reflection               as Reflection
-import           Type.Reflection                ( eqTypeRep )
 import           Nix.Atoms
-import           Nix.Utils
 #if !MIN_VERSION_text(1,2,4)
 -- NOTE: Remove package @th-lift-instances@ removing this
-import           Instances.TH.Lift              ()  -- importing Lift Text fo GHC 8.6
+import           Instances.TH.Lift              ()  -- importing Lift Text for GHC 8.6
 #endif
 
 
--- * Utilitary: orphan instances
+-- * utils
 
+-- | Holds file positionng information for abstrations.
+-- A type synonym for @HashMap VarName SourcePos@.
+type PositionSet = HashMap VarName SourcePos
+
+--  2021-07-16: NOTE: Should replace @ParamSet@ List
+-- | > Hashmap VarName -- type synonym
+type AttrSet = HashMap VarName
+
+-- ** orphan instances
+
 -- Placed here because TH inference depends on declaration sequence.
 
 -- Upstreaming so far was not pursued.
@@ -102,24 +109,53 @@
   parseJSON = fmap mkPos . parseJSON
 instance FromJSON SourcePos
 
-
 -- * Components of Nix expressions
 
 -- NExpr is a composition of
---   * direct reuse of the Haskell types (list, FilePath, Text)
+--   * direct reuse of the Haskell types (list, Path, Text)
 --   * NAtom
 --   * Types in this section
 --   * Fixpoint nature
 
-type VarName = Text
+-- ** newtype VarName
 
+newtype VarName = VarName Text
+  deriving
+    ( Eq, Ord, Generic
+    , Typeable, Data, NFData, Serialise, Binary, ToJSON, FromJSON
+    , Show, Read, Hashable
+    )
 
--- ** @Params@
+instance IsString VarName where
+  fromString = coerce . fromString @Text
 
+instance ToString VarName where
+  toString = toString @Text . coerce
+
+-- ** data Params
+
+-- *** utils
+
 -- This uses an association list because nix XML serialization preserves the
 -- order of the param set.
 type ParamSet r = [(VarName, Maybe r)]
 
+data Variadic = Closed | Variadic
+  deriving
+    ( Eq, Ord, Generic
+    , Typeable, Data, NFData, Serialise, Binary, ToJSON, FromJSON
+    , Show, Read, Hashable
+    )
+
+instance Semigroup Variadic where
+  (<>) Closed Closed = Closed
+  (<>) _      _      = Variadic
+
+instance Monoid Variadic where
+  mempty = Closed
+
+-- *** data Params
+
 -- | @Params@ represents all the ways the formal parameters to a
 -- function can be represented.
 data Params r
@@ -127,20 +163,18 @@
   -- ^ For functions with a single named argument, such as @x: x + 1@.
   --
   -- > Param "x"                                  ~  x
-  | ParamSet !(ParamSet r) !Bool !(Maybe VarName)
-  --  2021-05-15: NOTE: Seems like we should flip the ParamSet, so partial application kicks in for Bool?
-  --  2021-05-15: NOTE: '...' variadic property probably needs a Bool synonym.
+  | ParamSet !(Maybe VarName) !Variadic !(ParamSet r)
   -- ^ Explicit parameters (argument must be a set). Might specify a name to
   -- bind to the set in the function body. The bool indicates whether it is
   -- variadic or not.
   --
-  -- > ParamSet [("x",Nothing)] False Nothing     ~  { x }
-  -- > ParamSet [("x",pure y)]  True  (pure "s")  ~  s@{ x ? y, ... }
+  -- > ParamSet  Nothing   False [("x",Nothing)]  ~  { x }
+  -- > ParamSet (pure "s") True  [("x", pure y)]  ~  s@{ x ? y, ... }
   deriving
     ( Eq, Ord, Generic, Generic1
     , Typeable, Data, NFData, NFData1, Serialise, Binary, ToJSON, ToJSON1, FromJSON, FromJSON1
     , Functor, Foldable, Traversable
-    , Show, Hashable
+    , Show, Hashable, Hashable1
     )
 
 instance IsString (Params r) where
@@ -151,14 +185,12 @@
 $(deriveEq1   ''Params)
 $(deriveOrd1  ''Params)
 
-deriving instance Hashable1 Params
-
--- *** Lens traversals
+-- *** lens traversals
 
 $(makeTraversals ''Params)
 
 
--- ** @Antiquoted@
+-- ** data Antiquoted
 
 -- | 'Antiquoted' represents an expression that is either
 -- antiquoted (surrounded by ${...}) or plain (not antiquoted).
@@ -178,7 +210,7 @@
     , Typeable, Data, NFData, NFData1, Serialise, Binary
     , ToJSON, ToJSON1, FromJSON, FromJSON1
     , Functor, Foldable, Traversable
-    , Show, Read, Hashable
+    , Show, Read, Hashable, Hashable1
     )
 
 instance Hashable2 Antiquoted where
@@ -196,26 +228,25 @@
 $(deriveOrd2  ''Antiquoted)
 $(deriveJSON2 defaultOptions ''Antiquoted)
 
-deriving instance Hashable v => Hashable1 (Antiquoted v)
 
--- *** Lens traversals
+-- *** lens traversals
 
 $(makeTraversals ''Antiquoted)
 
 
--- ** @NString@
+-- ** data NString
 
 -- | An 'NString' is a list of things that are either a plain string
 -- or an antiquoted expression. After the antiquotes have been evaluated,
 -- the final string is constructed by concatenating all the parts.
 data NString r
   = DoubleQuoted ![Antiquoted Text r]
-  -- ^ Strings wrapped with double-quotes (") can contain literal newline
+  -- ^ Strings wrapped with double-quotes (__@"@__) can contain literal newline
   -- characters, but the newlines are preserved and no indentation is stripped.
   --
   -- > DoubleQuoted [Plain "x",Antiquoted y]   ~  "x${y}"
   | Indented !Int ![Antiquoted Text r]
-  -- ^ Strings wrapped with two single quotes ('') can contain newlines, and
+  -- ^ Strings wrapped with two single quotes (__@''@__) can contain newlines, and
   --   their indentation will be stripped, but the amount stripped is
   --   remembered.
   --
@@ -230,27 +261,25 @@
     ( Eq, Ord, Generic, Generic1
     , Typeable, Data, NFData, NFData1, Serialise, Binary, ToJSON, ToJSON1, FromJSON, FromJSON1
     , Functor, Foldable, Traversable
-    , Show, Read, Hashable
+    , Show, Read, Hashable, Hashable1
     )
 
 -- | For the the 'IsString' instance, we use a plain doublequoted string.
 instance IsString (NString r) where
   fromString ""     = DoubleQuoted mempty
-  fromString string = DoubleQuoted [Plain $ toText string]
+  fromString string = DoubleQuoted [Plain $ fromString string]
 
 $(deriveShow1 ''NString)
 $(deriveRead1 ''NString)
 $(deriveEq1   ''NString)
 $(deriveOrd1  ''NString)
 
-deriving instance Hashable1 NString
-
--- *** Lens traversals
+-- *** lens traversals
 
 $(makeTraversals ''NString)
 
 
--- ** @NKeyName@
+-- ** data NKeyName
 
 -- | A 'KeyName' is something that can appear on the left side of an
 -- equals sign. For example, @a@ is a 'KeyName' in @{ a = 3; }@, @let a = 3;
@@ -318,11 +347,12 @@
 -- occurs not only as last argument in @Antiquoted (NString r) r@
 instance Show1 NKeyName where
   liftShowsPrec sp sl p = \case
-    DynamicKey a -> showsUnaryWith
-      (liftShowsPrec2 (liftShowsPrec sp sl) (liftShowList sp sl) sp sl)
-      "DynamicKey"
-      p
-      a
+    DynamicKey a ->
+      showsUnaryWith
+        (liftShowsPrec2 (liftShowsPrec sp sl) (liftShowList sp sl) sp sl)
+        "DynamicKey"
+        p
+        a
     StaticKey t -> showsUnaryWith Text.Show.showsPrec "StaticKey" p t
 
 -- Deriving this instance automatically is not possible because @r@
@@ -344,12 +374,12 @@
     DynamicKey EscapedNewline   -> pure $ DynamicKey EscapedNewline
     StaticKey  key              -> pure $ StaticKey key
 
--- *** Lens traversals
+-- *** lens traversals
 
 $(makeTraversals ''NKeyName)
 
 
--- ** @NAttrPath@
+-- ** type NAttrPath
 
 -- | A selector (for example in a @let@ or an attribute set) is made up
 -- of strung-together key names.
@@ -358,7 +388,7 @@
 type NAttrPath r = NonEmpty (NKeyName r)
 
 
--- ** @Binding@
+-- ** data Binding
 
 #if !MIN_VERSION_hashable(1,3,1)
 -- Required by Hashable Binding deriving. There was none of this Hashable instance before mentioned version, remove this in year >2022
@@ -371,7 +401,7 @@
   -- ^ An explicit naming.
   --
   -- > NamedVar (StaticKey "x" :| [StaticKey "y"]) z SourcePos{}  ~  x.y = z;
-  | Inherit !(Maybe r) ![NKeyName r] !SourcePos
+  | Inherit !(Maybe r) ![VarName] !SourcePos
   -- ^ Inheriting an attribute (binding) into the attribute set from the other scope (attribute set). No denoted scope means to inherit from the closest outside scope.
   --
   -- +---------------------------------------------------------------+--------------------+-----------------------+
@@ -391,7 +421,7 @@
     ( Eq, Ord, Generic, Generic1
     , Typeable, Data, NFData, NFData1, Serialise, Binary, ToJSON, FromJSON
     , Functor, Foldable, Traversable
-    , Show, Hashable
+    , Show, Hashable, Hashable1
     )
 
 $(deriveShow1 ''Binding)
@@ -399,29 +429,33 @@
 $(deriveOrd1  ''Binding)
 --x $(deriveJSON1 defaultOptions ''Binding)
 
-deriving instance Hashable1 Binding
-
--- *** Lens traversals
+-- *** lens traversals
 
 $(makeTraversals ''Binding)
 
 
--- ** @NRecordType@
+-- ** data Recursivity
 
--- | 'NRecordType' distinguishes between recursive and non-recursive attribute
+-- | Distinguishes between recursive and non-recursive. Mainly for attribute
 -- sets.
-data NRecordType
-  = NNonRecursive  -- ^ >     { ... }
-  | NRecursive     -- ^ > rec { ... }
+data Recursivity
+  = NonRecursive  -- ^ >     { ... }
+  | Recursive     -- ^ > rec { ... }
   deriving
     ( Eq, Ord, Enum, Bounded, Generic
     , Typeable, Data, NFData, Serialise, Binary, ToJSON, FromJSON
     , Show, Read, Hashable
     )
 
+instance Semigroup Recursivity where
+  (<>) NonRecursive NonRecursive = NonRecursive
+  (<>) _ _ = Recursive
 
--- ** @NUnaryOp@
+instance Monoid Recursivity where
+  mempty = NonRecursive
 
+-- ** data NUnaryOp
+
 -- | There are two unary operations: logical not and integer negation.
 data NUnaryOp
   = NNeg  -- ^ @-@
@@ -432,12 +466,12 @@
     , Show, Read, Hashable
     )
 
--- *** Lens traversals
+-- *** lens traversals
 
 $(makeTraversals ''NUnaryOp)
 
 
--- ** @NBinaryOp@
+-- ** data NBinaryOp
 
 -- | Binary operators expressible in the nix language.
 data NBinaryOp
@@ -465,12 +499,12 @@
     , Show, Read, Hashable
     )
 
--- *** Lens traversals
+-- *** lens traversals
 
 $(makeTraversals ''NBinaryOp)
 
 
--- ** @NExprF@ - Nix expressions, base functor
+-- * data NExprF - Nix expressions, base functor
 
 -- | The main Nix expression type. As it is polimophic, has a functor,
 -- which allows to traverse expressions and map functions over them.
@@ -490,19 +524,19 @@
   -- ^ A list literal.
   --
   -- > NList [x,y]                                 ~  [ x y ]
-  | NSet !NRecordType ![Binding r]
+  | NSet !Recursivity ![Binding r]
   -- ^ An attribute set literal
   --
-  -- > NSet NRecursive    [NamedVar x y _]         ~  rec { x = y; }
-  -- > NSet NNonRecursive [Inherit Nothing [x] _]  ~  { inherit x; }
-  | NLiteralPath !FilePath
+  -- > NSet Recursive    [NamedVar x y _]         ~  rec { x = y; }
+  -- > NSet NonRecursive [Inherit Nothing [x] _]  ~  { inherit x; }
+  | NLiteralPath !Path
   -- ^ A path expression, which is evaluated to a store path. The path here
   -- can be relative, in which case it's evaluated relative to the file in
   -- which it appears.
   --
   -- > NLiteralPath "/x"                           ~  /x
   -- > NLiteralPath "x/y"                          ~  x/y
-  | NEnvPath !FilePath
+  | NEnvPath !Path
   -- ^ A path which refers to something in the Nix search path (the NIX_PATH
   -- environment variable. For example, @<nixpkgs/pkgs>@.
   --
@@ -517,14 +551,12 @@
   --
   -- > NBinary NPlus x y                           ~  x + y
   -- > NBinary NApp  f x                           ~  f x
-  | NSelect !r !(NAttrPath r) !(Maybe r)
-  --  2021-05-15: NOTE: Default value should be first argument to leverage partial application.
-  -- Cascading change diff is not that big.
+  | NSelect !(Maybe r) !r !(NAttrPath r)
   -- ^ Dot-reference into an attribute set, optionally providing an
   -- alternative if the key doesn't exist.
   --
-  -- > NSelect s (x :| []) Nothing                 ~  s.x
-  -- > NSelect s (x :| []) (pure y)                ~  s.x or y
+  -- > NSelect Nothing  s (x :| [])                ~  s.x
+  -- > NSelect (pure y) s (x :| [])                ~  s.x or y
   | NHasAttr !r !(NAttrPath r)
   -- ^ Ask if a set contains a given attribute path.
   --
@@ -549,7 +581,7 @@
   --
   -- > NWith x y                                   ~  with x; y
   | NAssert !r !r
-  -- ^ Assert that the first returns @true@ before evaluating the second.
+  -- ^ Checks that the first argument is a predicate that is @true@ before evaluating the second argument.
   --
   -- > NAssert x y                                 ~  assert x; y
   | NSynHole !VarName
@@ -562,7 +594,7 @@
     ( Eq, Ord, Generic, Generic1
     , Typeable, Data, NFData, NFData1, Serialise, Binary, ToJSON, FromJSON
     , Functor, Foldable, Traversable
-    , Show, Hashable
+    , Show, Hashable, Hashable1
     )
 
 
@@ -571,14 +603,12 @@
 $(deriveOrd1  ''NExprF)
 --x $(deriveJSON1 defaultOptions ''NExprF)
 
-deriving instance Hashable1 NExprF
-
--- *** Lens traversals
+-- ** lens traversals
 
 $(makeTraversals ''NExprF)
 
 
--- *** @NExpr@
+-- ** type NExpr
 
 -- | The monomorphic expression type is a fixed point of the polymorphic one.
 type NExpr = Fix NExprF
@@ -600,13 +630,13 @@
           -- Reflection is a key strategy in metaprogramming.
           -- <https://en.wikipedia.org/wiki/Reflective_programming>
           HRefl <-
-            eqTypeRep
+            Reflection.eqTypeRep
               (Reflection.typeRep @Text)
               (Reflection.typeOf  b    )
           pure [| $(TH.lift b) |]
       )
 #if MIN_VERSION_template_haskell(2,17,0)
-  liftTyped = TH.liftTyped
+  liftTyped = TH.unsafeCodeCoerce . TH.lift
 #elif MIN_VERSION_template_haskell(2,16,0)
   liftTyped = TH.unsafeTExpCoerce . TH.lift
 #endif
@@ -624,31 +654,47 @@
 #else
 hashAt :: VarName -> Lens' (AttrSet v) (Maybe v)
 #endif
-hashAt = flip alterF
+hashAt = alterF
+ where
+  alterF
+    :: (Functor f)
+    => VarName
+    -> (Maybe v -> f (Maybe v))
+    -> AttrSet v
+    -> f (AttrSet v)
+  alterF (coerce -> k) f m =
+    maybe
+      (MapL.delete k m)
+      (\ v -> MapL.insert k v m)
+      <$> f (MapL.lookup k m)
 
 -- | Get the name out of the parameter (there might be none).
 paramName :: Params r -> Maybe VarName
-paramName (Param n       ) = pure n
-paramName (ParamSet _ _ n) = n
+paramName (Param name        ) = pure name
+paramName (ParamSet mname _ _) = mname
 
+stringParts :: NString r -> [Antiquoted Text r]
+stringParts (DoubleQuoted parts) = parts
+stringParts (Indented _   parts) = parts
+
 stripPositionInfo :: NExpr -> NExpr
 stripPositionInfo = transport phi
  where
   transport f (Fix x) = Fix $ transport f <$> f x
 
-  phi (NSet recur binds) = NSet recur $ go <$> binds
-  phi (NLet binds body) = NLet (go <$> binds) body
+  phi (NSet recur binds) = NSet recur $ erasePositions <$> binds
+  phi (NLet binds body) = NLet (erasePositions <$> binds) body
   phi x                 = x
 
-  go (NamedVar path r     _pos) = NamedVar path r     nullPos
-  go (Inherit  ms   names _pos) = Inherit  ms   names nullPos
+  erasePositions (NamedVar path r     _pos) = NamedVar path r     nullPos
+  erasePositions (Inherit  ms   names _pos) = Inherit  ms   names nullPos
 
 nullPos :: SourcePos
 nullPos = on (SourcePos "<string>") mkPos 1 1
 
 -- * Dead code
 
--- ** @class NExprAnn@
+-- ** class NExprAnn
 
 class NExprAnn ann g | g -> ann where
   fromNExpr :: g r -> (NExprF r, ann)
@@ -657,37 +703,39 @@
 -- ** Other
 
 ekey
-  :: NExprAnn ann g
-  => NonEmpty Text
+  :: forall ann g
+  . NExprAnn ann g
+  => NonEmpty VarName
   -> SourcePos
   -> Lens' (Fix g) (Maybe (Fix g))
-ekey keys pos f e@(Fix x) | (NSet NNonRecursive xs, ann) <- fromNExpr x =
-  case go xs of
-    ((v, []      ) : _) -> fromMaybe e <$> f (pure v)
-    ((v, r : rest) : _) -> ekey (r :| rest) pos f v
+ekey keys pos f e@(Fix x)
+  | (NSet NonRecursive xs, ann) <- fromNExpr x =
+    let
+      vals :: [(Fix g, [VarName])]
+      vals =
+        do
+          let keys' = NE.toList keys
+          (ks, rest) <- zip (inits keys') (tails keys')
+          list
+            mempty
+            (\ (j : js) ->
+              do
+                NamedVar ns v _p <- xs
+                guard $ (j : js) == (NE.toList ns ^.. traverse . _StaticKey)
+                pure (v, rest)
+            )
+            ks
+    in
+    case vals of
+      ((v, []      ) : _) -> fromMaybe e <$> f (pure v)
+      ((v, r : rest) : _) -> ekey (r :| rest) pos f v
 
-    _                   ->
-      maybe
-        e
-        (\ v ->
-          let entry = NamedVar (StaticKey <$> keys) v pos in
-          Fix $ toNExpr ( NSet NNonRecursive $ [entry] <> xs, ann )
-        )
-      <$>
-        f Nothing
-  where
-    go xs =
-      do
-        let keys' = NE.toList keys
-        (ks, rest) <- zip (inits keys') (tails keys')
-        list
-          mempty
-          (\ (j : js) ->
-            do
-              NamedVar ns v _p <- xs
-              guard $ (j : js) == (NE.toList ns ^.. traverse . _StaticKey)
-              pure (v, rest)
+      _                   ->
+        maybe
+          e
+          (\ v ->
+            let entry = NamedVar (StaticKey <$> keys) v pos in
+            Fix $ toNExpr ( NSet mempty $ one entry <> xs, ann )
           )
-          ks
-
+        <$> f Nothing
 ekey _ _ f e = fromMaybe e <$> f Nothing
diff --git a/src/Nix/Expr/Types/Annotated.hs b/src/Nix/Expr/Types/Annotated.hs
--- a/src/Nix/Expr/Types/Annotated.hs
+++ b/src/Nix/Expr/Types/Annotated.hs
@@ -1,9 +1,9 @@
-{-# LANGUAGE CPP                #-}
-{-# LANGUAGE DeriveAnyClass     #-}
-{-# LANGUAGE KindSignatures     #-}
-{-# LANGUAGE PatternSynonyms    #-}
-{-# LANGUAGE RankNTypes         #-}
-{-# LANGUAGE TemplateHaskell    #-}
+{-# language CPP                #-}
+{-# language DeriveAnyClass     #-}
+{-# language KindSignatures     #-}
+{-# language PatternSynonyms    #-}
+{-# language RankNTypes         #-}
+{-# language TemplateHaskell    #-}
 
 -- | The source location annotated nix expression type and supporting types.
 --
@@ -26,7 +26,8 @@
 import           Data.Data
 import           Data.Eq.Deriving
 import           Data.Fix                       ( Fix(..)
-                                                , unfoldFix )
+                                                , unfoldFix
+                                                )
 import           Data.Functor.Compose
 import           Data.Hashable.Lifted
 import           Data.Ord.Deriving
@@ -44,18 +45,18 @@
 
 -- | Demarcation of a chunk in a source file.
 data SrcSpan = SrcSpan
-    { spanBegin :: SourcePos
-    , spanEnd   :: SourcePos
-    }
-    deriving (Ord, Eq, Generic, Typeable, Data, Show, NFData, Hashable)
+  { spanBegin :: SourcePos
+  , spanEnd   :: SourcePos
+  }
+ deriving (Ord, Eq, Generic, Typeable, Data, Show, NFData, Hashable)
 
 -- ** Instances
 
 instance Semigroup SrcSpan where
   s1 <> s2 =
     SrcSpan
-      ((min `on` spanBegin) s1 s2)
-      ((max `on` spanEnd  ) s1 s2)
+      (on min spanBegin s1 s2)
+      (on max spanEnd   s1 s2)
 
 instance Binary SrcSpan
 instance ToJSON SrcSpan
@@ -65,70 +66,78 @@
 
 -- * data type @Ann@
 
+--  2021-08-02: NOTE: Annotation needs to be after what is annotated.
 -- | A type constructor applied to a type along with an annotation
 --
 -- Intended to be used with 'Fix':
--- @type MyType = Fix (Compose (Ann Annotation) F)@
-data Ann ann a = Ann
-    { annotation :: ann
-    , annotated  :: a
-    }
-    deriving (Ord, Eq, Data, Generic, Generic1, Typeable, Functor, Foldable,
-              Traversable, Read, Show, NFData, Hashable)
+-- @type MyType = Fix (Compose (AnnUnit Annotation) F)@
+data AnnUnit ann expr = AnnUnit
+  { annotation :: ann
+  , annotated  :: expr
+  }
+ deriving
+  ( Eq, Ord, Data, Typeable, Hashable
+  , Generic, Generic1, NFData
+  , Functor, Foldable, Traversable
+  , Show, Read
+  )
 
-type AnnF ann f = Compose (Ann ann) f
+type AnnF ann f = Compose (AnnUnit ann) f
 
--- | Pattern: @(Compose (Ann _ _))@.
-pattern AnnFP
+-- | Pattern: @(Compose (AnnUnit _ _))@.
+pattern AnnF
   :: ann
   -> f a
-  -> Compose (Ann ann) f a
-pattern AnnFP ann f = Compose (Ann ann f)
-{-# complete AnnFP #-}
+  -> Compose (AnnUnit ann) f a
+pattern AnnF ann f = Compose (AnnUnit ann f)
+{-# complete AnnF #-}
 
--- | Pattern: @Fix (Compose (Ann _ _))@.
+
+type Ann ann f = Fix (AnnF ann f)
+
+-- | Pattern: @Fix (Compose (AnnUnit _ _))@.
 -- Fix composes units of (annotations & the annotated) into one object.
 -- Giving annotated expression.
-pattern AnnE
+pattern Ann
   :: forall ann (f :: Type -> Type)
   . ann
-  -> f (Fix (AnnF ann f))
-  -> Fix (AnnF ann f)
-pattern AnnE ann a = Fix (AnnFP ann a)
-{-# complete AnnE #-}
+  -> f (Ann ann f)
+  -> Ann ann f
+pattern Ann ann a = Fix (AnnF ann a)
+{-# complete Ann #-}
 
-annToAnnF :: Ann ann (f (Fix (AnnF ann f))) -> Fix (AnnF ann f)
-annToAnnF (Ann ann a) = AnnE ann a
+annUnitToAnn :: AnnUnit ann (f (Ann ann f)) -> Ann ann f
+annUnitToAnn (AnnUnit ann a) = Ann ann a
 
 -- ** Instances
 
-instance Hashable ann => Hashable1 (Ann ann)
+instance Hashable ann => Hashable1 (AnnUnit ann)
 
-instance NFData ann => NFData1 (Ann ann)
+instance NFData ann => NFData1 (AnnUnit ann)
 
-instance (Binary ann, Binary a) => Binary (Ann ann a)
+instance (Binary ann, Binary a) => Binary (AnnUnit ann a)
 
-$(deriveEq1   ''Ann)
-$(deriveEq2   ''Ann)
-$(deriveOrd1  ''Ann)
-$(deriveOrd2  ''Ann)
-$(deriveRead1 ''Ann)
-$(deriveRead2 ''Ann)
-$(deriveShow1 ''Ann)
-$(deriveShow2 ''Ann)
-$(deriveJSON1 defaultOptions ''Ann)
-$(deriveJSON2 defaultOptions ''Ann)
+$(deriveEq1   ''AnnUnit)
+$(deriveEq2   ''AnnUnit)
+$(deriveOrd1  ''AnnUnit)
+$(deriveOrd2  ''AnnUnit)
+$(deriveRead1 ''AnnUnit)
+$(deriveRead2 ''AnnUnit)
+$(deriveShow1 ''AnnUnit)
+$(deriveShow2 ''AnnUnit)
+$(deriveJSON1 defaultOptions ''AnnUnit)
+$(deriveJSON2 defaultOptions ''AnnUnit)
 
-instance (Serialise ann, Serialise a) => Serialise (Ann ann a)
+instance (Serialise ann, Serialise a) => Serialise (AnnUnit ann a)
 
 -- ** @NExprLoc{,F}@ - annotated Nix expression
 
 type NExprLocF = AnnF SrcSpan NExprF
 
 instance Serialise r => Serialise (NExprLocF r) where
-  encode (AnnFP ann a) = encode ann <> encode a
+  encode (AnnF ann a) = encode ann <> encode a
   decode =
-    liftA2 AnnFP
+    liftA2 AnnF
       decode
       decode
 
@@ -143,105 +152,150 @@
 
 -- * Other
 
-stripAnnotation :: Functor f => Fix (AnnF ann f) -> Fix f
-stripAnnotation = unfoldFix (stripAnn . unFix)
+stripAnnF :: AnnF ann f r -> f r
+stripAnnF = annotated . getCompose
 
-stripAnn :: AnnF ann f r -> f r
-stripAnn = annotated . getCompose
+stripAnnotation :: Functor f => Ann ann f -> Fix f
+stripAnnotation = unfoldFix (stripAnnF . unFix)
 
-nUnary :: Ann SrcSpan NUnaryOp -> NExprLoc -> NExprLoc
-nUnary (Ann s1 u) e1@(AnnE s2 _) = AnnE (s1 <> s2) $ NUnary u e1
-{-# inline nUnary #-}
+annNUnary :: AnnUnit SrcSpan NUnaryOp -> NExprLoc -> NExprLoc
+annNUnary (AnnUnit s1 u) e1@(Ann s2 _) = NUnaryAnn (s1 <> s2) u e1
+{-# inline annNUnary #-}
 
-nBinary :: Ann SrcSpan NBinaryOp -> NExprLoc -> NExprLoc -> NExprLoc
-nBinary (Ann s1 b) e1@(AnnE s2 _) e2@(AnnE s3 _) =
-  AnnE (s1 <> s2 <> s3) $ NBinary b e1 e2
+annNBinary :: AnnUnit SrcSpan NBinaryOp -> NExprLoc -> NExprLoc -> NExprLoc
+annNBinary (AnnUnit s1 b) e1@(Ann s2 _) e2@(Ann s3 _) = NBinaryAnn (s1 <> s2 <> s3) b e1 e2
 
-nSelectLoc
-  :: NExprLoc -> Ann SrcSpan (NAttrPath NExprLoc) -> Maybe NExprLoc -> NExprLoc
-nSelectLoc e1@(AnnE s1 _) (Ann s2 ats) =
-  --  2021-05-16: NOTE: This could been rewritten into function application of @(s3, pure e2)@
-  -- if @SrcSpan@ was Monoid, which requires @SorcePos@ to be a Monoid, and upstream code prevents it.
-  -- Question upstream: https://github.com/mrkkrp/megaparsec/issues/450
-  maybe
-    (                    AnnE  s1s2        $ NSelect e1 ats   Nothing)
-    (\ e2@(AnnE s3 _) -> AnnE (s1s2 <> s3) $ NSelect e1 ats $ pure e2)
- where
-  s1s2 = s1 <> s2
+annNSelect
+  :: Maybe NExprLoc -> NExprLoc -> AnnUnit SrcSpan (NAttrPath NExprLoc) -> NExprLoc
+annNSelect  Nothing             e2@(Ann s2 _) (AnnUnit s1 ats) = NSelectAnn (      s2 <> s1)  Nothing  e2 ats
+annNSelect (Just e3@(Ann s3 _)) e2@(Ann s2 _) (AnnUnit s1 ats) = NSelectAnn (s3 <> s2 <> s1) (pure e3) e2 ats
 
-nHasAttr :: NExprLoc -> Ann SrcSpan (NAttrPath NExprLoc) -> NExprLoc
-nHasAttr e1@(AnnE s1 _) (Ann s2 ats) = AnnE (s1 <> s2) $ NHasAttr e1 ats
+annNHasAttr :: NExprLoc -> AnnUnit SrcSpan (NAttrPath NExprLoc) -> NExprLoc
+annNHasAttr e1@(Ann s1 _) (AnnUnit s2 ats) = NHasAttrAnn (s1 <> s2) e1 ats
 
-nApp :: NExprLoc -> NExprLoc -> NExprLoc
-nApp e1@(AnnE s1 _) e2@(AnnE s2 _) = AnnE (s1 <> s2) $ NBinary NApp e1 e2
+annNApp :: NExprLoc -> NExprLoc -> NExprLoc
+annNApp e1@(Ann s1 _) e2@(Ann s2 _) = NBinaryAnn (s1 <> s2) NApp e1 e2
 
-nAbs :: Ann SrcSpan (Params NExprLoc) -> NExprLoc -> NExprLoc
-nAbs (Ann s1 ps) e1@(AnnE s2 _) = AnnE (s1 <> s2) $ NAbs ps e1
+annNAbs :: AnnUnit SrcSpan (Params NExprLoc) -> NExprLoc -> NExprLoc
+annNAbs (AnnUnit s1 ps) e1@(Ann s2 _) = NAbsAnn (s1 <> s2) ps e1
 
-nStr :: Ann SrcSpan (NString NExprLoc) -> NExprLoc
-nStr (Ann s1 s) = AnnE s1 $ NStr s
+annNStr :: AnnUnit SrcSpan (NString NExprLoc) -> NExprLoc
+annNStr (AnnUnit s1 s) = NStrAnn s1 s
 
 deltaInfo :: SourcePos -> (Text, Int, Int)
-deltaInfo (SourcePos fp l c) = (toText fp, unPos l, unPos c)
+deltaInfo (SourcePos fp l c) = (fromString fp, unPos l, unPos c)
 
-nNull :: NExprLoc
-nNull = AnnE nullSpan $ NConstant NNull
-{-# inline nNull #-}
+annNNull :: NExprLoc
+annNNull = NConstantAnn nullSpan NNull
+{-# inline annNNull #-}
 
 nullSpan :: SrcSpan
 nullSpan = SrcSpan nullPos nullPos
 {-# inline nullSpan #-}
 
--- | Pattern systems for matching on NExprLocF constructions.
+-- | Pattern systems for matching on @NExprLocF@ constructions.
 
-pattern NConstant_ :: SrcSpan -> NAtom -> NExprLocF r
-pattern NConstant_ ann x = AnnFP ann (NConstant x)
+pattern NConstantAnnF    :: SrcSpan -> NAtom -> NExprLocF r
+pattern NConstantAnnF    ann x      = AnnF ann (NConstant x)
 
-pattern NStr_ :: SrcSpan -> NString r -> NExprLocF r
-pattern NStr_ ann x = AnnFP ann (NStr x)
+pattern NStrAnnF         :: SrcSpan -> NString r -> NExprLocF r
+pattern NStrAnnF         ann x      = AnnF ann (NStr x)
 
-pattern NSym_ :: SrcSpan -> VarName -> NExprLocF r
-pattern NSym_ ann x = AnnFP ann (NSym x)
+pattern NSymAnnF         :: SrcSpan -> VarName -> NExprLocF r
+pattern NSymAnnF         ann x      = AnnF ann (NSym x)
 
-pattern NList_ :: SrcSpan -> [r] -> NExprLocF r
-pattern NList_ ann x = AnnFP ann (NList x)
+pattern NListAnnF        :: SrcSpan -> [r] -> NExprLocF r
+pattern NListAnnF        ann x      = AnnF ann (NList x)
 
-pattern NSet_ :: SrcSpan -> NRecordType -> [Binding r] -> NExprLocF r
-pattern NSet_ ann recur x = AnnFP ann (NSet recur x)
+pattern NSetAnnF         :: SrcSpan -> Recursivity -> [Binding r] -> NExprLocF r
+pattern NSetAnnF         ann rec x  = AnnF ann (NSet rec x)
 
-pattern NLiteralPath_ :: SrcSpan -> FilePath -> NExprLocF r
-pattern NLiteralPath_ ann x = AnnFP ann (NLiteralPath x)
+pattern NLiteralPathAnnF :: SrcSpan -> Path -> NExprLocF r
+pattern NLiteralPathAnnF ann x      = AnnF ann (NLiteralPath x)
 
-pattern NEnvPath_ :: SrcSpan -> FilePath -> NExprLocF r
-pattern NEnvPath_ ann x = AnnFP ann (NEnvPath x)
+pattern NEnvPathAnnF     :: SrcSpan -> Path -> NExprLocF r
+pattern NEnvPathAnnF     ann x      = AnnF ann (NEnvPath x)
 
-pattern NUnary_ :: SrcSpan -> NUnaryOp -> r -> NExprLocF r
-pattern NUnary_ ann op x = AnnFP ann (NUnary op x)
+pattern NUnaryAnnF       :: SrcSpan -> NUnaryOp -> r -> NExprLocF r
+pattern NUnaryAnnF       ann op x   = AnnF ann (NUnary op x)
 
-pattern NBinary_ :: SrcSpan -> NBinaryOp -> r -> r -> NExprLocF r
-pattern NBinary_ ann op x y = AnnFP ann (NBinary op x y)
+pattern NBinaryAnnF      :: SrcSpan -> NBinaryOp -> r -> r -> NExprLocF r
+pattern NBinaryAnnF      ann op x y = AnnF ann (NBinary op x y)
 
-pattern NSelect_ :: SrcSpan -> r -> NAttrPath r -> Maybe r -> NExprLocF r
-pattern NSelect_ ann x p v = AnnFP ann (NSelect x p v)
+pattern NSelectAnnF      :: SrcSpan ->  Maybe r -> r -> NAttrPath r -> NExprLocF r
+pattern NSelectAnnF      ann v x p  = AnnF ann (NSelect v x p)
 
-pattern NHasAttr_ :: SrcSpan -> r -> NAttrPath r -> NExprLocF r
-pattern NHasAttr_ ann x p = AnnFP ann (NHasAttr x p)
+pattern NHasAttrAnnF     :: SrcSpan -> r -> NAttrPath r -> NExprLocF r
+pattern NHasAttrAnnF     ann x p    = AnnF ann (NHasAttr x p)
 
-pattern NAbs_ :: SrcSpan -> Params r-> r -> NExprLocF r
-pattern NAbs_ ann x b = AnnFP ann (NAbs x b)
+pattern NAbsAnnF         :: SrcSpan -> Params r-> r -> NExprLocF r
+pattern NAbsAnnF         ann x b    = AnnF ann (NAbs x b)
 
-pattern NLet_ :: SrcSpan -> [Binding r] -> r -> NExprLocF r
-pattern NLet_ ann x b = AnnFP ann (NLet x b)
+pattern NLetAnnF         :: SrcSpan -> [Binding r] -> r -> NExprLocF r
+pattern NLetAnnF         ann x b    = AnnF ann (NLet x b)
 
-pattern NIf_ :: SrcSpan -> r -> r -> r -> NExprLocF r
-pattern NIf_ ann c t e = AnnFP ann (NIf c t e)
+pattern NIfAnnF          :: SrcSpan -> r -> r -> r -> NExprLocF r
+pattern NIfAnnF          ann c t e  = AnnF ann (NIf c t e)
 
-pattern NWith_ :: SrcSpan -> r -> r -> NExprLocF r
-pattern NWith_ ann x y = AnnFP ann (NWith x y)
+pattern NWithAnnF        :: SrcSpan -> r -> r -> NExprLocF r
+pattern NWithAnnF        ann x y    = AnnF ann (NWith x y)
 
-pattern NAssert_ :: SrcSpan -> r -> r -> NExprLocF r
-pattern NAssert_ ann x y = AnnFP ann (NAssert x y)
+pattern NAssertAnnF      :: SrcSpan -> r -> r -> NExprLocF r
+pattern NAssertAnnF      ann x y    = AnnF ann (NAssert x y)
 
-pattern NSynHole_ :: SrcSpan -> Text -> NExprLocF r
-pattern NSynHole_ ann x = AnnFP ann (NSynHole x)
-{-# complete NConstant_, NStr_, NSym_, NList_, NSet_, NLiteralPath_, NEnvPath_, NUnary_, NBinary_, NSelect_, NHasAttr_, NAbs_, NLet_, NIf_, NWith_, NAssert_, NSynHole_ #-}
+pattern NSynHoleAnnF     :: SrcSpan -> VarName -> NExprLocF r
+pattern NSynHoleAnnF     ann x      = AnnF ann (NSynHole x)
+{-# complete NConstantAnnF, NStrAnnF, NSymAnnF, NListAnnF, NSetAnnF, NLiteralPathAnnF, NEnvPathAnnF, NUnaryAnnF, NBinaryAnnF, NSelectAnnF, NHasAttrAnnF, NAbsAnnF, NLetAnnF, NIfAnnF, NWithAnnF, NAssertAnnF, NSynHoleAnnF #-}
+
+
+pattern NConstantAnn    :: SrcSpan -> NAtom -> NExprLoc
+pattern NConstantAnn    ann x      = Ann ann (NConstant x)
+
+pattern NStrAnn         :: SrcSpan -> NString NExprLoc -> NExprLoc
+pattern NStrAnn         ann x      = Ann ann (NStr x)
+
+pattern NSymAnn         :: SrcSpan -> VarName -> NExprLoc
+pattern NSymAnn         ann x      = Ann ann (NSym x)
+
+pattern NListAnn        :: SrcSpan -> [NExprLoc] -> NExprLoc
+pattern NListAnn        ann x      = Ann ann (NList x)
+
+pattern NSetAnn         :: SrcSpan -> Recursivity -> [Binding NExprLoc] -> NExprLoc
+pattern NSetAnn         ann rec x  = Ann ann (NSet rec x)
+
+pattern NLiteralPathAnn :: SrcSpan -> Path -> NExprLoc
+pattern NLiteralPathAnn ann x      = Ann ann (NLiteralPath x)
+
+pattern NEnvPathAnn     :: SrcSpan -> Path -> NExprLoc
+pattern NEnvPathAnn     ann x      = Ann ann (NEnvPath x)
+
+pattern NUnaryAnn       :: SrcSpan -> NUnaryOp -> NExprLoc -> NExprLoc
+pattern NUnaryAnn       ann op x   = Ann ann (NUnary op x)
+
+pattern NBinaryAnn      :: SrcSpan -> NBinaryOp -> NExprLoc -> NExprLoc -> NExprLoc
+pattern NBinaryAnn      ann op x y = Ann ann (NBinary op x y)
+
+pattern NSelectAnn      :: SrcSpan ->  Maybe NExprLoc -> NExprLoc -> NAttrPath NExprLoc -> NExprLoc
+pattern NSelectAnn      ann v x p  = Ann ann (NSelect v x p)
+
+pattern NHasAttrAnn     :: SrcSpan -> NExprLoc -> NAttrPath NExprLoc -> NExprLoc
+pattern NHasAttrAnn     ann x p    = Ann ann (NHasAttr x p)
+
+pattern NAbsAnn         :: SrcSpan -> Params NExprLoc -> NExprLoc -> NExprLoc
+pattern NAbsAnn         ann x b    = Ann ann (NAbs x b)
+
+pattern NLetAnn         :: SrcSpan -> [Binding NExprLoc] -> NExprLoc -> NExprLoc
+pattern NLetAnn         ann x b    = Ann ann (NLet x b)
+
+pattern NIfAnn          :: SrcSpan -> NExprLoc -> NExprLoc -> NExprLoc -> NExprLoc
+pattern NIfAnn          ann c t e  = Ann ann (NIf c t e)
+
+pattern NWithAnn        :: SrcSpan -> NExprLoc -> NExprLoc -> NExprLoc
+pattern NWithAnn        ann x y    = Ann ann (NWith x y)
+
+pattern NAssertAnn      :: SrcSpan -> NExprLoc -> NExprLoc -> NExprLoc
+pattern NAssertAnn      ann x y    = Ann ann (NAssert x y)
+
+pattern NSynHoleAnn     :: SrcSpan -> VarName -> NExprLoc
+pattern NSynHoleAnn     ann x      = Ann ann (NSynHole x)
+{-# complete NConstantAnn, NStrAnn, NSymAnn, NListAnn, NSetAnn, NLiteralPathAnn, NEnvPathAnn, NUnaryAnn, NBinaryAnn, NSelectAnn, NHasAttrAnn, NAbsAnn, NLetAnn, NIfAnn, NWithAnn, NAssertAnn, NSynHoleAnn #-}
diff --git a/src/Nix/Frames.hs b/src/Nix/Frames.hs
--- a/src/Nix/Frames.hs
+++ b/src/Nix/Frames.hs
@@ -1,6 +1,5 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE ExistentialQuantification #-}
-{-# LANGUAGE ScopedTypeVariables #-}
+{-# language ConstraintKinds #-}
+{-# language ExistentialQuantification #-}
 
 -- | Definitions of Frames. Frames are messages that gather and ship themself with a context related to the message. For example - the message about some exception would also gather, keep and bring with it the tracing information.
 module Nix.Frames
@@ -12,30 +11,20 @@
   , withFrame
   , throwError
   , module Data.Typeable
-  , module Control.Exception
   )
 where
 
-import           Prelude                 hiding ( traceM )
 import           Data.Typeable           hiding ( typeOf )
 import           Control.Monad.Catch            ( MonadThrow(..) )
-import           Control.Exception       hiding ( catch
-                                                , evaluate
-                                                )
 import qualified Text.Show
-import           Nix.Utils                      ( Has(..)
-                                                , view
-                                                , over
-                                                , traceM
-                                                )
 
 data NixLevel = Fatal | Error | Warning | Info | Debug
-    deriving (Ord, Eq, Bounded, Enum, Show)
+  deriving (Ord, Eq, Bounded, Enum, Show)
 
 data NixFrame = NixFrame
-    { frameLevel :: NixLevel
-    , frame      :: SomeException
-    }
+  { frameLevel :: NixLevel
+  , frame      :: SomeException
+  }
 
 instance Show NixFrame where
   show (NixFrame level f) =
@@ -46,7 +35,7 @@
 type Framed e m = (MonadReader e m, Has e Frames, MonadThrow m)
 
 newtype NixException = NixException Frames
-    deriving Show
+  deriving Show
 
 instance Exception NixException
 
diff --git a/src/Nix/Fresh.hs b/src/Nix/Fresh.hs
--- a/src/Nix/Fresh.hs
+++ b/src/Nix/Fresh.hs
@@ -1,9 +1,9 @@
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE UndecidableInstances #-}
+{-# language CPP #-}
+{-# language GeneralizedNewtypeDeriving #-}
+{-# language TypeFamilies #-}
+{-# language UndecidableInstances #-}
 
-{-# OPTIONS_GHC -Wno-orphans #-}
+{-# options_ghc -Wno-orphans #-}
 
 
 module Nix.Fresh where
@@ -21,7 +21,7 @@
 import           Nix.Thunk
 
 --  2021-06-02: NOTE: Remove singleton newtype accessor in favour of free coerce
-newtype FreshIdT i m a = FreshIdT { unFreshIdT :: ReaderT (Ref m i) m a }
+newtype FreshIdT i m a = FreshIdT (ReaderT (Ref m i) m a)
   deriving
     ( Functor
     , Applicative
@@ -60,4 +60,4 @@
     atomicModifyRef v (\i -> (succ i, i))
 
 runFreshIdT :: Functor m => Ref m i -> FreshIdT i m a -> m a
-runFreshIdT i m = runReaderT (unFreshIdT m) i
+runFreshIdT i m = runReaderT (coerce m) i
diff --git a/src/Nix/Fresh/Basic.hs b/src/Nix/Fresh/Basic.hs
--- a/src/Nix/Fresh/Basic.hs
+++ b/src/Nix/Fresh/Basic.hs
@@ -1,7 +1,6 @@
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE ScopedTypeVariables #-}
+{-# language CPP #-}
 
-{-# OPTIONS_GHC -Wno-orphans #-}
+{-# options_ghc -Wno-orphans #-}
 
 
 module Nix.Fresh.Basic where
@@ -29,7 +28,7 @@
 
 instance (MonadEffects t f m, MonadDataContext f m)
   => MonadEffects t f (StdIdT m) where
-  makeAbsolutePath = lift . makeAbsolutePath @t @f @m
+  toAbsolutePath = lift . toAbsolutePath @t @f @m
   findEnvPath      = lift . findEnvPath @t @f @m
   findPath vs path = do
     i <- FreshIdT ask
diff --git a/src/Nix/Json.hs b/src/Nix/Json.hs
--- a/src/Nix/Json.hs
+++ b/src/Nix/Json.hs
@@ -4,30 +4,44 @@
 
 import qualified Data.Aeson                    as A
 import qualified Data.Aeson.Encoding           as A
-import qualified Data.HashMap.Lazy             as HM
-import qualified Data.Text.Lazy.Encoding       as TL
+import qualified Data.Vector                   as V
+import qualified Data.HashMap.Strict           as HM
 #if MIN_VERSION_aeson(2,0,0)
 import qualified Data.Aeson.Key                as AKM
 import qualified Data.Aeson.KeyMap             as AKM
-#else
-import           Nix.Expr.Types
 #endif
-import qualified Data.Vector                   as V
 import           Nix.Atoms
 import           Nix.Effects
 import           Nix.Exec
 import           Nix.Frames
 import           Nix.String
-import           Nix.Utils
 import           Nix.Value
 import           Nix.Value.Monad
+import           Nix.Expr.Types
 
+-- This was moved from Utils.
+toEncodingSorted :: A.Value -> A.Encoding
+toEncodingSorted = \case
+  A.Object m ->
+    A.pairs
+      . fold
+      . ((\(k, v) -> A.pair k $ toEncodingSorted v) <$>)
+      . sortWith fst $
+#if MIN_VERSION_aeson(2,0,0)
+          AKM.toList
+#else
+          HM.toList
+#endif
+            m
+  A.Array l -> A.list toEncodingSorted $ V.toList l
+  v         -> A.toEncoding v
+
 nvalueToJSONNixString :: MonadNix e t f m => NValue t f m -> m NixString
 nvalueToJSONNixString =
   runWithStringContextT .
     fmap
-      ( toStrict
-      . TL.decodeUtf8
+      ( decodeUtf8
+      -- This is completely not optimal, but seems we do not have better encoding analog (except for @unsafe*@), Aeson gatekeeps through this.
       . A.encodingToLazyByteString
       . toEncodingSorted
       )
@@ -42,7 +56,7 @@
   NVConstant NNull      -> pure   A.Null
   NVStr      ns         -> A.toJSON <$> extractNixString ns
   NVList l -> A.Array . V.fromList <$> traverse intoJson l
-  NVSet m _ ->
+  NVSet _ m ->
     maybe
       (A.Object <$> traverse intoJson kmap)
       intoJson
@@ -50,17 +64,18 @@
    where
 #if MIN_VERSION_aeson(2,0,0)
     lkup = AKM.lookup
-    kmap = AKM.fromHashMap (HM.mapKeys (AKM.fromText . coerce) m)
+    kmap = AKM.fromHashMap $ HM.mapKeys (AKM.fromText . coerce) m
 #else
     lkup = HM.lookup
     kmap = HM.mapKeys (coerce @VarName @Text) m
 #endif
   NVPath p ->
     do
-      fp <- lift $ unStorePath <$> addPath p
-      addSingletonStringContext $ StringContext (toText fp) DirectPath
+      fp <- lift $ coerce <$> addPath p
+      addSingletonStringContext $ StringContext (fromString fp) DirectPath
       pure $ A.toJSON fp
   v -> lift $ throwError $ CoercionToJson v
 
  where
+  intoJson :: MonadNix e t f m => NValue t f m -> WithStringContextT m A.Value
   intoJson nv = join $ lift $ nvalueToJSON <$> demand nv
diff --git a/src/Nix/Lint.hs b/src/Nix/Lint.hs
--- a/src/Nix/Lint.hs
+++ b/src/Nix/Lint.hs
@@ -1,21 +1,19 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE DataKinds #-}
-{-# LANGUAGE GADTs #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE UndecidableInstances #-}
+{-# language ConstraintKinds #-}
+{-# language CPP #-}
+{-# language DataKinds #-}
+{-# language GADTs #-}
+{-# language GeneralizedNewtypeDeriving #-}
+{-# language TypeFamilies #-}
+{-# language UndecidableInstances #-}
 
-{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
-{-# OPTIONS_GHC -Wno-missing-methods #-}
+{-# options_ghc -fno-warn-name-shadowing #-}
+{-# options_ghc -Wno-missing-methods #-}
 
 module Nix.Lint where
 
-import           Prelude                 hiding ( head
-                                                , force
-                                                )
-import           Nix.Utils
+import           Relude.Unsafe                 as Unsafe ( head )
+import           Control.Exception              ( throw )
+import           GHC.Exception                  ( ErrorCall(ErrorCall) )
 import           Control.Monad                  ( foldM )
 import           Control.Monad.Catch
 import           Control.Monad.Fix
@@ -23,7 +21,7 @@
 import           Control.Monad.ST
 import qualified Data.HashMap.Lazy             as M
 -- Plese, use NonEmpty
-import           Data.List
+import           Data.List                      ( intersect )
 import qualified Data.List.NonEmpty            as NE
 import qualified Data.Text                     as Text
 import qualified Text.Show
@@ -32,7 +30,8 @@
 import           Nix.Convert
 import           Nix.Eval                       ( MonadEval(..) )
 import qualified Nix.Eval                      as Eval
-import           Nix.Expr
+import           Nix.Expr.Types
+import           Nix.Expr.Types.Annotated
 import           Nix.Frames
 import           Nix.Fresh
 import           Nix.String
@@ -53,7 +52,7 @@
   = TConstant [TAtom]
   | TStr
   | TList r
-  | TSet (Maybe (HashMap Text r))
+  | TSet (Maybe (AttrSet r))
   | TClosure (Params ())
   | TPath
   | TBuiltin Text (Symbolic m -> m r)
@@ -103,7 +102,7 @@
   :: MonadAtomicRef m
   => [NTypeF m (Symbolic m)]
   -> m (Symbolic m)
-mkSymbolic xs = packSymbolic (NMany xs)
+mkSymbolic = packSymbolic . NMany
 
 packSymbolic
   :: MonadAtomicRef m
@@ -129,31 +128,50 @@
 symerr = evalError @(Symbolic m) . ErrorCall . toString
 
 renderSymbolic :: MonadLint e m => Symbolic m -> m Text
-renderSymbolic = unpackSymbolic >=> \case
-  NAny     -> pure "<any>"
-  NMany xs -> fmap (Text.intercalate ", ") $ forM xs $ \case
-    TConstant ys -> fmap (Text.intercalate ", ") $ forM ys $ pure . \case
-      TInt   -> "int"
-      TFloat -> "float"
-      TBool  -> "bool"
-      TNull  -> "null"
-    TStr    -> pure "string"
-    TList r -> do
-      x <- renderSymbolic =<< demand r
-      pure $ "[" <> x <> "]"
-    TSet Nothing  -> pure "<any set>"
-    TSet (Just s) -> do
-      x <- traverse (renderSymbolic <=< demand) s
-      pure $ "{" <> show x <> "}"
-    f@(TClosure p) -> do
-      (args, sym) <- do
-        f' <- mkSymbolic [f]
-        lintApp (NAbs (void p) ()) f' everyPossible
-      args' <- traverse renderSymbolic args
-      sym'  <- renderSymbolic sym
-      pure $ "(" <> show args' <> " -> " <> sym' <> ")"
-    TPath          -> pure "path"
-    TBuiltin _n _f -> pure "<builtin function>"
+renderSymbolic =
+  (\case
+    NAny     -> pure "<any>"
+    NMany xs ->
+      Text.intercalate ", " <$>
+        traverse
+          (\case
+            TConstant ys ->
+              pure $
+                Text.intercalate ", "
+                  (fmap
+                    (\case
+                      TInt   -> "int"
+                      TFloat -> "float"
+                      TBool  -> "bool"
+                      TNull  -> "null"
+                    )
+                    ys
+                  )
+            TStr    -> pure "string"
+            TList r ->
+              fmap brackets $ renderSymbolic =<< demand r
+            TSet Nothing  -> pure "<any set>"
+            TSet (Just s) ->
+              braces . show <$> traverse (renderSymbolic <=< demand) s
+            f@(TClosure p) ->
+              do
+                (args, sym) <-
+                  do
+                    f' <- mkSymbolic $ one f
+                    lintApp (NAbs p mempty) f' everyPossible
+                args' <- traverse renderSymbolic args
+                sym'  <- renderSymbolic sym
+                pure $ parens $ show args' <> " -> " <> sym'
+            TPath          -> pure "path"
+            TBuiltin _n _f -> pure "<builtin function>"
+          )
+          xs
+  ) <=< unpackSymbolic
+ where
+  between a b c = a <> b <> c
+  parens = between "(" ")"
+  brackets = between "[" "]"
+  braces = between "{" "}"
 
 -- This function is order and uniqueness preserving (of types).
 merge
@@ -171,20 +189,19 @@
     -> m [NTypeF m (Symbolic m)]
   go []       _        = stub
   go _        []       = stub
-  go (x : xs) (y : ys) = case (x, y) of
-    (TStr , TStr ) -> (TStr :) <$> go xs ys
-    (TPath, TPath) -> (TPath :) <$> go xs ys
+  go xxs@(x : xs) yys@(y : ys) = case (x, y) of
+    (TStr , TStr ) -> (one TStr <>) <$> rest
+    (TPath, TPath) -> (one TPath <>) <$> rest
     (TConstant ls, TConstant rs) ->
-      (TConstant (ls `intersect` rs) :) <$> go xs ys
+      (one (TConstant (ls `intersect` rs)) <>) <$> rest
     (TList l, TList r) ->
-      (\l' ->
-        (\r' -> do
-          m <- defer $ unify context l' r'
-          (TList m :) <$> go xs ys
-        ) =<< demand r
-      ) =<< demand l
-    (TSet x       , TSet Nothing ) -> (TSet x :) <$> go xs ys
-    (TSet Nothing , TSet x       ) -> (TSet x :) <$> go xs ys
+      do
+        l' <- demand l
+        r' <- demand r
+        m <- defer $ unify context l' r'
+        (one (TList m) <>) <$> rest
+    (TSet x       , TSet Nothing ) -> (one (TSet x) <>) <$> rest
+    (TSet Nothing , TSet x       ) -> (one (TSet x) <>) <$> rest
     (TSet (Just l), TSet (Just r)) -> do
       m <- sequenceA $ M.intersectionWith
         (\ i j ->
@@ -199,23 +216,26 @@
         id
         ((TSet (pure m) :) <$>)
         (not $ M.null m)
-        (go xs ys)
+        rest
 
     (TClosure{}, TClosure{}) ->
       throwError $ ErrorCall "Cannot unify functions"
     (TBuiltin _ _, TBuiltin _ _) ->
       throwError $ ErrorCall "Cannot unify builtin functions"
-    _ | compareTypes x y == LT -> go xs (y : ys)
-      | compareTypes x y == GT -> go (x : xs) ys
+    _ | compareTypes x y == LT -> go xs yys
+      | compareTypes x y == GT -> go xxs ys
       | otherwise              -> error "impossible"
+   where
+    rest :: m [NTypeF m (Symbolic m)]
+    rest = go xs ys
 
 {-
     mergeFunctions pl nl fl pr fr xs ys = do
         m <- sequenceA $ M.intersectionWith
             (\i j -> i >>= \i' -> j >>= \j' -> case (i', j') of
-                    (Nothing, Nothing) -> pure $ pure Nothing
-                    (_, Nothing) -> pure Nothing
-                    (Nothing, _) -> pure Nothing
+                    (Nothing, Nothing) -> stub
+                    (_, Nothing) -> stub
+                    (Nothing, _) -> stub
                     (Just i'', Just j'') ->
                         pure . pure <$> unify context i'' j'')
             (pure <$> pl) (pure <$> pr)
@@ -254,7 +274,7 @@
           writeRef y   (NMany m)
           packSymbolic (NMany m)
         )
-        (do
+        (
               -- x' <- renderSymbolic (Symbolic x)
               -- y' <- renderSymbolic (Symbolic y)
           throwError $ ErrorCall "Cannot unify "
@@ -273,9 +293,9 @@
 
 instance FromValue NixString m (Symbolic m) where
 
-instance FromValue (AttrSet (Symbolic m), AttrSet SourcePos) m (Symbolic m) where
+instance FromValue (AttrSet (Symbolic m), PositionSet) m (Symbolic m) where
 
-instance ToValue (AttrSet (Symbolic m), AttrSet SourcePos) m (Symbolic m) where
+instance ToValue (AttrSet (Symbolic m), PositionSet) m (Symbolic m) where
 
 instance (MonadThunkId m, MonadAtomicRef m, MonadCatch m)
   => MonadValue (Symbolic m) m where
@@ -297,7 +317,7 @@
 
 
 instance MonadLint e m => MonadEval (Symbolic m) m where
-  freeVariable var = symerr $ "Undefined variable '" <> var <> "'"
+  freeVariable var = symerr $ "Undefined variable '" <> coerce var <> "'"
 
   attrMissing ks ms =
     evalError @(Symbolic m) $ ErrorCall $ toString $
@@ -306,30 +326,30 @@
         (\ s ->  "Could not look up attribute " <> attr <> " in " <> show s)
         ms
    where
-    attr = Text.intercalate "." (NE.toList ks)
+    attr = Text.intercalate "." $ NE.toList $ coerce ks
 
   evalCurPos = do
-    f <- mkSymbolic [TPath]
-    l <- mkSymbolic [TConstant [TInt]]
-    c <- mkSymbolic [TConstant [TInt]]
-    mkSymbolic [TSet (pure (M.fromList [("file", f), ("line", l), ("col", c)]))]
+    f <- mkSymbolic $ one TPath
+    l <- mkSymbolic $ one $ TConstant $ one TInt
+    c <- mkSymbolic $ one $ TConstant $ one TInt
+    mkSymbolic $ one $ TSet (pure (M.fromList [("file", f), ("line", l), ("col", c)]))
 
-  evalConstant c = mkSymbolic [go c]
+  evalConstant c = mkSymbolic $ one $ fun c
    where
-    go =
+    fun =
       \case
         NURI   _ -> TStr
-        NInt   _ -> TConstant [TInt]
-        NFloat _ -> TConstant [TFloat]
-        NBool  _ -> TConstant [TBool]
-        NNull    -> TConstant [TNull]
+        NInt   _ -> TConstant $ one TInt
+        NFloat _ -> TConstant $ one TFloat
+        NBool  _ -> TConstant $ one TBool
+        NNull    -> TConstant $ one TNull
 
-  evalString      = const $ mkSymbolic [TStr]
-  evalLiteralPath = const $ mkSymbolic [TPath]
-  evalEnvPath     = const $ mkSymbolic [TPath]
+  evalString      = const $ mkSymbolic $ one TStr
+  evalLiteralPath = const $ mkSymbolic $ one TPath
+  evalEnvPath     = const $ mkSymbolic $ one TPath
 
   evalUnary op arg =
-    unify (void (NUnary op arg)) arg =<< mkSymbolic [TConstant [TInt, TBool]]
+    unify (void (NUnary op arg)) arg =<< mkSymbolic (one (TConstant [TInt, TBool]))
 
   evalBinary = lintBinaryOp
 
@@ -343,7 +363,7 @@
 
       pushWeakScope
         (case s of
-          NMany [TSet (Just s')] -> pure s'
+          NMany [TSet (Just (coerce -> scope))] -> pure scope
           NMany [TSet Nothing] -> error "NYI: with unknown"
           _ -> throwError $ ErrorCall "scope must be a set in with statement"
         )
@@ -355,18 +375,18 @@
       f' <- f
       let e = NIf cond t' f'
 
-      _ <- unify (void e) cond =<< mkSymbolic [TConstant [TBool]]
+      _ <- unify (void e) cond =<< mkSymbolic (one $ TConstant $ one TBool)
       unify (void e) t' f'
 
   evalAssert cond body =
     do
       body' <- body
       let e = NAssert cond body'
-      _ <- unify (void e) cond =<< mkSymbolic [TConstant [TBool]]
+      _ <- unify (void e) cond =<< mkSymbolic (one $ TConstant $ one TBool)
       pure body'
 
   evalApp = (fmap snd .) . lintApp (NBinary NApp () ())
-  evalAbs params _ = mkSymbolic [TClosure (void params)]
+  evalAbs params _ = mkSymbolic (one $ TClosure $ void params)
 
   evalError = throwError
 
@@ -389,36 +409,41 @@
           NEq     -> [TConstant [TInt, TBool, TNull], TStr, TList y]
           NNEq    -> [TConstant [TInt, TBool, TNull], TStr, TList y]
 
-          NLt     -> [TConstant [TInt, TBool, TNull]]
-          NLte    -> [TConstant [TInt, TBool, TNull]]
-          NGt     -> [TConstant [TInt, TBool, TNull]]
-          NGte    -> [TConstant [TInt, TBool, TNull]]
+          NLt     -> one $ TConstant [TInt, TBool, TNull]
+          NLte    -> one $ TConstant [TInt, TBool, TNull]
+          NGt     -> one $ TConstant [TInt, TBool, TNull]
+          NGte    -> one $ TConstant [TInt, TBool, TNull]
 
-          NAnd    -> [TConstant [TBool]]
-          NOr     -> [TConstant [TBool]]
-          NImpl   -> [TConstant [TBool]]
+          NAnd    -> one $ TConstant $ one TBool
+          NOr     -> one $ TConstant $ one TBool
+          NImpl   -> one $ TConstant $ one TBool
 
           -- jww (2018-04-01): NYI: Allow Path + Str
-          NPlus   -> [TConstant [TInt], TStr, TPath]
-          NMinus  -> [TConstant [TInt]]
-          NMult   -> [TConstant [TInt]]
-          NDiv    -> [TConstant [TInt]]
+          NPlus   -> [TConstant $ one TInt, TStr, TPath]
+          NMinus  -> one $ TConstant $ one TInt
+          NMult   -> one $ TConstant $ one TInt
+          NDiv    -> one $ TConstant $ one TInt
 
-          NUpdate -> [TSet mempty]
+          NUpdate -> one $ TSet mempty
 
-          NConcat -> [TList y]
+          NConcat -> one $ TList y
 #if __GLASGOW_HASKELL__ < 900
           _ -> fail "Should not be possible"  -- symerr or this fun signature should be changed to work in type scope
 #endif
+
+
+
  where
   check lsym rsym xs =
     do
-      let e = NBinary op lsym rsym
+      let
+        e = NBinary op lsym rsym
+        unifyE = unify (void e)
 
       m <- mkSymbolic xs
-      _ <- unify (void e) lsym m
-      _ <- unify (void e) rsym m
-      unify (void e) lsym rsym
+      _ <- unifyE lsym m
+      _ <- unifyE rsym m
+      unifyE lsym rsym
 
 infixl 1 `lintApp`
 lintApp
@@ -428,25 +453,29 @@
   -> Symbolic m
   -> m (Symbolic m)
   -> m (HashMap VarName (Symbolic m), Symbolic m)
-lintApp context fun arg = unpackSymbolic fun >>= \case
-  NAny ->
-    throwError $ ErrorCall "Cannot apply something not known to be a function"
-  NMany xs -> do
-    (args, ys) <- fmap unzip $ forM xs $ \case
-      TClosure _params -> arg >>= unpackSymbolic >>= \case
-        NAny -> do
-          error "NYI"
+lintApp context fun arg =
+  (\case
+    NAny ->
+      throwError $ ErrorCall "Cannot apply something not known to be a function"
+    NMany xs -> do
+      (args, ys) <- fmap unzip $ forM xs $ \case
+        TClosure _params ->
+          (\case
+            NAny -> do
+              error "NYI"
 
-        NMany [TSet (Just _)] -> do
-          error "NYI"
+            NMany [TSet (Just _)] -> do
+              error "NYI"
 
-        NMany _ -> throwError $ ErrorCall "NYI: lintApp NMany not set"
-      TBuiltin _ _f -> throwError $ ErrorCall "NYI: lintApp builtin"
-      TSet _m       -> throwError $ ErrorCall "NYI: lintApp Set"
-      _x            -> throwError $ ErrorCall "Attempt to call non-function"
+            NMany _ -> throwError $ ErrorCall "NYI: lintApp NMany not set"
+          ) =<< unpackSymbolic =<< arg
+        TBuiltin _ _f -> throwError $ ErrorCall "NYI: lintApp builtin"
+        TSet _m       -> throwError $ ErrorCall "NYI: lintApp Set"
+        _x            -> throwError $ ErrorCall "Attempt to call non-function"
 
-    y <- everyPossible
-    (head args, ) <$> foldM (unify context) y ys
+      y <- everyPossible
+      (Unsafe.head args, ) <$> foldM (unify context) y ys
+  ) =<< unpackSymbolic fun
 
 newtype Lint s a = Lint
   { runLint :: ReaderT (Context (Lint s) (Symbolic (Lint s))) (FreshIdT Int (ST s)) a }
@@ -462,10 +491,10 @@
     )
 
 instance MonadThrow (Lint s) where
-  throwM e = Lint $ ReaderT $ \_ -> throw e
+  throwM e = Lint $ ReaderT $ const (throw e)
 
 instance MonadCatch (Lint s) where
-  catch _m _h = Lint $ ReaderT $ \_ -> fail "Cannot catch in 'Lint s'"
+  catch _m _h = Lint $ ReaderT $ const (fail "Cannot catch in 'Lint s'")
 
 runLintM :: Options -> Lint s a -> ST s a
 runLintM opts action = do
@@ -475,7 +504,7 @@
 symbolicBaseEnv
   :: Monad m
   => m (Scopes m (Symbolic m))
-symbolicBaseEnv = pure mempty
+symbolicBaseEnv = stub
 
 lint :: Options -> NExprLoc -> ST s (Symbolic (Lint s))
 lint opts expr =
diff --git a/src/Nix/Normal.hs b/src/Nix/Normal.hs
--- a/src/Nix/Normal.hs
+++ b/src/Nix/Normal.hs
@@ -1,18 +1,15 @@
-{-# LANGUAGE AllowAmbiguousTypes #-}
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE DataKinds #-}
-{-# LANGUAGE GADTs #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE RankNTypes #-}
+{-# language AllowAmbiguousTypes #-}
+{-# language ConstraintKinds #-}
+{-# language DataKinds #-}
+{-# language GADTs #-}
+{-# language TypeFamilies #-}
+{-# language RankNTypes #-}
 
 -- | Code for normalization (reduction into a normal form) of Nix expressions.
 -- Nix language allows recursion, so some expressions do not converge.
 -- And so do not converge into a normal form.
 module Nix.Normal where
 
-import           Prelude            hiding ( force )
-import           Nix.Utils
 import           Control.Monad.Free        ( Free(..) )
 import           Data.Set                  ( member
                                            , insert
@@ -23,7 +20,7 @@
 import           Nix.Value
 
 newtype NormalLoop t f m = NormalLoop (NValue t f m)
-    deriving Show
+  deriving Show
 
 instance MonadDataErrorContext t f m => Exception (NormalLoop t f m)
 
@@ -158,10 +155,10 @@
     (\_ t ->
       Free $
         NValue' $
-          foldr
-            (addProvenance1 @m @(NValue t f m))
+          foldl'
+            (flip $ addProvenance1 @m @(NValue t f m))
             cyc
-            (reverse $ citations @m @(NValue t f m) t)
+            (citations @m @(NValue t f m) t)
     )
     Free
  where
diff --git a/src/Nix/Options.hs b/src/Nix/Options.hs
--- a/src/Nix/Options.hs
+++ b/src/Nix/Options.hs
@@ -1,40 +1,42 @@
-{-# LANGUAGE StrictData #-}
+{-# language StrictData #-}
 
 -- | Definitions & defaults for the CLI options
 module Nix.Options where
 
 import           Data.Time
 
+--  2021-07-15: NOTE: What these are? They need to be documented.
+-- Also need better names. Foe example, Maybes & lists names need to show their type in the name.
 data Options = Options
     { verbose      :: Verbosity
     , tracing      :: Bool
     , thunks       :: Bool
     , values       :: Bool
     , showScopes   :: Bool
-    , reduce       :: Maybe FilePath
+    , reduce       :: Maybe Path
     , reduceSets   :: Bool
     , reduceLists  :: Bool
     , parse        :: Bool
     , parseOnly    :: Bool
     , finder       :: Bool
-    , findFile     :: Maybe FilePath
+    , findFile     :: Maybe Path
     , strict       :: Bool
     , evaluate     :: Bool
     , json         :: Bool
     , xml          :: Bool
     , attr         :: Maybe Text
-    , include      :: [FilePath]
+    , include      :: [Path]
     , check        :: Bool
-    , readFrom     :: Maybe FilePath
+    , readFrom     :: Maybe Path
     , cache        :: Bool
     , repl         :: Bool
     , ignoreErrors :: Bool
     , expression   :: Maybe Text
     , arg          :: [(Text, Text)]
     , argstr       :: [(Text, Text)]
-    , fromFile     :: Maybe FilePath
+    , fromFile     :: Maybe Path
     , currentTime  :: UTCTime
-    , filePaths    :: [FilePath]
+    , filePaths    :: [Path]
     }
     deriving Show
 
diff --git a/src/Nix/Options/Parser.hs b/src/Nix/Options/Parser.hs
--- a/src/Nix/Options/Parser.hs
+++ b/src/Nix/Options/Parser.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE TemplateHaskell #-}
+{-# language TemplateHaskell #-}
 
 -- | Code that configures presentation parser for the CLI options
 module Nix.Options.Parser where
@@ -212,7 +212,7 @@
   debugVersionOpt :: Parser (a -> a)
   debugVersionOpt =
     infoOption
-      ( concat
+      ( fold
           [ "Version: ", showVersion version
           , "\nCommit: ", $(gitHash)
           , "\n  date: ", $(gitCommitDate)
@@ -227,4 +227,4 @@
 nixOptionsInfo current =
   info
     (helper <*> versionOpt <*> nixOptions current)
-    (fullDesc <> progDesc "" <> header "hnix")
+    (fullDesc <> progDesc mempty <> header "hnix")
diff --git a/src/Nix/Parser.hs b/src/Nix/Parser.hs
--- a/src/Nix/Parser.hs
+++ b/src/Nix/Parser.hs
@@ -1,773 +1,920 @@
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE DeriveAnyClass #-}
-
-{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
-
--- | Main module for parsing Nix expressions.
-module Nix.Parser
-  ( parseNixFile
-  , parseNixFileLoc
-  , parseNixText
-  , parseNixTextLoc
-  , parseFromFileEx
-  , Parser
-  , parseFromText
-  , Result
-  , reservedNames
-  , OperatorInfo(..)
-  , NSpecialOp(..)
-  , NAssoc(..)
-  , NOperatorDef
-  , getUnaryOperator
-  , getBinaryOperator
-  , getSpecialOperator
-  , nixToplevelForm
-  , nixExpr
-  , nixSet
-  , nixBinders
-  , nixSelector
-  , nixSym
-  , nixPath
-  , nixString
-  , nixUri
-  , nixSearchPath
-  , nixFloat
-  , nixInt
-  , nixBool
-  , nixNull
-  , symbol
-  , whiteSpace
-  )
-where
-
-import           Prelude                 hiding ( some
-                                                , many
-                                                , readFile
-                                                )
-import           Data.Foldable                  ( foldr1 )
-
-import           Control.Monad                  ( msum )
-import           Control.Monad.Combinators.Expr ( makeExprParser
-                                                , Operator( Postfix
-                                                          , InfixN
-                                                          , InfixR
-                                                          , Prefix
-                                                          , InfixL
-                                                          )
-                                                )
-import           Data.Char                      ( isAlpha
-                                                , isDigit
-                                                , isSpace
-                                                )
-import           Data.Data                      ( Data(..) )
-import           Data.Fix                       ( Fix(..) )
-import qualified Data.HashSet                  as HashSet
-import qualified Data.Map                      as Map
-import           Data.Text                      ( cons )
-import           Nix.Expr                hiding ( ($>) )
-import           Nix.Expr.Strings               ( escapeCodes
-                                                , stripIndent
-                                                , mergePlain
-                                                , removePlainEmpty
-                                                )
-import           Nix.Render                     ( MonadFile(readFile) )
-import           Prettyprinter                  ( Doc
-                                                , pretty
-                                                )
--- `parser-combinators` ships performance enhanced & MonadPlus-aware combinators.
--- For example `smome` and `many` impoted here.
-import           Text.Megaparsec         hiding ( State )
-import           Text.Megaparsec.Char           ( space1
-                                                , string
-                                                , letterChar
-                                                , char
-                                                )
-import qualified Text.Megaparsec.Char.Lexer    as Lexer
-
--- | Different to @isAlphaNum@
-isAlphanumeric :: Char -> Bool
-isAlphanumeric x = isAlpha x || isDigit x
-{-# inline isAlphanumeric #-}
-
-infixl 3 <+>
-(<+>) :: MonadPlus m => m a -> m a -> m a
-(<+>) = mplus
-
----------------------------------------------------------------------------------
-
-nixExpr :: Parser NExprLoc
-nixExpr =
-  makeExprParser
-    nixTerm $
-      snd <<$>>
-        nixOperators nixSelector
-
-antiStart :: Parser Text
-antiStart = symbol "${" <?> "${"
-
-nixAntiquoted :: Parser a -> Parser (Antiquoted a NExprLoc)
-nixAntiquoted p =
-  Antiquoted <$>
-    (antiStart *> nixToplevelForm <* symbol "}")
-      <+> Plain <$>
-        p
-      <?> "anti-quotation"
-
-selDot :: Parser ()
-selDot = try (symbol "." *> notFollowedBy nixPath) <?> "."
-
-nixSelect :: Parser NExprLoc -> Parser NExprLoc
-nixSelect term =
-  do
-    res <-
-      liftA2 build
-        term
-        (optional $
-          liftA2 (,)
-            (selDot *> nixSelector)
-            (optional $ reserved "or" *> nixTerm)
-        )
-    continues <- optional $ lookAhead selDot
-
-    maybe
-      id
-      (const nixSelect)
-      continues
-      (pure res)
- where
-  build
-    :: NExprLoc
-    -> Maybe ( Ann SrcSpan (NAttrPath NExprLoc)
-        , Maybe NExprLoc
-        )
-    -> NExprLoc
-  build t mexpr =
-    maybe
-      id
-      (\ expr t -> (uncurry $ nSelectLoc t) expr)
-      mexpr
-      t
-
-nixSelector :: Parser (Ann SrcSpan (NAttrPath NExprLoc))
-nixSelector =
-  annotateLocation $
-    do
-      (x : xs) <- keyName `sepBy1` selDot
-      pure $ x :| xs
-
-nixTerm :: Parser NExprLoc
-nixTerm = do
-  c <- try $ lookAhead $ satisfy $ \x ->
-    pathChar x || (`elem` ("({[</\"'^" :: String)) x
-  case c of
-    '('  -> nixSelect nixParens
-    '{'  -> nixSelect nixSet
-    '['  -> nixList
-    '<'  -> nixSearchPath
-    '/'  -> nixPath
-    '"'  -> nixString
-    '\'' -> nixString
-    '^'  -> nixSynHole
-    _ ->
-      msum
-        $  [ nixSelect nixSet | c == 'r' ]
-        <> [ nixPath | pathChar c ]
-        <> if isDigit c
-             then [ nixFloat, nixInt ]
-             else
-               [ nixUri | isAlpha c ]
-               <> [ nixBool | c == 't' || c == 'f' ]
-               <> [ nixNull | c == 'n' ]
-               <> [ nixSelect nixSym ]
-
-nixToplevelForm :: Parser NExprLoc
-nixToplevelForm = keywords <+> nixLambda <+> nixExpr
- where
-  keywords = nixLet <+> nixIf <+> nixAssert <+> nixWith
-
-nixSym :: Parser NExprLoc
-nixSym = annotateLocation1 $ mkSymF <$> identifier
-
-nixSynHole :: Parser NExprLoc
-nixSynHole = annotateLocation1 $ mkSynHoleF <$> (char '^' *> identifier)
-
-nixInt :: Parser NExprLoc
-nixInt = annotateLocation1 (mkIntF <$> integer <?> "integer")
-
-nixFloat :: Parser NExprLoc
-nixFloat =
-  annotateLocation1 (try (mkFloatF . realToFrac <$> float) <?> "float")
-
-nixBool :: Parser NExprLoc
-nixBool =
-  annotateLocation1 (bool "true" True <+> bool "false" False) <?> "bool"
- where
-  bool str b = mkBoolF b <$ reserved str
-
-nixNull :: Parser NExprLoc
-nixNull = annotateLocation1 (mkNullF <$ reserved "null" <?> "null")
-
--- | 'nixTopLevelForm' returns an expression annotated with a source position,
--- however this position doesn't include the parsed parentheses, so remove the
--- "inner" location annotateion and annotate again, including the parentheses.
-nixParens :: Parser NExprLoc
-nixParens = annotateLocation1 (parens (stripAnn . unFix <$> nixToplevelForm) <?> "parens")
-
-nixList :: Parser NExprLoc
-nixList = annotateLocation1 (brackets (NList <$> many nixTerm) <?> "list")
-
-pathChar :: Char -> Bool
-pathChar x =
-  isAlphanumeric x || (`elem` ("._-+~" :: String)) x
-
-slash :: Parser Char
-slash =
-  try
-    (  char '/'
-    <* notFollowedBy (satisfy (\x -> x == '/' || x == '*' || isSpace x))
-    )
-    <?> "slash"
-
--- | A path surrounded by angle brackets, indicating that it should be
--- looked up in the NIX_PATH environment variable at evaluation.
-nixSearchPath :: Parser NExprLoc
-nixSearchPath =
-  annotateLocation1
-    (mkPathF True <$>
-      try (char '<' *> many (satisfy pathChar <+> slash) <* symbol ">")
-      <?> "spath"
-    )
-
-pathStr :: Parser FilePath
-pathStr =
-  lexeme $
-    liftA2 (<>)
-      (many $ satisfy pathChar)
-      (concat <$>
-        some
-          (liftA2 (:)
-            slash
-            (some $ satisfy pathChar)
-          )
-      )
-
-nixPath :: Parser NExprLoc
-nixPath = annotateLocation1 (try (mkPathF False <$> pathStr) <?> "path")
-
-nixLet :: Parser NExprLoc
-nixLet = annotateLocation1
-  (reserved "let" *> (letBody <+> letBinders) <?> "let block")
- where
-  letBinders =
-    liftA2 NLet
-      nixBinders
-      (reserved "in" *> nixToplevelForm)
-  -- Let expressions `let {..., body = ...}' are just desugared
-  -- into `(rec {..., body = ...}).body'.
-  letBody    = (\x -> NSelect x (StaticKey "body" :| mempty) Nothing) <$> aset
-  aset       = annotateLocation1 $ NSet NRecursive <$> braces nixBinders
-
-nixIf :: Parser NExprLoc
-nixIf = annotateLocation1
-  (liftA3 NIf
-    (reserved "if"   *> nixExpr        )
-    (reserved "then" *> nixToplevelForm)
-    (reserved "else" *> nixToplevelForm)
-  <?> "if"
-  )
-
-nixAssert :: Parser NExprLoc
-nixAssert = annotateLocation1
-  (liftA2 NAssert
-    (reserved "assert" *> nixToplevelForm)
-    (semi              *> nixToplevelForm)
-  <?> "assert"
-  )
-
-nixWith :: Parser NExprLoc
-nixWith = annotateLocation1
-  (liftA2 NWith
-    (reserved "with" *> nixToplevelForm)
-    (semi            *> nixToplevelForm)
-  <?> "with"
-  )
-
-nixLambda :: Parser NExprLoc
-nixLambda =
-  liftA2 nAbs
-    (annotateLocation $ try argExpr)
-    nixToplevelForm
-
-nixString :: Parser NExprLoc
-nixString = nStr <$> annotateLocation nixString'
-
-nixUri :: Parser NExprLoc
-nixUri = lexeme $ annotateLocation1 $ try $ do
-  start    <- letterChar
-  protocol <- many $
-    satisfy $
-      \ x ->
-        isAlphanumeric x
-        || (`elem` ("+-." :: String)) x
-  _       <- string ":"
-  address <-
-    some $
-      satisfy $
-        \ x ->
-          isAlphanumeric x
-          || (`elem` ("%/?:@&=+$,-_.!~*'" :: String)) x
-  pure $ NStr $ DoubleQuoted
-    [Plain $ toText $ start : protocol ++ ':' : address]
-
-nixString' :: Parser (NString NExprLoc)
-nixString' = lexeme (doubleQuoted <+> indented <?> "string")
- where
-  doubleQuoted :: Parser (NString NExprLoc)
-  doubleQuoted =
-    DoubleQuoted
-    . removePlainEmpty
-    . mergePlain <$>
-      ( doubleQ
-      *> many (stringChar doubleQ (void $ char '\\') doubleEscape)
-      <* doubleQ
-      )
-      <?> "double quoted string"
-
-  doubleQ      = void $ char '"'
-  doubleEscape = Plain . one <$> (char '\\' *> escapeCode)
-
-  indented :: Parser (NString NExprLoc)
-  indented =
-    stripIndent <$>
-      (indentedQ
-      *> many (stringChar indentedQ indentedQ indentedEscape)
-      <* indentedQ
-      )
-      <?> "indented string"
-
-  indentedQ      = void (string "''" <?> "\"''\"")
-  indentedEscape =
-    try $
-      do
-        indentedQ
-        (Plain <$> ("''" <$ char '\'' <+> "$" <$ char '$')) <+>
-          do
-            _ <- char '\\'
-            c <- escapeCode
-
-            pure $
-              bool
-                EscapedNewline
-                (Plain $ one c)
-                (c /= '\n')
-
-  stringChar end escStart esc =
-    Antiquoted <$>
-      (antiStart *> nixToplevelForm <* char '}')
-        <+> Plain . one <$>
-          char '$' <+> esc <+> Plain . toText <$>
-            some plainChar
-   where
-    plainChar =
-      notFollowedBy (end <+> void (char '$') <+> escStart) *> anySingle
-
-  escapeCode =
-    msum
-      [ c <$ char e | (c, e) <- escapeCodes ]
-    <+> anySingle
-
--- | Gets all of the arguments for a function.
-argExpr :: Parser (Params NExprLoc)
-argExpr =
-  msum
-    [ atLeft
-    , onlyname
-    , atRight
-    ]
-  <* symbol ":"
- where
-  -- An argument not in curly braces. There's some potential ambiguity
-  -- in the case of, for example `x:y`. Is it a lambda function `x: y`, or
-  -- a URI `x:y`? Nix syntax says it's the latter. So we need to fail if
-  -- there's a valid URI parse here.
-  onlyname =
-    msum
-      [ nixUri *> unexpected (Label ('v' :| "alid uri"))
-      , Param <$> identifier
-      ]
-
-  -- Parameters named by an identifier on the left (`args @ {x, y}`)
-  atLeft =
-    try $
-      do
-        name               <- identifier <* symbol "@"
-        (params, variadic) <- params
-        pure $ ParamSet params variadic $ pure name
-
-  -- Parameters named by an identifier on the right, or none (`{x, y} @ args`)
-  atRight =
-    do
-      (params, variadic) <- params
-      name               <- optional $ symbol "@" *> identifier
-      pure $ ParamSet params variadic name
-
-  -- Return the parameters set.
-  params = braces getParams
-
-  -- Collects the parameters within curly braces. Returns the parameters and
-  -- a boolean indicating if the parameters are variadic.
-  getParams :: Parser ([(Text, Maybe NExprLoc)], Bool)
-  getParams = go mempty
-   where
-    -- Attempt to parse `...`. If this succeeds, stop and return True.
-    -- Otherwise, attempt to parse an argument, optionally with a
-    -- default. If this fails, then return what has been accumulated
-    -- so far.
-    go acc = ((acc, True) <$ symbol "...") <+> getMore
-     where
-      getMore =
-        -- Could be nothing, in which just return what we have so far.
-        option (acc, False) $
-          do
-            -- Get an argument name and an optional default.
-            pair <-
-              liftA2 (,)
-                identifier
-                (optional $ question *> nixToplevelForm)
-
-            let args = acc <> [pair]
-
-            -- Either return this, or attempt to get a comma and restart.
-            option (args, False) $ comma *> go args
-
-nixBinders :: Parser [Binding NExprLoc]
-nixBinders = (inherit <+> namedVar) `endBy` semi where
-  inherit =
-    do
-      -- We can't use 'reserved' here because it would consume the whitespace
-      -- after the keyword, which is not exactly the semantics of C++ Nix.
-      try $ string "inherit" *> lookAhead (void (satisfy reservedEnd))
-      p <- getSourcePos
-      x <- whiteSpace *> optional scope
-      liftA2 (Inherit x)
-        (many keyName)
-        (pure p)
-        <?> "inherited binding"
-  namedVar =
-    do
-      p <- getSourcePos
-      liftA3 NamedVar
-        (annotated <$> nixSelector)
-        (equals *> nixToplevelForm)
-        (pure p)
-        <?> "variable binding"
-  scope = nixParens <?> "inherit scope"
-
-keyName :: Parser (NKeyName NExprLoc)
-keyName = dynamicKey <+> staticKey
- where
-  staticKey  = StaticKey <$> identifier
-  dynamicKey = DynamicKey <$> nixAntiquoted nixString'
-
-nixSet :: Parser NExprLoc
-nixSet = annotateLocation1 ((isRec <*> braces nixBinders) <?> "set")
- where
-  isRec = (reserved "rec" $> NSet NRecursive <?> "recursive set") <+> pure (NSet NNonRecursive)
-
-parseNixFile :: MonadFile m => FilePath -> m (Result NExpr)
-parseNixFile =
-  parseFromFileEx $ stripAnnotation <$> (whiteSpace *> nixToplevelForm <* eof)
-
-parseNixFileLoc :: MonadFile m => FilePath -> m (Result NExprLoc)
-parseNixFileLoc = parseFromFileEx (whiteSpace *> nixToplevelForm <* eof)
-
-parseNixText :: Text -> Result NExpr
-parseNixText =
-  parseFromText $ stripAnnotation <$> (whiteSpace *> nixToplevelForm <* eof)
-
-parseNixTextLoc :: Text -> Result NExprLoc
-parseNixTextLoc = parseFromText (whiteSpace *> nixToplevelForm <* eof)
-
-{- Parser.Library -}
-
-skipLineComment' :: Tokens Text -> Parser ()
-skipLineComment' prefix =
-  string prefix *> void (takeWhileP (pure "character") (\x -> x /= '\n' && x /= '\r'))
-
-whiteSpace :: Parser ()
-whiteSpace = do
-  put =<< getSourcePos
-  Lexer.space space1 lineCmnt blockCmnt
- where
-  lineCmnt  = skipLineComment' "#"
-  blockCmnt = Lexer.skipBlockComment "/*" "*/"
-
-lexeme :: Parser a -> Parser a
-lexeme p = p <* whiteSpace
-
-symbol :: Text -> Parser Text
-symbol = lexeme . string
-
-reservedEnd :: Char -> Bool
-reservedEnd x =
-  isSpace x || (`elem` ("{([})];:.\"'," :: String)) x
-{-# inline reservedEnd #-}
-
-reserved :: Text -> Parser ()
-reserved n =
-  lexeme $ try $ string n *> lookAhead (void (satisfy reservedEnd) <|> eof)
-
-identifier :: Parser Text
-identifier = lexeme $ try $ do
-  ident <-
-    liftA2 cons
-      (satisfy (\x -> isAlpha x || x == '_'))
-      (takeWhileP mempty identLetter)
-  guard $ not $ ident `HashSet.member` reservedNames
-  pure ident
- where
-  identLetter x = isAlphanumeric x || x == '_' || x == '\'' || x == '-'
-
--- We restrict the type of 'parens' and 'brackets' here because if they were to
--- take a @Parser NExprLoc@ argument they would parse additional text which
--- wouldn't be captured in the source location annotation.
---
--- Braces and angles in hnix don't enclose a single expression so this type
--- restriction would not be useful.
-parens :: Parser (NExprF f) -> Parser (NExprF f)
-parens   = between (symbol "(") (symbol ")")
-braces :: ParsecT Void Text (State SourcePos) a -> ParsecT Void Text (State SourcePos) a
-braces   = between (symbol "{") (symbol "}")
--- angles    = between (symbol "<") (symbol ">")
-brackets :: Parser (NExprF f) -> Parser (NExprF f)
-brackets = between (symbol "[") (symbol "]")
-semi :: Parser Text
-semi     = symbol ";"
-comma :: Parser Text
-comma    = symbol ","
--- colon     = symbol ":"
--- dot       = symbol "."
-equals :: Parser Text
-equals   = symbol "="
-question :: Parser Text
-question = symbol "?"
-
-integer :: Parser Integer
-integer = lexeme Lexer.decimal
-
-float :: Parser Double
-float = lexeme Lexer.float
-
-reservedNames :: HashSet Text
-reservedNames =
-  HashSet.fromList
-    ["let", "in", "if", "then", "else", "assert", "with", "rec", "inherit"]
-
-type Parser = ParsecT Void Text (State SourcePos)
-
-type Result a = Either (Doc Void) a
-
-parseFromFileEx :: MonadFile m => Parser a -> FilePath -> m (Result a)
-parseFromFileEx parser file =
-  do
-    input <- decodeUtf8 <$> readFile file
-
-    pure $
-      either
-        (Left . pretty . errorBundlePretty)
-        pure
-        $ (`evalState` initialPos file) $ runParserT parser file input
-
-parseFromText :: Parser a -> Text -> Result a
-parseFromText parser input =
-  let stub = "<string>" in
-  either
-    (Left . pretty . errorBundlePretty)
-    pure
-    $ (`evalState` initialPos stub) $ (`runParserT` stub) parser input
-
-{- Parser.Operators -}
-
-data NSpecialOp = NHasAttrOp | NSelectOp
-  deriving (Eq, Ord, Generic, Typeable, Data, Show, NFData)
-
-data NAssoc = NAssocNone | NAssocLeft | NAssocRight
-  deriving (Eq, Ord, Generic, Typeable, Data, Show, NFData)
-
-data NOperatorDef
-  = NUnaryDef   Text NUnaryOp
-  | NBinaryDef  Text NBinaryOp  NAssoc
-  | NSpecialDef Text NSpecialOp NAssoc
-  deriving (Eq, Ord, Generic, Typeable, Data, Show, NFData)
-
-annotateLocation :: Parser a -> Parser (Ann SrcSpan a)
-annotateLocation p =
-  do
-    begin <- getSourcePos
-    res <- p
-    end   <- get -- The state set before the last whitespace
-
-    pure $ Ann (SrcSpan begin end) res
-
-annotateLocation1 :: Parser (NExprF NExprLoc) -> Parser NExprLoc
-annotateLocation1 = fmap annToAnnF . annotateLocation
-
-manyUnaryOp :: MonadPlus f => f (a -> a) -> f (a -> a)
-manyUnaryOp f = foldr1 (.) <$> some f
-
-operator :: Text -> Parser Text
-operator op =
-  case op of
-    "-" -> tuneLexer "-" '>'
-    "/" -> tuneLexer "/" '/'
-    "<" -> tuneLexer "<" '='
-    ">" -> tuneLexer ">" '='
-    n   -> symbol n
- where
-  tuneLexer opchar nonextchar =
-    lexeme . try $ string opchar <* notFollowedBy (char nonextchar)
-
-opWithLoc :: Text -> o -> (Ann SrcSpan o -> a) -> Parser a
-opWithLoc name op f =
-  do
-    Ann ann _ <-
-      annotateLocation $
-        {- dbg (toString name) $ -}
-        operator name
-
-    pure $ f $ Ann ann op
-
-binaryN :: Text -> NBinaryOp -> (NOperatorDef, Operator (ParsecT Void Text (State SourcePos)) NExprLoc)
-binaryN name op =
-  (NBinaryDef name op NAssocNone, InfixN $ opWithLoc name op nBinary)
-binaryL :: Text -> NBinaryOp -> (NOperatorDef, Operator (ParsecT Void Text (State SourcePos)) NExprLoc)
-binaryL name op =
-  (NBinaryDef name op NAssocLeft, InfixL $ opWithLoc name op nBinary)
-binaryR :: Text -> NBinaryOp -> (NOperatorDef, Operator (ParsecT Void Text (State SourcePos)) NExprLoc)
-binaryR name op =
-  (NBinaryDef name op NAssocRight, InfixR $ opWithLoc name op nBinary)
-prefix :: Text -> NUnaryOp -> (NOperatorDef, Operator (ParsecT Void Text (State SourcePos)) NExprLoc)
-prefix name op =
-  (NUnaryDef name op, Prefix $ manyUnaryOp $ opWithLoc name op nUnary)
--- postfix name op = (NUnaryDef name op,
---                    Postfix (opWithLoc name op nUnary))
-
-nixOperators
-  :: Parser (Ann SrcSpan (NAttrPath NExprLoc))
-  -> [[(NOperatorDef, Operator Parser NExprLoc)]]
-nixOperators selector =
-  [ -- This is not parsed here, even though technically it's part of the
-    -- expression table. The problem is that in some cases, such as list
-    -- membership, it's also a term. And since terms are effectively the
-    -- highest precedence entities parsed by the expression parser, it ends up
-    -- working out that we parse them as a kind of "meta-term".
-
-    -- {-  1 -} [ (NSpecialDef "." NSelectOp NAssocLeft,
-    --             Postfix $ do
-    --                    sel <- seldot *> selector
-    --                    mor <- optional (reserved "or" *> term)
-    --                    pure $ \x -> nSelectLoc x sel mor) ]
-
-    {-  2 -}
-    [ ( NBinaryDef " " NApp NAssocLeft
-      ,
-        -- Thanks to Brent Yorgey for showing me this trick!
-        InfixL $ nApp <$ symbol ""
-      )
-    ]
-  , {-  3 -}
-    [ prefix  "-"  NNeg ]
-  , {-  4 -}
-    [ ( NSpecialDef "?" NHasAttrOp NAssocLeft
-      , Postfix $ symbol "?" *> (flip nHasAttr <$> selector)
-      )
-    ]
-  , {-  5 -}
-    [ binaryR "++" NConcat ]
-  , {-  6 -}
-    [ binaryL "*"  NMult
-    , binaryL "/"  NDiv
-    ]
-  , {-  7 -}
-    [ binaryL "+"  NPlus
-    , binaryL "-"  NMinus
-    ]
-  , {-  8 -}
-    [ prefix  "!"  NNot ]
-  , {-  9 -}
-    [ binaryR "//" NUpdate ]
-  , {- 10 -}
-    [ binaryL "<"  NLt
-    , binaryL ">"  NGt
-    , binaryL "<=" NLte
-    , binaryL ">=" NGte
-    ]
-  , {- 11 -}
-    [ binaryN "==" NEq
-    , binaryN "!=" NNEq
-    ]
-  , {- 12 -}
-    [ binaryL "&&" NAnd ]
-  , {- 13 -}
-    [ binaryL "||" NOr ]
-  , {- 14 -}
-    [ binaryR "->" NImpl ]
-  ]
-
-data OperatorInfo = OperatorInfo
-  { precedence    :: Int
-  , associativity :: NAssoc
-  , operatorName  :: Text
-  } deriving (Eq, Ord, Generic, Typeable, Data, Show)
-
-getUnaryOperator :: NUnaryOp -> OperatorInfo
-getUnaryOperator = (m Map.!)
- where
-  m =
-    Map.fromList $
-      concat $
-        zipWith
-          buildEntry
-          [1 ..]
-          (nixOperators $ fail "unused")
-
-  buildEntry i =
-    concatMap $
-      \case
-        (NUnaryDef name op, _) -> [(op, OperatorInfo i NAssocNone name)]
-        _                      -> mempty
-
-getBinaryOperator :: NBinaryOp -> OperatorInfo
-getBinaryOperator = (m Map.!)
- where
-  m =
-    Map.fromList $
-      concat $
-        zipWith
-          buildEntry
-          [1 ..]
-          (nixOperators $ fail "unused")
-
-  buildEntry i =
-    concatMap $
-      \case
-        (NBinaryDef name op assoc, _) -> [(op, OperatorInfo i assoc name)]
-        _                             -> mempty
-
-getSpecialOperator :: NSpecialOp -> OperatorInfo
-getSpecialOperator NSelectOp = OperatorInfo 1 NAssocLeft "."
-getSpecialOperator o         = m Map.! o
- where
-  m =
-    Map.fromList $
-      concat $
-        zipWith
-          buildEntry
-          [1 ..]
-          (nixOperators $ fail "unused")
-
-  buildEntry i =
-    concatMap $
-      \case
-        (NSpecialDef name op assoc, _) -> [(op, OperatorInfo i assoc name)]
-        _                              -> mempty
+{-# language CPP #-}
+{-# language DeriveAnyClass #-}
+
+{-# options_ghc -fno-warn-name-shadowing #-}
+
+-- | Main module for parsing Nix expressions.
+module Nix.Parser
+  ( parseNixFile
+  , parseNixFileLoc
+  , parseNixText
+  , parseNixTextLoc
+  , parseFromFileEx
+  , Parser
+  , parseFromText
+  , Result
+  , reservedNames
+  , OperatorInfo(..)
+  , NSpecialOp(..)
+  , NAssoc(..)
+  , NOperatorDef
+  , getUnaryOperator
+  , getBinaryOperator
+  , getSpecialOperator
+  , nixExpr
+  , nixExprAlgebra
+  , nixSet
+  , nixBinders
+  , nixSelector
+  , nixSym
+  , nixPath
+  , nixString
+  , nixUri
+  , nixSearchPath
+  , nixFloat
+  , nixInt
+  , nixBool
+  , nixNull
+  , whiteSpace
+  )
+where
+
+import           Prelude                 hiding ( (<|>)
+                                                , some
+                                                , many
+                                                )
+import           Data.Foldable                  ( foldr1 )
+
+import           Control.Monad                  ( msum )
+import           Control.Monad.Combinators.Expr ( makeExprParser
+                                                , Operator( Postfix
+                                                          , InfixN
+                                                          , InfixR
+                                                          , Prefix
+                                                          , InfixL
+                                                          )
+                                                )
+import           Data.Char                      ( isAlpha
+                                                , isDigit
+                                                , isSpace
+                                                )
+import           Data.Data                      ( Data(..) )
+import           Data.Fix                       ( Fix(..) )
+import qualified Data.HashSet                  as HashSet
+import qualified Data.Map                      as Map
+import qualified Data.Text                     as Text
+import           Nix.Expr.Types
+import           Nix.Expr.Shorthands     hiding ( ($>) )
+import           Nix.Expr.Types.Annotated
+import           Nix.Expr.Strings               ( escapeCodes
+                                                , stripIndent
+                                                , mergePlain
+                                                , removeEmptyPlains
+                                                )
+import           Nix.Render                     ( MonadFile() )
+import           Prettyprinter                  ( Doc
+                                                , pretty
+                                                )
+-- `parser-combinators` ships performance enhanced & MonadPlus-aware combinators.
+-- For example `some` and `many` impoted here.
+import           Text.Megaparsec         hiding ( (<|>)
+                                                , State
+                                                )
+import           Text.Megaparsec.Char           ( space1
+                                                , letterChar
+                                                , char
+                                                )
+import qualified Text.Megaparsec.Char.Lexer    as Lexer
+
+
+type Parser = ParsecT Void Text (State SourcePos)
+
+-- * Utils
+
+-- | Different to @isAlphaNum@
+isAlphanumeric :: Char -> Bool
+isAlphanumeric x = isAlpha x || isDigit x
+{-# inline isAlphanumeric #-}
+
+-- | @<|>@ with additional preservation of @MonadPlus@ constraint.
+infixl 3 <|>
+(<|>) :: MonadPlus m => m a -> m a -> m a
+(<|>) = mplus
+
+-- ** Annotated
+
+annotateLocation1 :: Parser a -> Parser (AnnUnit SrcSpan a)
+annotateLocation1 p =
+  do
+    begin <- getSourcePos
+    res <- p
+    end   <- get -- The state set before the last whitespace
+
+    pure $ AnnUnit (SrcSpan begin end) res
+
+annotateLocation :: Parser (NExprF NExprLoc) -> Parser NExprLoc
+annotateLocation = (annUnitToAnn <$>) . annotateLocation1
+
+annotateNamedLocation :: String -> Parser (NExprF NExprLoc) -> Parser NExprLoc
+annotateNamedLocation name = annotateLocation . label name
+
+
+-- ** Grammar
+
+reservedNames :: HashSet VarName
+reservedNames =
+  HashSet.fromList
+    ["let", "in", "if", "then", "else", "assert", "with", "rec", "inherit"]
+
+reservedEnd :: Char -> Bool
+reservedEnd x =
+  isSpace x || (`elem` ("{([})];:.\"'," :: String)) x
+{-# inline reservedEnd #-}
+
+reserved :: Text -> Parser ()
+reserved n =
+  lexeme $ try $ chunk n *> lookAhead (void (satisfy reservedEnd) <|> eof)
+
+exprAfterP :: Parser a -> Parser NExprLoc
+exprAfterP p = p *> nixExpr
+
+exprAfterSymbol :: Char -> Parser NExprLoc
+exprAfterSymbol p = exprAfterP $ symbol p
+
+exprAfterReservedWord :: Text -> Parser NExprLoc
+exprAfterReservedWord word = exprAfterP $ reserved word
+
+-- | A literal copy of @megaparsec@ one but with addition of the @\r@ for Windows EOL case (@\r\n@).
+-- Overall, parser should simply @\r\n -> \n@.
+skipLineComment' :: Tokens Text -> Parser ()
+skipLineComment' prefix =
+  chunk prefix *> void (takeWhileP (pure "character") $ \x -> x /= '\n' && x /= '\r')
+
+whiteSpace :: Parser ()
+whiteSpace =
+  do
+    put =<< getSourcePos
+    Lexer.space space1 lineCmnt blockCmnt
+ where
+  lineCmnt  = skipLineComment' "#"
+  blockCmnt = Lexer.skipBlockComment "/*" "*/"
+
+-- | Lexeme is a unit of the language.
+-- Convention is that after lexeme an arbitrary amount of empty entities (space, comments, line breaks) are allowed.
+-- This lexeme definition just skips over superflous @megaparsec: lexeme@ abstraction.
+lexeme :: Parser a -> Parser a
+lexeme p = p <* whiteSpace
+
+symbol :: Char -> Parser Char
+symbol = lexeme . char
+
+symbols :: Text -> Parser Text
+symbols = lexeme . chunk
+
+-- We restrict the type of 'parens' and 'brackets' here because if they were to
+-- take a @Parser NExprLoc@ argument they would parse additional text which
+-- wouldn't be captured in the source location annotation.
+--
+-- Braces and angles in hnix don't enclose a single expression so this type
+-- restriction would not be useful.
+parens :: Parser (NExprF f) -> Parser (NExprF f)
+parens   = on between symbol '(' ')'
+
+braces :: Parser a -> Parser a
+braces   = on between symbol '{' '}'
+
+brackets :: Parser (NExprF f) -> Parser (NExprF f)
+brackets = on between symbol '[' ']'
+
+antiquotedIsHungryForTrailingSpaces :: Bool -> Parser (Antiquoted v NExprLoc)
+antiquotedIsHungryForTrailingSpaces hungry = Antiquoted <$> (antiStart *> nixExpr <* antiEnd)
+ where
+  antiStart :: Parser Text
+  antiStart = label "${" $ symbols "${"
+
+  antiEnd :: Parser Char
+  antiEnd = label "}" $
+    bool
+      id
+      lexeme
+      hungry
+      (char '}')
+
+antiquotedLexeme :: Parser (Antiquoted v NExprLoc)
+antiquotedLexeme = antiquotedIsHungryForTrailingSpaces True
+
+antiquoted :: Parser (Antiquoted v NExprLoc)
+antiquoted = antiquotedIsHungryForTrailingSpaces False
+
+---------------------------------------------------------------------------------
+
+-- * Parser parts
+
+-- ** Constrants
+
+nixNull :: Parser NExprLoc
+nixNull =
+  annotateNamedLocation "null" $
+    mkNullF <$ reserved "null"
+
+nixBool :: Parser NExprLoc
+nixBool =
+  annotateNamedLocation "bool" $
+    on (<|>) lmkBool (True, "true") (False, "false")
+ where
+  lmkBool (b, txt) = mkBoolF b <$ reserved txt
+
+integer :: Parser Integer
+integer = lexeme Lexer.decimal
+
+nixInt :: Parser NExprLoc
+nixInt =
+  annotateNamedLocation "integer" $
+    mkIntF <$> integer
+
+float :: Parser Double
+float = lexeme Lexer.float
+
+nixFloat :: Parser NExprLoc
+nixFloat =
+  annotateNamedLocation "float" $
+    try $
+      mkFloatF . realToFrac <$> float
+
+nixUri :: Parser NExprLoc
+nixUri =
+  lexeme $
+    annotateLocation $
+      try $
+        do
+          start    <- letterChar
+          protocol <-
+            takeWhileP mempty $
+              \ x ->
+                isAlphanumeric x
+                || (`elem` ("+-." :: String)) x
+          _       <- single ':'
+          address <-
+            takeWhile1P mempty $
+                \ x ->
+                  isAlphanumeric x
+                  || (`elem` ("%/?:@&=+$,-_.!~*'" :: String)) x
+          pure . NStr . DoubleQuoted . one . Plain $ start `Text.cons` protocol <> ":" <> address
+
+
+-- ** Strings
+
+nixAntiquoted :: Parser a -> Parser (Antiquoted a NExprLoc)
+nixAntiquoted p =
+  label "anti-quotation" $
+    antiquotedLexeme
+    <|> Plain <$> p
+
+nixString' :: Parser (NString NExprLoc)
+nixString' = label "string" $ lexeme $ doubleQuoted <|> indented
+ where
+  doubleQuoted :: Parser (NString NExprLoc)
+  doubleQuoted =
+    label "double quoted string" $
+      DoubleQuoted . removeEmptyPlains . mergePlain <$>
+        inQuotationMarks (many $ stringChar quotationMark (void $ char '\\') doubleEscape)
+   where
+    inQuotationMarks :: Parser a -> Parser a
+    inQuotationMarks expr = quotationMark *> expr <* quotationMark
+
+    quotationMark :: Parser ()
+    quotationMark = void $ char '"'
+
+    doubleEscape :: Parser (Antiquoted Text r)
+    doubleEscape = Plain . one <$> (char '\\' *> escapeCode)
+
+  indented :: Parser (NString NExprLoc)
+  indented =
+    label "indented string" $
+      stripIndent <$>
+        inIndentedQuotation (many $ join stringChar indentedQuotationMark indentedEscape)
+   where
+    indentedEscape :: Parser (Antiquoted Text r)
+    indentedEscape =
+      try $
+        do
+          indentedQuotationMark
+          (Plain <$> ("''" <$ char '\'' <|> "$" <$ char '$'))
+            <|>
+              do
+                _ <- char '\\'
+                c <- escapeCode
+
+                pure $
+                  bool
+                    EscapedNewline
+                    (Plain $ one c)
+                    (c /= '\n')
+
+    inIndentedQuotation :: Parser a -> Parser a
+    inIndentedQuotation expr = indentedQuotationMark *> expr <* indentedQuotationMark
+
+    indentedQuotationMark :: Parser ()
+    indentedQuotationMark = label "\"''\"" . void $ chunk "''"
+
+  stringChar
+    :: Parser ()
+    -> Parser ()
+    -> Parser (Antiquoted Text NExprLoc)
+    -> Parser (Antiquoted Text NExprLoc)
+  stringChar end escStart esc =
+    antiquoted
+    <|> Plain . one <$> char '$'
+    <|> esc
+    <|> Plain . fromString <$> some plainChar
+   where
+    plainChar :: Parser Char
+    plainChar =
+      notFollowedBy (end <|> void (char '$') <|> escStart) *> anySingle
+
+  escapeCode :: Parser Char
+  escapeCode =
+    msum
+      [ c <$ char e | (c, e) <- escapeCodes ]
+    <|> anySingle
+
+nixString :: Parser NExprLoc
+nixString = annNStr <$> annotateLocation1 nixString'
+
+
+-- ** Names (variables aka symbols)
+
+identifier :: Parser VarName
+identifier =
+  lexeme $
+    try $
+      do
+        (coerce -> iD) <-
+          liftA2 Text.cons
+            (satisfy (\x -> isAlpha x || x == '_'))
+            (takeWhileP mempty identLetter)
+        guard $ not $ iD `HashSet.member` reservedNames
+        pure iD
+ where
+  identLetter x = isAlphanumeric x || x == '_' || x == '\'' || x == '-'
+
+nixSym :: Parser NExprLoc
+nixSym = annotateLocation $ mkSymF <$> coerce identifier
+
+
+-- ** ( ) parens
+
+-- | 'nixExpr' returns an expression annotated with a source position,
+-- however this position doesn't include the parsed parentheses, so remove the
+-- "inner" location annotateion and annotate again, including the parentheses.
+nixParens :: Parser NExprLoc
+nixParens =
+  annotateNamedLocation "parens" $
+    parens $ stripAnnF . unFix <$> nixExpr
+
+
+-- ** [ ] list
+
+nixList :: Parser NExprLoc
+nixList =
+  annotateNamedLocation "list" $
+    brackets $ NList <$> many nixTerm
+
+
+-- ** { } set
+
+nixBinders :: Parser [Binding NExprLoc]
+nixBinders = (inherit <|> namedVar) `endBy` symbol ';' where
+  inherit =
+    do
+      -- We can't use 'reserved' here because it would consume the whitespace
+      -- after the keyword, which is not exactly the semantics of C++ Nix.
+      try $ chunk "inherit" *> lookAhead (void $ satisfy reservedEnd)
+      p <- getSourcePos
+      x <- whiteSpace *> optional scope
+      label "inherited binding" $
+        liftA2 (Inherit x)
+          (many identifier)
+          (pure p)
+  namedVar =
+    do
+      p <- getSourcePos
+      label "variable binding" $
+        liftA3 NamedVar
+          (annotated <$> nixSelector)
+          (exprAfterSymbol '=')
+          (pure p)
+  scope = label "inherit scope" nixParens
+
+nixSet :: Parser NExprLoc
+nixSet =
+  annotateNamedLocation "set" $
+    isRec <*> braces nixBinders
+ where
+  isRec =
+    label "recursive set" (reserved "rec" $> NSet Recursive)
+    <|> pure (NSet mempty)
+
+-- ** /x/y/z literal Path
+
+pathChar :: Char -> Bool
+pathChar x =
+  isAlphanumeric x || (`elem` ("._-+~" :: String)) x
+
+slash :: Parser Char
+slash =
+  label "slash " $
+    try $
+      char '/' <* notFollowedBy (satisfy $ \x -> x == '/' || x == '*' || isSpace x)
+
+pathStr :: Parser Path
+pathStr =
+  lexeme $ coerce . toString <$>
+    liftA2 (<>)
+      (takeWhileP mempty pathChar)
+      (Text.concat <$>
+        some
+          (liftA2 Text.cons
+            slash
+            (takeWhile1P mempty pathChar)
+          )
+      )
+
+nixPath :: Parser NExprLoc
+nixPath =
+  annotateNamedLocation "path" $
+    try $ mkPathF False <$> coerce pathStr
+
+
+-- ** <<x>> environment path
+
+-- | A path surrounded by angle brackets, indicating that it should be
+-- looked up in the NIX_PATH environment variable at evaluation.
+nixSearchPath :: Parser NExprLoc
+nixSearchPath =
+  annotateNamedLocation "spath" $
+    mkPathF True <$> try (lexeme $ char '<' *> many (satisfy pathChar <|> slash) <* char '>')
+
+
+-- ** Operators
+
+data NSpecialOp = NHasAttrOp | NSelectOp
+  deriving (Eq, Ord, Generic, Typeable, Data, Show, NFData)
+
+data NAssoc = NAssocNone | NAssocLeft | NAssocRight
+  deriving (Eq, Ord, Generic, Typeable, Data, Show, NFData)
+
+data NOperatorDef
+  = NUnaryDef          NUnaryOp   Text
+  | NBinaryDef  NAssoc NBinaryOp  Text
+  | NSpecialDef NAssoc NSpecialOp Text
+  deriving (Eq, Ord, Generic, Typeable, Data, Show, NFData)
+
+manyUnaryOp :: MonadPlus f => f (a -> a) -> f (a -> a)
+manyUnaryOp f = foldr1 (.) <$> some f
+
+operator :: Text -> Parser Text
+operator op =
+  case op of
+    c@"-" -> c `without` '>'
+    c@"/" -> c `without` '/'
+    c@"<" -> c `without` '='
+    c@">" -> c `without` '='
+    n   -> symbols n
+ where
+  without :: Text -> Char -> Parser Text
+  without opChar noNextChar =
+    lexeme . try $ chunk opChar <* notFollowedBy (char noNextChar)
+
+opWithLoc :: (AnnUnit SrcSpan o -> a) -> o -> Text -> Parser a
+opWithLoc f op name =
+  do
+    AnnUnit ann _ <-
+      annotateLocation1 $
+        operator name
+
+    pure . f $ AnnUnit ann op
+
+binary
+  :: NAssoc
+  -> (Parser (NExprLoc -> NExprLoc -> NExprLoc) -> b)
+  -> NBinaryOp
+  -> Text
+  -> (NOperatorDef, b)
+binary assoc fixity op name =
+  (NBinaryDef assoc op name, fixity $ opWithLoc annNBinary op name)
+
+binaryN, binaryL, binaryR :: NBinaryOp -> Text -> (NOperatorDef, Operator Parser NExprLoc)
+binaryN =
+  binary NAssocNone InfixN
+binaryL =
+  binary NAssocLeft InfixL
+binaryR =
+  binary NAssocRight InfixR
+
+prefix :: NUnaryOp -> Text -> (NOperatorDef, Operator Parser NExprLoc)
+prefix op name =
+  (NUnaryDef op name, Prefix $ manyUnaryOp $ opWithLoc annNUnary op name)
+-- postfix name op = (NUnaryDef name op,
+--                    Postfix (opWithLoc name op annNUnary))
+
+nixOperators
+  :: Parser (AnnUnit SrcSpan (NAttrPath NExprLoc))
+  -> [[ ( NOperatorDef
+       , Operator Parser NExprLoc
+       )
+    ]]
+nixOperators selector =
+  [ -- This is not parsed here, even though technically it's part of the
+    -- expression table. The problem is that in some cases, such as list
+    -- membership, it's also a term. And since terms are effectively the
+    -- highest precedence entities parsed by the expression parser, it ends up
+    -- working out that we parse them as a kind of "meta-term".
+
+    -- {-  1 -}
+    -- [ ( NSpecialDef "." NSelectOp NAssocLeft
+    --   , Postfix $
+    --       do
+    --         sel <- seldot *> selector
+    --         mor <- optional (reserved "or" *> term)
+    --         pure $ \x -> annNSelect x sel mor)
+    -- ]
+
+    {-  2 -}
+    one
+      ( NBinaryDef NAssocLeft NApp " "
+      ,
+        -- Thanks to Brent Yorgey for showing me this trick!
+        InfixL $ annNApp <$ symbols mempty
+      )
+  , {-  3 -}
+    one $ prefix  NNeg "-"
+  , {-  4 -}
+    one
+      ( NSpecialDef NAssocLeft NHasAttrOp "?"
+      , Postfix $ symbol '?' *> (flip annNHasAttr <$> selector)
+      )
+  , {-  5 -}
+    one $ binaryR NConcat "++"
+  , {-  6 -}
+    [ binaryL NMult "*"
+    , binaryL NDiv  "/"
+    ]
+  , {-  7 -}
+    [ binaryL NPlus "+"
+    , binaryL NMinus "-"
+    ]
+  , {-  8 -}
+    one $ prefix  NNot "!"
+  , {-  9 -}
+    one $ binaryR NUpdate "//"
+  , {- 10 -}
+    [ binaryL NLt "<"
+    , binaryL NGt ">"
+    , binaryL NLte "<="
+    , binaryL NGte ">="
+    ]
+  , {- 11 -}
+    [ binaryN NEq "=="
+    , binaryN NNEq "!="
+    ]
+  , {- 12 -}
+    one $ binaryL NAnd "&&"
+  , {- 13 -}
+    one $ binaryL NOr "||"
+  , {- 14 -}
+    one $ binaryR NImpl "->"
+  ]
+
+--  2021-08-10: NOTE:
+--  All this is a sidecar:
+--  * This type
+--  * detectPrecedence
+--  * getUnaryOperation
+--  * getBinaryOperation
+--  * getSpecialOperation
+--  can reduced in favour of adding precedence field into @NOperatorDef@.
+-- details: https://github.com/haskell-nix/hnix/issues/982
+data OperatorInfo = OperatorInfo
+  { precedence    :: Int
+  , associativity :: NAssoc
+  , operatorName  :: Text
+  } deriving (Eq, Ord, Generic, Typeable, Data, Show)
+
+detectPrecedence
+  :: Ord a
+  => ( Int
+    -> (NOperatorDef, Operator Parser NExprLoc)
+    -> [(a, OperatorInfo)]
+    )
+  -> a
+  -> OperatorInfo
+detectPrecedence spec = (mapOfOpWithPrecedence Map.!)
+ where
+  mapOfOpWithPrecedence =
+    Map.fromList $
+      fold $
+        zipWith
+          (foldMap . spec)
+          [1 ..]
+          l
+   where
+    l :: [[(NOperatorDef, Operator Parser NExprLoc)]]
+    l = nixOperators $ fail "unused"
+
+getUnaryOperator :: NUnaryOp -> OperatorInfo
+getUnaryOperator = detectPrecedence spec
+ where
+  spec :: Int -> (NOperatorDef, b) -> [(NUnaryOp, OperatorInfo)]
+  spec i =
+    \case
+      (NUnaryDef op name, _) -> one (op, OperatorInfo i NAssocNone name)
+      _                      -> mempty
+
+getBinaryOperator :: NBinaryOp -> OperatorInfo
+getBinaryOperator = detectPrecedence spec
+ where
+  spec :: Int -> (NOperatorDef, b) -> [(NBinaryOp, OperatorInfo)]
+  spec i =
+    \case
+      (NBinaryDef assoc op name, _) -> one (op, OperatorInfo i assoc name)
+      _                             -> mempty
+
+getSpecialOperator :: NSpecialOp -> OperatorInfo
+getSpecialOperator NSelectOp = OperatorInfo 1 NAssocLeft "."
+getSpecialOperator o         = detectPrecedence spec o
+ where
+  spec :: Int -> (NOperatorDef, b) -> [(NSpecialOp, OperatorInfo)]
+  spec i =
+      \case
+        (NSpecialDef assoc op name, _) -> one (op, OperatorInfo i assoc name)
+        _                              -> mempty
+
+-- ** x: y lambda function
+
+-- | Gets all of the arguments for a function.
+argExpr :: Parser (Params NExprLoc)
+argExpr =
+  msum
+    [ atLeft
+    , onlyname
+    , atRight
+    ]
+  <* symbol ':'
+ where
+  -- An argument not in curly braces. There's some potential ambiguity
+  -- in the case of, for example `x:y`. Is it a lambda function `x: y`, or
+  -- a URI `x:y`? Nix syntax says it's the latter. So we need to fail if
+  -- there's a valid URI parse here.
+  onlyname =
+    msum
+      [ nixUri *> unexpected (Label $ fromList "valid uri" )
+      , Param <$> identifier
+      ]
+
+  -- Parameters named by an identifier on the left (`args @ {x, y}`)
+  atLeft =
+    try $
+      do
+        name             <- identifier <* symbol '@'
+        (pset, variadic) <- params
+        pure $ ParamSet (pure name) variadic pset
+
+  -- Parameters named by an identifier on the right, or none (`{x, y} @ args`)
+  atRight =
+    do
+      (pset, variadic) <- params
+      name             <- optional $ symbol '@' *> identifier
+      pure $ ParamSet name variadic pset
+
+  -- Return the parameters set.
+  params = braces getParams
+
+  -- Collects the parameters within curly braces. Returns the parameters and
+  -- an flag indication if the parameters are variadic.
+  getParams = go mempty
+   where
+    -- Attempt to parse `...`. If this succeeds, stop and return True.
+    -- Otherwise, attempt to parse an argument, optionally with a
+    -- default. If this fails, then return what has been accumulated
+    -- so far.
+    go acc = ((acc, Variadic) <$ symbols "...") <|> getMore
+     where
+      getMore :: Parser ([(VarName, Maybe NExprLoc)], Variadic)
+      getMore =
+        -- Could be nothing, in which just return what we have so far.
+        option (acc, mempty) $
+          do
+            -- Get an argument name and an optional default.
+            pair <-
+              liftA2 (,)
+                identifier
+                (optional $ exprAfterSymbol '?')
+
+            let args = acc <> one pair
+
+            -- Either return this, or attempt to get a comma and restart.
+            option (args, mempty) $ symbol ',' *> go args
+
+nixLambda :: Parser NExprLoc
+nixLambda =
+  liftA2 annNAbs
+    (annotateLocation1 $ try argExpr)
+    nixExpr
+
+
+-- ** let expression
+
+nixLet :: Parser NExprLoc
+nixLet =
+  annotateNamedLocation "let block" $
+    reserved "let" *> (letBody <|> letBinders)
+ where
+  letBinders =
+    liftA2 NLet
+      nixBinders
+      (exprAfterReservedWord "in")
+  -- Let expressions `let {..., body = ...}' are just desugared
+  -- into `(rec {..., body = ...}).body'.
+  letBody    = (\x -> NSelect Nothing x (one $ StaticKey "body")) <$> aset
+  aset       = annotateLocation $ NSet Recursive <$> braces nixBinders
+
+-- ** if then else
+
+nixIf :: Parser NExprLoc
+nixIf =
+  annotateNamedLocation "if" $
+    liftA3 NIf
+      (reserved "if"   *> nixExprAlgebra)
+      (exprAfterReservedWord "then")
+      (exprAfterReservedWord "else")
+
+-- ** with
+
+nixWith :: Parser NExprLoc
+nixWith =
+  annotateNamedLocation "with" $
+    liftA2 NWith
+      (exprAfterReservedWord "with")
+      (exprAfterSymbol       ';'   )
+
+
+-- ** assert
+
+nixAssert :: Parser NExprLoc
+nixAssert =
+  annotateNamedLocation "assert" $
+    liftA2 NAssert
+      (exprAfterReservedWord "assert")
+      (exprAfterSymbol       ';'     )
+
+-- ** . - reference (selector) into attr
+
+selDot :: Parser ()
+selDot = label "." $ try (symbol '.' *> notFollowedBy nixPath)
+
+keyName :: Parser (NKeyName NExprLoc)
+keyName = dynamicKey <|> staticKey
+ where
+  staticKey  = StaticKey <$> identifier
+  dynamicKey = DynamicKey <$> nixAntiquoted nixString'
+
+nixSelector :: Parser (AnnUnit SrcSpan (NAttrPath NExprLoc))
+nixSelector =
+  annotateLocation1 $
+    do
+      (x : xs) <- keyName `sepBy1` selDot
+      pure $ x :| xs
+
+nixSelect :: Parser NExprLoc -> Parser NExprLoc
+nixSelect term =
+  do
+    res <-
+      liftA2 build
+        term
+        (optional $
+          liftA2 (,)
+            (selDot *> nixSelector)
+            (optional $ reserved "or" *> nixTerm)
+        )
+    continues <- optional $ lookAhead selDot
+
+    maybe
+      id
+      (const nixSelect)
+      continues
+      (pure res)
+ where
+  build
+    :: NExprLoc
+    -> Maybe
+      ( AnnUnit SrcSpan (NAttrPath NExprLoc)
+      , Maybe NExprLoc
+      )
+    -> NExprLoc
+  build t =
+    maybe
+      t
+      (\ (a, m) -> (`annNSelect` t) m a)
+
+
+-- ** _ - syntax hole
+
+nixSynHole :: Parser NExprLoc
+nixSynHole = annotateLocation $ mkSynHoleF <$> coerce (char '^' *> identifier)
+
+
+-- ** Expr & its constituents (Language term, expr algebra)
+
+nixTerm :: Parser NExprLoc
+nixTerm =
+  do
+    c <- try . lookAhead . satisfy $
+      \x -> (`elem` ("({[</\"'^" :: String)) x || pathChar x
+    case c of
+      '('  -> nixSelect nixParens
+      '{'  -> nixSelect nixSet
+      '['  -> nixList
+      '<'  -> nixSearchPath
+      '/'  -> nixPath
+      '"'  -> nixString
+      '\'' -> nixString
+      '^'  -> nixSynHole
+      _ ->
+        msum
+          $  [ nixSelect nixSet | c == 'r' ]
+          <> [ nixPath | pathChar c ]
+          <> if isDigit c
+              then [ nixFloat, nixInt ]
+              else
+                [ nixUri | isAlpha c ]
+                <> [ nixBool | c == 't' || c == 'f' ]
+                <> [ nixNull | c == 'n' ]
+                <> one (nixSelect nixSym)
+
+-- | Nix expression algebra parser.
+-- "Expression algebra" is to explain @megaparsec@ use of the term "Expression" (parser for language algebraic coperators without any statements (without @let@ etc.)), which is essentially an algebra inside the language.
+nixExprAlgebra :: Parser NExprLoc
+nixExprAlgebra =
+  makeExprParser
+    nixTerm
+    (snd <<$>>
+      nixOperators nixSelector
+    )
+
+nixExpr :: Parser NExprLoc
+nixExpr = keywords <|> nixLambda <|> nixExprAlgebra
+ where
+  keywords = nixLet <|> nixIf <|> nixAssert <|> nixWith
+
+
+-- * Parse
+
+type Result a = Either (Doc Void) a
+
+parseFromFileEx :: MonadFile m => Parser a -> Path -> m (Result a)
+parseFromFileEx parser file =
+  do
+    input <- liftIO $ readFile file
+
+    pure $
+      either
+        (Left . pretty . errorBundlePretty)
+        pure
+        $ (`evalState` initialPos (coerce file)) $ runParserT parser (coerce file) input
+
+parseFromText :: Parser a -> Text -> Result a
+parseFromText parser input =
+  let stub = "<string>" in
+  either
+    (Left . pretty . errorBundlePretty)
+    pure
+    $ (`evalState` initialPos stub) $ (`runParserT` stub) parser input
+
+fullContent :: Parser NExprLoc
+fullContent = whiteSpace *> nixExpr <* eof
+
+parseNixFile' :: MonadFile m => (Parser NExprLoc -> Parser a) -> Path -> m (Result a)
+parseNixFile' f =
+  parseFromFileEx $ f fullContent
+
+parseNixFile :: MonadFile m => Path -> m (Result NExpr)
+parseNixFile =
+  parseNixFile' (stripAnnotation <$>)
+
+parseNixFileLoc :: MonadFile m => Path -> m (Result NExprLoc)
+parseNixFileLoc =
+  parseNixFile' id
+
+parseNixText' :: (Parser NExprLoc -> Parser a) -> Text -> Result a
+parseNixText' f =
+  parseFromText $ f fullContent
+
+parseNixText :: Text -> Result NExpr
+parseNixText =
+  parseNixText' (stripAnnotation <$>)
+
+parseNixTextLoc :: Text -> Result NExprLoc
+parseNixTextLoc =
+  parseNixText' id
+
diff --git a/src/Nix/Pretty.hs b/src/Nix/Pretty.hs
--- a/src/Nix/Pretty.hs
+++ b/src/Nix/Pretty.hs
@@ -1,14 +1,12 @@
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE ViewPatterns #-}
+{-# language CPP #-}
+{-# language AllowAmbiguousTypes #-}
 
-{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
+{-# options_ghc -fno-warn-name-shadowing #-}
 
 
 module Nix.Pretty where
 
 import           Prelude                  hiding ( toList, group )
-import           Nix.Utils
 import           Control.Monad.Free             ( Free(Free) )
 import           Data.Fix                       ( Fix(..)
                                                 , foldFix )
@@ -23,7 +21,8 @@
 import           Prettyprinter           hiding ( list )
 import           Nix.Atoms
 import           Nix.Cited
-import           Nix.Expr
+import           Nix.Expr.Types
+import           Nix.Expr.Types.Annotated
 import           Nix.Expr.Strings
 import           Nix.Normal
 import           Nix.Parser
@@ -101,80 +100,90 @@
     (wasPath sub)
 
 prettyString :: NString (NixDoc ann) -> Doc ann
-prettyString (DoubleQuoted parts) = "\"" <> (mconcat . fmap prettyPart $ parts) <> "\""
+prettyString (DoubleQuoted parts) = "\"" <> foldMap prettyPart parts <> "\""
  where
   -- It serializes Text -> String, because the helper code is done for String,
   -- please, can someone break that code.
-  prettyPart (Plain t)      = pretty . concatMap escape . toString $ t
+  prettyPart (Plain t)      = pretty . foldMap escape . toString $ t
   prettyPart EscapedNewline = "''\\n"
   prettyPart (Antiquoted r) = "${" <> withoutParens r <> "}"
   escape '"' = "\\\""
   escape x   =
     maybe
-      [x]
-      (('\\' :) . (: mempty))
+      (one x)
+      (('\\' :) . one)
       (toEscapeCode x)
 prettyString (Indented _ parts) = group $ nest 2 $ vcat
   ["''", content, "''"]
  where
   content = vsep . fmap prettyLine . stripLastIfEmpty . splitLines $ parts
-  stripLastIfEmpty = reverse . f . reverse   where
-    f ([Plain t] : xs) | Text.null (strip t) = xs
-    f xs = xs
+  stripLastIfEmpty :: [[Antiquoted Text r]] -> [[Antiquoted Text r]]
+  stripLastIfEmpty = filter flt
+   where
+    flt :: [Antiquoted Text r] -> Bool
+    flt [Plain t] | Text.null (strip t) = False
+    flt _ = True
+
   prettyLine = hcat . fmap prettyPart
   prettyPart (Plain t) =
     pretty . replace "${" "''${" . replace "''" "'''" $ t
   prettyPart EscapedNewline = "\\n"
   prettyPart (Antiquoted r) = "${" <> withoutParens r <> "}"
 
+prettyVarName :: VarName -> Doc ann
+prettyVarName = pretty @Text . coerce
+
 prettyParams :: Params (NixDoc ann) -> Doc ann
-prettyParams (Param n           ) = pretty n
-prettyParams (ParamSet s v mname) = prettyParamSet s v <>
-  maybe
-    mempty
-    (\ name ->
-       bool
-         mempty
-         ("@" <> pretty name)
-         (not (Text.null name))
-    )
-    mname
+prettyParams (Param n           ) = prettyVarName n
+prettyParams (ParamSet mname variadic pset) =
+  prettyParamSet variadic pset <>
+     toDoc `whenJust` mname
+ where
+  toDoc :: VarName -> Doc ann
+  toDoc (coerce -> name) =
+    ("@" <> pretty name) `whenFalse` Text.null name
 
-prettyParamSet :: ParamSet (NixDoc ann) -> Bool -> Doc ann
-prettyParamSet args var =
+prettyParamSet :: Variadic -> ParamSet (NixDoc ann) -> Doc ann
+prettyParamSet variadic args =
   encloseSep
     "{ "
     (align " }")
     sep
-    (fmap prettySetArg args <> prettyVariadic)
+    (fmap prettySetArg args <> one "..." `whenTrue` (variadic == Variadic))
  where
   prettySetArg (n, maybeDef) =
     maybe
-      (pretty n)
-      (\x -> pretty n <> " ? " <> withoutParens x)
+      varName
+      (\x -> varName <> " ? " <> withoutParens x)
       maybeDef
-  prettyVariadic = [ "..." | var ]
+   where
+    varName = prettyVarName n
   sep            = align ", "
 
 prettyBind :: Binding (NixDoc ann) -> Doc ann
 prettyBind (NamedVar n v _p) =
   prettySelector n <> " = " <> withoutParens v <> ";"
 prettyBind (Inherit s ns _p) =
-  "inherit " <> scope <> align (fillSep $ prettyKeyName <$> ns) <> ";"
+  "inherit " <> scope <> align (fillSep $ prettyVarName <$> ns) <> ";"
   where
     scope =
-      maybe
-        mempty
-        ((<> " ") . parens . withoutParens)
-        s
+      ((<> " ") . parens . withoutParens) `whenJust` s
 
 prettyKeyName :: NKeyName (NixDoc ann) -> Doc ann
-prettyKeyName (StaticKey "") = "\"\""
-prettyKeyName (StaticKey key) | HashSet.member key reservedNames = "\"" <> pretty key <> "\""
-prettyKeyName (StaticKey  key) = pretty key
+prettyKeyName (StaticKey key) =
+  bool
+    "\"\""
+    (bool
+      varName
+      ("\"" <> varName <> "\"")
+      (HashSet.member key reservedNames)
+    )
+    (not $ Text.null $ coerce key)
+ where
+  varName = prettyVarName key
 prettyKeyName (DynamicKey key) =
   runAntiquoted
-    (DoubleQuoted [Plain "\n"])
+    (DoubleQuoted $ one $ Plain "\n")
     prettyString
     (\ x -> "${" <> withoutParens x <> "}")
     key
@@ -183,7 +192,7 @@
 prettySelector = hcat . punctuate "." . fmap prettyKeyName . NE.toList
 
 prettyAtom :: NAtom -> NixDoc ann
-prettyAtom atom = simpleExpr $ pretty $ atomText atom
+prettyAtom = simpleExpr . pretty . atomText
 
 prettyNix :: NExpr -> Doc ann
 prettyNix = withoutParens . foldFix exprFNixDoc
@@ -195,16 +204,16 @@
   -> Doc ann
 prettyOriginExpr = withoutParens . go
  where
-  go = exprFNixDoc . stripAnn . fmap render
-
-  render :: Maybe (NValue t f m) -> NixDoc ann
-  render Nothing = simpleExpr "_"
-  render (Just (Free (reverse . citations @m -> p:_))) = go (_originExpr p)
-  render _       = simpleExpr "?"
-    -- render (Just (NValue (citations -> ps))) =
-        -- simpleExpr $ foldr ((\x y -> vsep [x, y]) . parens . indent 2 . withoutParens
-        --                           . go . originExpr)
-        --     mempty (reverse ps)
+  go = exprFNixDoc . stripAnnF . fmap render
+   where
+    render :: Maybe (NValue t f m) -> NixDoc ann
+    render Nothing = simpleExpr "_"
+    render (Just (Free (reverse . citations @m -> p:_))) = go (_originExpr p)
+    render _       = simpleExpr "?"
+      -- render (Just (NValue (citations -> ps))) =
+          -- simpleExpr $ foldr ((\x y -> vsep [x, y]) . parens . indent 2 . withoutParens
+          --                           . go . originExpr)
+          --     mempty (reverse ps)
 
 exprFNixDoc :: NExprF (NixDoc ann) -> NixDoc ann
 exprFNixDoc = \case
@@ -212,9 +221,9 @@
   NStr      str  -> simpleExpr $ prettyString str
   NList xs ->
     prettyContainer "[" (wrapParens appOpNonAssoc) "]" xs
-  NSet NNonRecursive xs ->
+  NSet NonRecursive xs ->
     prettyContainer "{" prettyBind "}" xs
-  NSet NRecursive xs ->
+  NSet Recursive xs ->
     prettyContainer "rec {" prettyBind "}" xs
   NAbs args body ->
     leastPrecedence $
@@ -229,23 +238,26 @@
     mkNixDoc
       opInfo $
       hsep
-        [ wrapParens (f NAssocLeft) r1
+        [ f NAssocLeft r1
         , pretty $ operatorName opInfo
-        , wrapParens (f NAssocRight) r2
+        , f NAssocRight r2
         ]
    where
     opInfo = getBinaryOperator op
+    f :: NAssoc -> NixDoc ann1 -> Doc ann1
     f x =
-      bool
-        opInfo
-        (opInfo { associativity = NAssocNone })
-        (associativity opInfo /= x)
+      wrapParens
+        $ bool
+            opInfo
+            (opInfo { associativity = NAssocNone })
+            (associativity opInfo /= x)
   NUnary op r1 ->
     mkNixDoc
-      opInfo
-      (pretty (operatorName opInfo) <> wrapParens opInfo r1)
-    where opInfo = getUnaryOperator op
-  NSelect r' attr o ->
+      opInfo $
+      pretty (operatorName opInfo) <> wrapParens opInfo r1
+   where
+    opInfo = getUnaryOperator op
+  NSelect o r' attr ->
     maybe
       (mkNixDoc selectOp)
       (const leastPrecedence)
@@ -254,16 +266,13 @@
    where
     r     = mkNixDoc selectOp (wrapParens appOpNonAssoc r')
     ordoc =
-      maybe
-        mempty
-        ((" or " <>) . wrapParens appOpNonAssoc)
-        o
+      ((" or " <>) . wrapParens appOpNonAssoc) `whenJust` o
   NHasAttr r attr ->
     mkNixDoc hasAttrOp (wrapParens hasAttrOp r <> " ? " <> prettySelector attr)
-  NEnvPath     p -> simpleExpr $ pretty ("<" <> p <> ">")
+  NEnvPath     p -> simpleExpr $ pretty @String $ "<" <> coerce p <> ">"
   NLiteralPath p ->
     pathExpr $
-      pretty $
+      pretty @FilePath $ coerce $
         case p of
           "./"  -> "./."
           "../" -> "../."
@@ -272,8 +281,8 @@
             bool
               ("./" <> _txt)
               _txt
-              (any (`isPrefixOf` _txt) ["/", "~/", "./", "../"])
-  NSym name -> simpleExpr $ pretty name
+              (any (`isPrefixOf` coerce _txt) ["/", "~/", "./", "../"])
+  NSym name -> simpleExpr $ prettyVarName name
   NLet binds body ->
     leastPrecedence $
       group $
@@ -295,12 +304,12 @@
     prettyAddScope "with " scope body
   NAssert cond body ->
     prettyAddScope "assert " cond body
-  NSynHole name -> simpleExpr $ pretty ("^" <> name)
+  NSynHole name -> simpleExpr $ pretty @Text ("^" <> coerce name)
  where
   prettyContainer h f t c =
     list
       (simpleExpr (h <> t))
-      (const $ simpleExpr $ group $ nest 2 $ vsep $ [h] <> (f <$> c) <> [t])
+      (const $ simpleExpr $ group $ nest 2 $ vsep $ one h <> (f <$> c) <> one t)
       c
 
   prettyAddScope h c b =
@@ -316,15 +325,15 @@
 
   phi :: NValue' t f m NExpr -> NExprF NExpr
   phi (NVConstant' a     ) = NConstant a
-  phi (NVStr'      ns    ) = NStr $ DoubleQuoted [Plain (stringIgnoreContext ns)]
+  phi (NVStr'      ns    ) = NStr $ DoubleQuoted $ one $ Plain $ stringIgnoreContext ns
   phi (NVList'     l     ) = NList l
-  phi (NVSet'      s    p) = NSet NNonRecursive
-    [ NamedVar (StaticKey k :| mempty) v (fromMaybe nullPos (M.lookup k p))
+  phi (NVSet'      p    s) = NSet mempty
+    [ NamedVar (one $ StaticKey k) v (fromMaybe nullPos $ (`M.lookup` p) k)
     | (k, v) <- toList s
     ]
   phi (NVClosure'  _    _) = NSym "<closure>"
   phi (NVPath'     p     ) = NLiteralPath p
-  phi (NVBuiltin'  name _) = NSym $ "builtins." <> name
+  phi (NVBuiltin'  name _) = NSym $ coerce ((mappend @Text) "builtins.") name
 
 prettyNValue
   :: forall t f m ann . MonadDataContext f m => NValue t f m -> Doc ann
@@ -346,7 +355,7 @@
       fillSep
         [ prettyNVal
         , indent 2 $
-          "(" <> mconcat ("from: " : (prettyOriginExpr . _originExpr <$> ps)) <> ")"
+          "(" <> fold (one "from: " <> (prettyOriginExpr . _originExpr <$> ps)) <> ")"
         ]
     )
     (citations @m @(NValue t f m) v)
@@ -370,34 +379,36 @@
       fillSep
         [ v'
         , indent 2 $
-          "(" <> mconcat ( "thunk from: " : (prettyOriginExpr . _originExpr <$> ps)) <> ")"
+          "(" <> fold (one "thunk from: " <> (prettyOriginExpr . _originExpr <$> ps)) <> ")"
         ]
 
 -- | This function is used only by the testing code.
-printNix :: forall t f m . MonadDataContext f m => NValue t f m -> String
+printNix :: forall t f m . MonadDataContext f m => NValue t f m -> Text
 printNix = iterNValueByDiscardWith thk phi
  where
-  thk = toString thunkStubText
+  thk = thunkStubText
 
-  phi :: NValue' t f m String -> String
-  phi (NVConstant' a ) = toString $ atomText a
+  phi :: NValue' t f m Text -> Text
+  phi (NVConstant' a ) = atomText a
   phi (NVStr'      ns) = show $ stringIgnoreContext ns
-  phi (NVList'     l ) = toString $ "[ " <> unwords (fmap toText l) <> " ]"
-  phi (NVSet' s _) =
+  phi (NVList'     l ) = "[ " <> unwords l <> " ]"
+  phi (NVSet' _ s) =
     "{ " <>
-      concat
-        [ check (toString k) <> " = " <> v <> "; "
-        | (k, v) <- sort $ toList s
+      fold
+        [ check k <> " = " <> v <> "; "
+        | (coerce -> k, v) <- sort $ toList s
         ] <> "}"
    where
-    check :: [Char] -> [Char]
+    check :: Text -> Text
     check v =
       fromMaybe
         v
-        (fmap (surround . show) (readMaybe v :: Maybe Int)
-        <|> fmap (surround . show) (readMaybe v :: Maybe Float)
-        )
-      where surround s = "\"" <> s <> "\""
+        (tryRead @Int <|> tryRead @Float)
+     where
+      surround s = "\"" <> s <> "\""
+
+      tryRead :: forall a . (Read a, Show a) => Maybe Text
+      tryRead = fmap (surround . show) (readMaybe (toString v) :: Maybe a)
   phi NVClosure'{}        = "<<lambda>>"
-  phi (NVPath' fp       ) = fp
-  phi (NVBuiltin' name _) = toString $ "<<builtin " <> name <> ">>"
+  phi (NVPath' fp       ) = fromString $ coerce fp
+  phi (NVBuiltin' name _) = "<<builtin " <> coerce name <> ">>"
diff --git a/src/Nix/Reduce.hs b/src/Nix/Reduce.hs
--- a/src/Nix/Reduce.hs
+++ b/src/Nix/Reduce.hs
@@ -1,13 +1,11 @@
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE AllowAmbiguousTypes #-}
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE PartialTypeSignatures #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE ViewPatterns #-}
+{-# language CPP #-}
+{-# language AllowAmbiguousTypes #-}
+{-# language ConstraintKinds #-}
+{-# language GeneralizedNewtypeDeriving #-}
+{-# language PartialTypeSignatures #-}
+{-# language TypeFamilies #-}
 
-{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
+{-# options_ghc -fno-warn-name-shadowing #-}
 
 
 -- | This module provides a "reducing" expression evaluator, which reduces
@@ -42,7 +40,8 @@
 import qualified Text.Show
 import           Nix.Atoms
 import           Nix.Effects.Basic              ( pathToDefaultNixFile )
-import           Nix.Expr
+import           Nix.Expr.Types
+import           Nix.Expr.Types.Annotated
 import           Nix.Frames
 import           Nix.Options                    ( Options
                                                 , reduceSets
@@ -50,18 +49,16 @@
                                                 )
 import           Nix.Parser
 import           Nix.Scope
-import           Nix.Utils
 import           System.Directory
-import           System.FilePath
 
 newtype Reducer m a = Reducer
     { runReducer ::
         ReaderT
-          ( Maybe FilePath
+          ( Maybe Path
           , Scopes (Reducer m) NExprLoc
           )
           ( StateT
-              ( HashMap FilePath NExprLoc
+              ( HashMap Path NExprLoc
               , HashMap Text Text
               )
             m
@@ -71,8 +68,8 @@
   deriving
     ( Functor, Applicative, Alternative
     , Monad, MonadPlus, MonadFix, MonadIO, MonadFail
-    , MonadReader (Maybe FilePath, Scopes (Reducer m) NExprLoc)
-    , MonadState (HashMap FilePath NExprLoc, HashMap Text Text)
+    , MonadReader (Maybe Path, Scopes (Reducer m) NExprLoc)
+    , MonadState (HashMap Path NExprLoc, HashMap Text Text)
     )
 
 staticImport
@@ -80,59 +77,62 @@
    . ( MonadIO m
      , Scoped NExprLoc m
      , MonadFail m
-     , MonadReader (Maybe FilePath, Scopes m NExprLoc) m
-     , MonadState (HashMap FilePath NExprLoc, HashMap Text Text) m
+     , MonadReader (Maybe Path, Scopes m NExprLoc) m
+     , MonadState (HashMap Path NExprLoc, HashMap Text Text) m
      )
   => SrcSpan
-  -> FilePath
+  -> Path
   -> m NExprLoc
-staticImport pann path = do
-  mfile <- asks fst
-  path  <- liftIO $ pathToDefaultNixFile path
-  path' <- liftIO $ pathToDefaultNixFile =<< canonicalizePath
-    (maybe id ((</>) . takeDirectory) mfile path)
+staticImport pann path =
+  do
+    mfile <- asks fst
+    path'  <- liftIO $ pathToDefaultNixFile path
+    path'' <- liftIO $ pathToDefaultNixFile =<< coerce canonicalizePath
+      (maybe id ((</>) . takeDirectory) mfile path')
 
-  imports <- gets fst
-  maybe
-    (go path')
-    pure
-    (HM.lookup path' imports)
- where
-  go path = do
-    liftIO $ putStrLn $ "Importing file " <> path
+    let
+      importIt :: m NExprLoc
+      importIt = do
+        liftIO $ putStrLn $ "Importing file " <> coerce path''
 
-    eres <- liftIO $ parseNixFileLoc path
-    either
-      (\ err -> fail $ "Parse failed: " <> show err)
-      (\ x -> do
-        let
-          pos  = SourcePos "Reduce.hs" (mkPos 1) (mkPos 1)
-          span = SrcSpan pos pos
-          cur  =
-            NamedVar
-              (StaticKey "__cur_file" :| mempty)
-              (Fix (NLiteralPath_ pann path))
-              pos
-          x' = Fix $ NLet_ span [cur] x
-        modify $ first $ HM.insert path x'
-        local
-          (const (pure path, mempty)) $
-          do
-            x'' <- foldFix reduce x'
-            modify $ first $ HM.insert path x''
-            pure x''
-      )
-      eres
+        eres <- liftIO $ parseNixFileLoc path''
+        either
+          (\ err -> fail $ "Parse failed: " <> show err)
+          (\ x -> do
+            let
+              pos  = join (SourcePos "Reduce.hs") $ mkPos 1
+              span = join SrcSpan pos
+              cur  =
+                NamedVar
+                  (one $ StaticKey "__cur_file")
+                  (NLiteralPathAnn pann path'')
+                  pos
+              x' = NLetAnn span (one cur) x
+            modify $ first $ HM.insert path'' x'
+            local
+              (const (pure path'', mempty)) $
+              do
+                x'' <- foldFix reduce x'
+                modify $ first $ HM.insert path'' x''
+                pure x''
+          )
+          eres
 
+    imports <- gets fst
+    maybe
+      importIt
+      pure
+      (HM.lookup path'' imports)
+
 -- gatherNames :: NExprLoc -> HashSet VarName
 -- gatherNames = foldFix $ \case
---     NSym_ _ var -> S.singleton var
---     Compose (Ann _ x) -> fold x
+--     NSymAnnF _ var -> S.singleton var
+--     AnnF _ x -> fold x
 
 reduceExpr
-  :: (MonadIO m, MonadFail m) => Maybe FilePath -> NExprLoc -> m NExprLoc
+  :: (MonadIO m, MonadFail m) => Maybe Path -> NExprLoc -> m NExprLoc
 reduceExpr mpath expr =
-  (`evalStateT` (mempty, mempty))
+  (`evalStateT` mempty)
     . (`runReaderT` (mpath, mempty))
     . runReducer
     $ foldFix reduce expr
@@ -142,26 +142,26 @@
    . ( MonadIO m
      , Scoped NExprLoc m
      , MonadFail m
-     , MonadReader (Maybe FilePath, Scopes m NExprLoc) m
-     , MonadState (HashMap FilePath NExprLoc, HashMap Text Text) m
+     , MonadReader (Maybe Path, Scopes m NExprLoc) m
+     , MonadState (HashMap Path NExprLoc, HashMap Text Text) m
      )
   => NExprLocF (m NExprLoc)
   -> m NExprLoc
 
 -- | Reduce the variable to its value if defined.
 --   Leave it as it is otherwise.
-reduce (NSym_ ann var) =
-  fromMaybe (Fix (NSym_ ann var)) <$> lookupVar var
+reduce (NSymAnnF ann var) =
+  fromMaybe (NSymAnn ann var) <$> lookupVar var
 
 -- | Reduce binary and integer negation.
-reduce (NUnary_ uann op arg) =
+reduce (NUnaryAnnF uann op arg) =
   do
     x <- arg
-    pure $ Fix $
+    pure $
       case (op, x) of
-        (NNeg, Fix (NConstant_ cann (NInt  n))) -> NConstant_ cann $ NInt $ negate n
-        (NNot, Fix (NConstant_ cann (NBool b))) -> NConstant_ cann $ NBool $ not b
-        _                                       -> NUnary_    uann op x
+        (NNeg, NConstantAnn cann (NInt  n)) -> NConstantAnn cann $ NInt $ negate n
+        (NNot, NConstantAnn cann (NBool b)) -> NConstantAnn cann $ NBool $ not b
+        _                                   -> NUnaryAnn    uann op x
 
 -- | Reduce function applications.
 --
@@ -169,32 +169,32 @@
 --
 --     * Reduce a lambda function by adding its name to the local
 --       scope and recursively reducing its body.
-reduce (NBinary_ bann NApp fun arg) = fun >>= \case
-  f@(Fix (NSym_ _ "import")) ->
+reduce (NBinaryAnnF bann NApp fun arg) = fun >>= \case
+  f@(NSymAnn _ "import") ->
     (\case
-        -- Fix (NEnvPath_     pann origPath) -> staticImport pann origPath
-      Fix (NLiteralPath_ pann origPath) -> staticImport pann origPath
-      v -> pure $ Fix $ NBinary_ bann NApp f v
+        -- NEnvPathAnn     pann origPath -> staticImport pann origPath
+      NLiteralPathAnn pann origPath -> staticImport pann origPath
+      v -> pure $ NBinaryAnn bann NApp f v
     ) =<< arg
 
-  Fix (NAbs_ _ (Param name) body) ->
+  NAbsAnn _ (Param name) body ->
     do
       x <- arg
       pushScope
-        (HM.singleton name x)
+        (coerce $ HM.singleton name x)
         (foldFix reduce body)
 
-  f -> Fix . NBinary_ bann NApp f <$> arg
+  f -> NBinaryAnn bann NApp f <$> arg
 
 -- | Reduce an integer addition to its result.
-reduce (NBinary_ bann op larg rarg) =
+reduce (NBinaryAnnF bann op larg rarg) =
   do
     lval <- larg
     rval <- rarg
-    pure $ Fix $
+    pure $
       case (op, lval, rval) of
-        (NPlus, Fix (NConstant_ ann (NInt x)), Fix (NConstant_ _ (NInt y))) -> NConstant_ ann  $ NInt $ x + y
-        _                                                                   -> NBinary_   bann op lval rval
+        (NPlus, NConstantAnn ann (NInt x), NConstantAnn _ (NInt y)) -> NConstantAnn ann  $ NInt $ x + y
+        _                                                           -> NBinaryAnn   bann op lval rval
 
 -- | Reduce a select on a Set by substituting the set to the selected value.
 --
@@ -203,13 +203,13 @@
 --   1. The selected expr is indeed a set.
 --   2. The selection AttrPath is a list of StaticKeys.
 --   3. The selected AttrPath exists in the set.
-reduce base@(NSelect_ _ _ attrs _)
+reduce base@(NSelectAnnF _ _ _ attrs)
   | sAttrPath $ NE.toList attrs = do
-    (NSelect_ _ aset attrs _) <- sequence base
+    (NSelectAnnF _ _ aset attrs) <- sequenceA base
     inspectSet (unFix aset) attrs
   | otherwise = sId
  where
-  sId = Fix <$> sequence base
+  sId = reduceLayer base
   -- The selection AttrPath is composed of StaticKeys.
   sAttrPath (StaticKey _ : xs) = sAttrPath xs
   sAttrPath []                 = True
@@ -220,7 +220,7 @@
     n@(NamedVar (a' :| _) _ _) | a' == a -> pure n
     _ -> findBind xs attrs
   -- Follow the attrpath recursively in sets.
-  inspectSet (NSet_ _ NNonRecursive binds) attrs = case findBind binds attrs of
+  inspectSet (NSetAnnF _ NonRecursive binds) attrs = case findBind binds attrs of
     Just (NamedVar _ e _) -> case NE.uncons attrs of
       (_, Just attrs) -> inspectSet (unFix e) attrs
       _               -> pure e
@@ -231,49 +231,52 @@
 
 -- | Reduce a set by inlining its binds outside of the set
 --   if none of the binds inherit the super set.
-reduce e@(NSet_ ann NNonRecursive binds) =
-  do
-    let
-      usesInherit =
-        any
-          (\case
-            Inherit{} -> True
-            _         -> False
-          )
-          binds
-
-    bool
-      (Fix <$> sequence e)
-      (clearScopes @NExprLoc $ Fix . NSet_ ann NNonRecursive <$> traverse sequence binds)
+reduce e@(NSetAnnF ann r binds) =
+  bool
+    -- Encountering a 'rec set' construction eliminates any hope of inlining
+    -- definitions.
+    mExprLoc
+    (bool
+      (reduceLayer e)
+      mExprLoc
       usesInherit
+    )
+    (r == NonRecursive)
+ where
+  mExprLoc :: m NExprLoc
+  mExprLoc =
+    clearScopes @NExprLoc $ NSetAnn ann r <$> traverse sequenceA binds
 
--- Encountering a 'rec set' construction eliminates any hope of inlining
--- definitions.
-reduce (NSet_ ann NRecursive binds) =
-  clearScopes @NExprLoc $ Fix . NSet_ ann NRecursive <$> traverse sequence binds
+  usesInherit =
+    any
+      (\case
+        Inherit{} -> True
+        _         -> False
+      )
+      binds
 
 -- Encountering a 'with' construction eliminates any hope of inlining
 -- definitions.
-reduce (NWith_ ann scope body) =
-  clearScopes @NExprLoc $ Fix <$> liftA2 (NWith_ ann) scope body
+reduce (NWithAnnF ann scope body) =
+  clearScopes @NExprLoc $ liftA2 (NWithAnn ann) scope body
 
 -- | Reduce a let binds section by pushing lambdas,
 --   constants and strings to the body scope.
-reduce (NLet_ ann binds body) =
+reduce (NLetAnnF ann binds body) =
   do
-    binds' <- traverse sequence binds
+    binds' <- traverse sequenceA binds
     body'  <-
-      (`pushScope` body) . HM.fromList . catMaybes =<<
+      (`pushScope` body) . coerce . HM.fromList . catMaybes =<<
         traverse
           (\case
             NamedVar (StaticKey name :| []) def _pos ->
               let
                 defcase =
                   \case
-                    d@(Fix NAbs_     {}) -> pure (name, d)
-                    d@(Fix NConstant_{}) -> pure (name, d)
-                    d@(Fix NStr_     {}) -> pure (name, d)
-                    _                    -> Nothing
+                    d@NAbsAnn     {} -> pure (name, d)
+                    d@NConstantAnn{} -> pure (name, d)
+                    d@NStrAnn     {} -> pure (name, d)
+                    _                -> Nothing
               in
               defcase <$> def
 
@@ -282,11 +285,11 @@
           binds
 
     -- let names = gatherNames body'
-    -- binds' <- traverse sequence binds <&> \b -> flip filter b $ \case
+    -- binds' <- traverse sequenceA binds <&> \b -> flip filter b $ \case
     --     NamedVar (StaticKey name _ :| []) _ ->
     --         name `S.member` names
     --     _ -> True
-    pure $ Fix $ NLet_ ann binds' body'
+    pure $ NLetAnn ann binds' body'
     -- where
     --   go m [] = pure m
     --   go m (x:xs) = case x of
@@ -297,34 +300,37 @@
 
 -- | Reduce an if to the relevant path if
 --   the condition is a boolean constant.
-reduce e@(NIf_ _ b t f) =
+reduce e@(NIfAnnF _ b t f) =
   (\case
-    Fix (NConstant_ _ (NBool b')) -> if b' then t else f
-    _                             -> Fix <$> sequence e
+    NConstantAnn _ (NBool b') -> bool f t b'
+    _                         -> reduceLayer e
   ) =<< b
 
 -- | Reduce an assert atom to its encapsulated
 --   symbol if the assertion is a boolean constant.
-reduce e@(NAssert_ _ b body) =
+reduce e@(NAssertAnnF _ b body) =
   (\case
-    Fix (NConstant_ _ (NBool b')) | b' -> body
-    _ -> Fix <$> sequence e
+    NConstantAnn _ (NBool True) -> body
+    _ -> reduceLayer e
   ) =<< b
 
-reduce (NAbs_ ann params body) = do
-  params' <- sequence params
+reduce (NAbsAnnF ann params body) = do
+  params' <- sequenceA params
   -- Make sure that variable definitions in scope do not override function
   -- arguments.
   let
-    args =
+    scope = coerce $
       case params' of
-        Param    name     -> HM.singleton name $ Fix $ NSym_ ann name
-        ParamSet pset _ _ ->
-          HM.fromList $ (\(k, _) -> (k, Fix $ NSym_ ann k)) <$> pset
-  Fix . NAbs_ ann params' <$> pushScope args body
+        Param    name     -> one (name, NSymAnn ann name)
+        ParamSet _ _ pset ->
+          HM.fromList $ (\(k, _) -> (k, NSymAnn ann k)) <$> pset
+  NAbsAnn ann params' <$> pushScope scope body
 
-reduce v = Fix <$> sequence v
+reduce v = reduceLayer v
 
+reduceLayer :: (Traversable f1, Applicative f2) => f1 (f2 (Fix f1)) -> f2 (Fix f1)
+reduceLayer v = Fix <$> sequenceA v
+
 -- newtype FlaggedF f r = FlaggedF { flagged :: (IORef Bool, f r) }
 newtype FlaggedF f r = FlaggedF (IORef Bool, f r)
   deriving (Functor, Foldable, Traversable)
@@ -337,7 +343,7 @@
 flagExprLoc :: (MonadIO n, Traversable f) => Fix f -> n (Flagged f)
 flagExprLoc = foldFixM $ \x -> do
   flag <- liftIO $ newIORef False
-  pure $ Fix $ FlaggedF (flag, x)
+  pure $ coerce (flag, x)
 
 -- stripFlags :: Functor f => Flagged f -> Fix f
 -- stripFlags = foldFix $ Fix . snd . flagged
@@ -348,7 +354,7 @@
     \(FlaggedF (b, Compose x)) ->
       bool
         Nothing
-        (Fix . Compose <$> traverse prune x)
+        (annUnitToAnn <$> traverse prune x)
         <$> liftIO (readIORef b)
  where
   prune :: NExprF (Maybe NExprLoc) -> Maybe (NExprF NExprLoc)
@@ -360,30 +366,30 @@
 
     NList l -> pure $ NList $
       bool
-        (fromMaybe nNull <$>)
+        (fromMaybe annNNull <$>)
         catMaybes
         (reduceLists opts)  -- Reduce list members that aren't used; breaks if elemAt is used
         l
     NSet recur binds -> pure $ NSet recur $
       bool
-        (fromMaybe nNull <<$>>)
-        (mapMaybe sequence)
+        (fromMaybe annNNull <<$>>)
+        (mapMaybe sequenceA)
         (reduceSets opts)  -- Reduce set members that aren't used; breaks if hasAttr is used
         binds
 
-    NLet binds (Just body@(AnnE _ x)) ->
+    NLet binds (Just body@(Ann _ x)) ->
       pure $
         list
           x
           (`NLet` body)
           (mapMaybe pruneBinding binds)
 
-    NSelect (Just aset) attr alt ->
-      pure $ NSelect aset (pruneKeyName <$> attr) $ join alt
+    NSelect alt (Just aset) attr ->
+      pure $ NSelect (join alt) aset $ pruneKeyName <$> attr
 
     -- These are the only short-circuiting binary operators
-    NBinary NAnd (Just (AnnE _ larg)) _ -> pure larg
-    NBinary NOr  (Just (AnnE _ larg)) _ -> pure larg
+    NBinary NAnd (Just (Ann _ larg)) _ -> pure larg
+    NBinary NOr  (Just (Ann _ larg)) _ -> pure larg
 
     -- If the function was never called, it means its argument was in a
     -- thunk that was forced elsewhere.
@@ -393,22 +399,22 @@
     -- invalid is that we're trying to emit what will reproduce whatever
     -- fail the user encountered, which means providing all aspects of
     -- the evaluation path they ultimately followed.
-    NBinary op Nothing (Just rarg) -> pure $ NBinary op nNull rarg
-    NBinary op (Just larg) Nothing -> pure $ NBinary op larg nNull
+    NBinary op Nothing (Just rarg) -> pure $ NBinary op annNNull rarg
+    NBinary op (Just larg) Nothing -> pure $ NBinary op larg annNNull
 
     -- If the scope of a with was never referenced, it's not needed
-    NWith Nothing (Just (AnnE _ body)) -> pure body
+    NWith Nothing (Just (Ann _ body)) -> pure body
 
     NAssert Nothing _              -> fail "How can an assert be used, but its condition not?"
-    NAssert _ (Just (AnnE _ body)) -> pure body
-    NAssert (Just cond) _          -> pure $ NAssert cond nNull
+    NAssert _ (Just (Ann _ body)) -> pure body
+    NAssert (Just cond) _          -> pure $ NAssert cond annNNull
 
     NIf Nothing _ _ -> fail "How can an if be used, but its condition not?"
 
-    NIf _ Nothing (Just (AnnE _ f)) -> pure f
-    NIf _ (Just (AnnE _ t)) Nothing -> pure t
+    NIf _ Nothing (Just (Ann _ f)) -> pure f
+    NIf _ (Just (Ann _ t)) Nothing -> pure t
 
-    x                     -> sequence x
+    x                     -> sequenceA x
 
   pruneString :: NString (Maybe NExprLoc) -> NString NExprLoc
   pruneString (DoubleQuoted xs) = DoubleQuoted $ mapMaybe pruneAntiquotedText xs
@@ -436,28 +442,28 @@
 
   pruneParams :: Params (Maybe NExprLoc) -> Params NExprLoc
   pruneParams (Param n) = Param n
-  pruneParams (ParamSet xs b n) =
-    ParamSet (reduceOrPassMode <$> xs) b n
+  pruneParams (ParamSet mname variadic pset) =
+    ParamSet mname variadic (reduceOrPassMode <$> pset)
    where
     reduceOrPassMode =
       second $
         bool
           fmap
-          ((pure .) . maybe nNull)
+          ((pure .) . maybe annNNull)
           (reduceSets opts)  -- Reduce set members that aren't used; breaks if hasAttr is used
-          (fromMaybe nNull)
+          (fromMaybe annNNull)
 
   pruneBinding :: Binding (Maybe NExprLoc) -> Maybe (Binding NExprLoc)
   pruneBinding (NamedVar _                 Nothing  _  ) = Nothing
   pruneBinding (NamedVar xs                (Just x) pos) = pure $ NamedVar (pruneKeyName <$> xs) x pos
   pruneBinding (Inherit  _                 []       _  ) = Nothing
   pruneBinding (Inherit  (join -> Nothing) _        _  ) = Nothing
-  pruneBinding (Inherit  (join -> m)       xs       pos) = pure $ Inherit m (pruneKeyName <$> xs) pos
+  pruneBinding (Inherit  (join -> m)       xs       pos) = pure $ Inherit m xs pos
 
 reducingEvalExpr
   :: (Framed e m, Has e Options, Exception r, MonadCatch m, MonadIO m)
   => (NExprLocF (m a) -> m a)
-  -> Maybe FilePath
+  -> Maybe Path
   -> NExprLoc
   -> m (NExprLoc, Either r a)
 reducingEvalExpr eval mpath expr =
@@ -467,7 +473,7 @@
       pure <$> foldFix (addEvalFlags eval) expr'
     opts :: Options <- asks $ view hasLens
     expr''          <- pruneTree opts expr'
-    pure (fromMaybe nNull expr'', eres)
+    pure (fromMaybe annNNull expr'', eres)
  where
   addEvalFlags k (FlaggedF (b, x)) = liftIO (writeIORef b True) *> k x
 
diff --git a/src/Nix/Render.hs b/src/Nix/Render.hs
--- a/src/Nix/Render.hs
+++ b/src/Nix/Render.hs
@@ -1,103 +1,100 @@
-{-# LANGUAGE UndecidableInstances #-}
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE DefaultSignatures #-}
-{-# LANGUAGE GADTs #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE TypeOperators #-}
-{-# LANGUAGE ViewPatterns #-}
-{-# LANGUAGE MultiWayIf #-}
+{-# language UndecidableInstances #-}
+{-# language CPP #-}
+{-# language ConstraintKinds #-}
+{-# language DefaultSignatures #-}
+{-# language GADTs #-}
+{-# language TypeFamilies #-}
+{-# language TypeOperators #-}
+{-# language MultiWayIf #-}
 
 module Nix.Render where
 
-import           Prelude                 hiding ( readFile )
-
-import qualified Data.ByteString               as BS
 import qualified Data.Set                      as Set
 import           Nix.Utils.Fix1                 ( Fix1T
-                                                , MonadFix1T )
+                                                , MonadFix1T
+                                                )
 import           Nix.Expr.Types.Annotated
 import           Prettyprinter
 import qualified System.Directory              as S
-import qualified System.Posix.Files            as S
+import qualified System.PosixCompat.Files      as S
 import           Text.Megaparsec.Error
 import           Text.Megaparsec.Pos
+import qualified Data.Text                     as Text
 
-class MonadFail m => MonadFile m where
-    readFile :: FilePath -> m ByteString
-    default readFile :: (MonadTrans t, MonadFile m', m ~ t m') => FilePath -> m ByteString
-    readFile = lift . readFile
-    listDirectory :: FilePath -> m [FilePath]
-    default listDirectory :: (MonadTrans t, MonadFile m', m ~ t m') => FilePath -> m [FilePath]
+class (MonadFail m, MonadIO m) => MonadFile m where
+    readFile :: Path -> m Text
+    default readFile :: (MonadTrans t, MonadIO m', MonadFile m', m ~ t m') => Path -> m Text
+    readFile = liftIO . Prelude.readFile
+    listDirectory :: Path -> m [Path]
+    default listDirectory :: (MonadTrans t, MonadFile m', m ~ t m') => Path -> m [Path]
     listDirectory = lift . listDirectory
-    getCurrentDirectory :: m FilePath
-    default getCurrentDirectory :: (MonadTrans t, MonadFile m', m ~ t m') => m FilePath
+    getCurrentDirectory :: m Path
+    default getCurrentDirectory :: (MonadTrans t, MonadFile m', m ~ t m') => m Path
     getCurrentDirectory = lift getCurrentDirectory
-    canonicalizePath :: FilePath -> m FilePath
-    default canonicalizePath :: (MonadTrans t, MonadFile m', m ~ t m') => FilePath -> m FilePath
+    canonicalizePath :: Path -> m Path
+    default canonicalizePath :: (MonadTrans t, MonadFile m', m ~ t m') => Path -> m Path
     canonicalizePath = lift . canonicalizePath
-    getHomeDirectory :: m FilePath
-    default getHomeDirectory :: (MonadTrans t, MonadFile m', m ~ t m') => m FilePath
+    getHomeDirectory :: m Path
+    default getHomeDirectory :: (MonadTrans t, MonadFile m', m ~ t m') => m Path
     getHomeDirectory = lift getHomeDirectory
-    doesPathExist :: FilePath -> m Bool
-    default doesPathExist :: (MonadTrans t, MonadFile m', m ~ t m') => FilePath -> m Bool
+    doesPathExist :: Path -> m Bool
+    default doesPathExist :: (MonadTrans t, MonadFile m', m ~ t m') => Path -> m Bool
     doesPathExist = lift . doesPathExist
-    doesFileExist :: FilePath -> m Bool
-    default doesFileExist :: (MonadTrans t, MonadFile m', m ~ t m') => FilePath -> m Bool
+    doesFileExist :: Path -> m Bool
+    default doesFileExist :: (MonadTrans t, MonadFile m', m ~ t m') => Path -> m Bool
     doesFileExist = lift . doesFileExist
-    doesDirectoryExist :: FilePath -> m Bool
-    default doesDirectoryExist :: (MonadTrans t, MonadFile m', m ~ t m') => FilePath -> m Bool
+    doesDirectoryExist :: Path -> m Bool
+    default doesDirectoryExist :: (MonadTrans t, MonadFile m', m ~ t m') => Path -> m Bool
     doesDirectoryExist = lift . doesDirectoryExist
-    getSymbolicLinkStatus :: FilePath -> m S.FileStatus
-    default getSymbolicLinkStatus :: (MonadTrans t, MonadFile m', m ~ t m') => FilePath -> m S.FileStatus
+    getSymbolicLinkStatus :: Path -> m S.FileStatus
+    default getSymbolicLinkStatus :: (MonadTrans t, MonadFile m', m ~ t m') => Path -> m S.FileStatus
     getSymbolicLinkStatus = lift . getSymbolicLinkStatus
 
 instance MonadFile IO where
-  readFile              = BS.readFile
-  listDirectory         = S.listDirectory
-  getCurrentDirectory   = S.getCurrentDirectory
-  canonicalizePath      = S.canonicalizePath
-  getHomeDirectory      = S.getHomeDirectory
-  doesPathExist         = S.doesPathExist
-  doesFileExist         = S.doesFileExist
-  doesDirectoryExist    = S.doesDirectoryExist
-  getSymbolicLinkStatus = S.getSymbolicLinkStatus
+  readFile              = Prelude.readFile
+  listDirectory         = coerce S.listDirectory
+  getCurrentDirectory   = coerce S.getCurrentDirectory
+  canonicalizePath      = coerce S.canonicalizePath
+  getHomeDirectory      = coerce S.getHomeDirectory
+  doesPathExist         = coerce S.doesPathExist
+  doesFileExist         = coerce S.doesFileExist
+  doesDirectoryExist    = coerce S.doesDirectoryExist
+  getSymbolicLinkStatus = coerce S.getSymbolicLinkStatus
 
 
-instance (MonadFix1T t m, MonadFail (Fix1T t m), MonadFile m) => MonadFile (Fix1T t m)
+instance (MonadFix1T t m, MonadIO (Fix1T t m), MonadFail (Fix1T t m), MonadFile m) => MonadFile (Fix1T t m)
 
 posAndMsg :: SourcePos -> Doc a -> ParseError s Void
 posAndMsg (SourcePos _ lineNo _) msg =
   FancyError
     (unPos lineNo)
-    (Set.fromList [ErrorFail (show msg) :: ErrorFancy Void])
+    (Set.fromList $ one (ErrorFail (show msg) :: ErrorFancy Void))
 
 renderLocation :: MonadFile m => SrcSpan -> Doc a -> m (Doc a)
-renderLocation (SrcSpan (SourcePos file begLine begCol) (SourcePos file' endLine endCol)) msg
-  | file == file' && file == "<string>" && begLine == endLine
-  = pure $ "In raw input string at position " <> pretty (unPos begCol)
+renderLocation (SrcSpan (SourcePos (coerce -> file) begLine begCol) (SourcePos (coerce -> file') endLine endCol)) msg
+  | file == file' && file == "<string>" && begLine == endLine =
+    pure $ "In raw input string at position " <> pretty (unPos begCol)
 
-  | file /= "<string>" && file == file'
-  = do
-    exist <- doesFileExist file
-    if exist
-      then do
+  | file /= "<string>" && file == file' =
+    bool
+      (pure msg)
+      (do
         txt <- sourceContext file begLine begCol endLine endCol msg
         pure $
           vsep
             [ "In file " <> errorContext file begLine begCol endLine endCol <> ":"
             , txt
             ]
-      else pure msg
+      )
+      =<< doesFileExist file
 renderLocation (SrcSpan beg end) msg = fail $ "Don't know how to render range from " <> show beg <>" to " <> show end <>" for fail: " <> show msg
 
-errorContext :: FilePath -> Pos -> Pos -> Pos -> Pos -> Doc a
-errorContext path bl bc _el _ec =
+errorContext :: Path -> Pos -> Pos -> Pos -> Pos -> Doc a
+errorContext (coerce @Path @FilePath -> path) bl bc _el _ec =
   pretty path <> ":" <> pretty (unPos bl) <> ":" <> pretty (unPos bc)
 
 sourceContext
-  :: MonadFile m => FilePath -> Pos -> Pos -> Pos -> Pos -> Doc a -> m (Doc a)
+  :: MonadFile m => Path -> Pos -> Pos -> Pos -> Pos -> Doc a -> m (Doc a)
 sourceContext path (unPos -> begLine) (unPos -> _begCol) (unPos -> endLine) (unPos -> _endCol) msg
   = do
     let beg' = max 1 $ begLine - 3
@@ -107,31 +104,35 @@
       .   take (end' - beg')
       .   drop (pred beg')
       .   lines
-      .   decodeUtf8
-      <$> readFile path
+      <$> Nix.Render.readFile path
     let
-      longest = length $ show @String (beg' + (length ls) - 1)
+      longest = Text.length $ show $ beg' + length ls - 1
+      pad :: Int -> Text
       pad n =
         let
+          ns :: Text
           ns = show n
-          nsp = replicate (longest - length ns) ' ' <> ns
+          nsp = Text.replicate (longest - Text.length ns) " " <> ns
         in
           if
           | n == begLine && n == endLine -> "==> " <> nsp <> " |  "
           | n >= begLine && n <= endLine -> "  > " <> nsp <> " |  "
           | otherwise                    -> "    " <> nsp <> " |  "
       composeLine n l =
-        [pretty (pad n) <> l]
-        ++ [ pretty
-               $  replicate (length (pad n) - 3) ' '
-               <> "|  "
-               <> replicate (_begCol - 1) ' '
-               <> replicate (_endCol - _begCol) '^'
-           | begLine == endLine && n == endLine ]
+        one (pretty (pad n) <> l)
+        <> whenTrue
+            (one $
+              pretty $
+                Text.replicate (Text.length (pad n) - 3) " "
+                <> "|"
+                <> Text.replicate (_begCol + 1) " "
+                <> Text.replicate (_endCol - _begCol) "^"
+            )
+            (begLine == endLine && n == endLine)
         -- XXX: Consider inserting the message here when it is small enough.
         -- ATM some messages are so huge that they take prevalence over the source listing.
         -- ++ [ indent (length $ pad n) msg | n == endLine ]
 
-      ls' = concat $ zipWith composeLine [beg' ..] ls
+      ls' = fold $ zipWith composeLine [beg' ..] ls
 
-    pure $ vsep $ ls' ++ [ indent (length $ pad begLine) msg ]
+    pure $ vsep $ ls' <> one (indent (Text.length $ pad begLine) msg)
diff --git a/src/Nix/Render/Frame.hs b/src/Nix/Render/Frame.hs
--- a/src/Nix/Render/Frame.hs
+++ b/src/Nix/Render/Frame.hs
@@ -1,21 +1,21 @@
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE AllowAmbiguousTypes #-}
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE MultiWayIf #-}
-{-# LANGUAGE GADTs #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TypeFamilies #-}
+{-# language CPP #-}
+{-# language AllowAmbiguousTypes #-}
+{-# language ConstraintKinds #-}
+{-# language MultiWayIf #-}
+{-# language GADTs #-}
+{-# language TypeFamilies #-}
 
 
 -- | Code for rendering/representation of the messages packaged with their context (Frames).
 module Nix.Render.Frame where
 
 import           Prelude             hiding ( Comparison )
-import           Nix.Utils
+import           GHC.Exception              ( ErrorCall )
 import           Data.Fix                   ( Fix(..) )
-import           Nix.Eval
+import           Nix.Eval            hiding ( addMetaInfo )
 import           Nix.Exec
-import           Nix.Expr
+import           Nix.Expr.Types
+import           Nix.Expr.Types.Annotated
 import           Nix.Frames
 import           Nix.Normal
 import           Nix.Options
@@ -39,37 +39,39 @@
   => Frames
   -> m (Doc ann)
 renderFrames []       = stub
-renderFrames (x : xs) = do
-  opts :: Options <- asks (view hasLens)
-  frames          <- if
-    | verbose opts <= ErrorsOnly -> renderFrame @v @t @f x
-    | verbose opts <= Informational -> do
-      f <- renderFrame @v @t @f x
-      pure $ concatMap go (reverse xs) <> f
-    | otherwise -> concat <$> traverse (renderFrame @v @t @f) (reverse (x : xs))
-  pure $
-    list
-      mempty
-      vsep
-      frames
+renderFrames xss@(x : xs) =
+  do
+    opts :: Options <- asks $ view hasLens
+    let
+      verbosity :: Verbosity
+      verbosity = verbose opts
+    renderedFrames <- if
+        | verbosity <= ErrorsOnly -> render1 x
+      --  2021-10-22: NOTE: List reverse is completely conterproductive. `reverse` of list famously neest to traverse the whole list to take the last element
+        | verbosity <= Informational -> (foldMap renderPosition (reverse xs) <>) <$> render1 x
+        | otherwise -> foldMapM render1 (reverse xss)
+    pure $ list mempty vsep renderedFrames
  where
-  go :: NixFrame -> [Doc ann]
-  go f =
-    maybe
-      mempty
-      (\ pos -> ["While evaluating at " <> pretty (sourcePosPretty pos) <> colon])
-      (framePos @v @m f)
+  render1 :: NixFrame -> m [Doc ann1]
+  render1 = renderFrame @v @t @f
 
+  renderPosition :: NixFrame -> [Doc ann]
+  renderPosition =
+    whenJust
+      (\ pos -> one ("While evaluating at " <> pretty (sourcePosPretty pos) <> colon))
+      . framePos @v @m
+
 framePos
   :: forall v (m :: Type -> Type)
    . (Typeable m, Typeable v)
   => NixFrame
   -> Maybe SourcePos
-framePos (NixFrame _ f)
-  | Just (e :: EvalFrame m v) <- fromException f = case e of
-    EvaluatingExpr _ (AnnE (SrcSpan beg _) _) -> pure beg
+framePos (NixFrame _ f) =
+  (\case
+    EvaluatingExpr _ (Ann (SrcSpan beg _) _) -> pure beg
     _ -> Nothing
-  | otherwise = Nothing
+  )
+  =<< fromException @(EvalFrame m v) f
 
 renderFrame
   :: forall v t f e m ann
@@ -87,55 +89,57 @@
   | Just (e :: ValueFrame t f m  ) <- fromException f = renderValueFrame level  e
   | Just (e :: NormalLoop t f m  ) <- fromException f = renderNormalLoop level  e
   | Just (e :: ExecFrame  t f m  ) <- fromException f = renderExecFrame  level  e
-  | Just (e :: ErrorCall         ) <- fromException f = pure [pretty (Text.show e)]
-  | Just (e :: SynHoleInfo    m v) <- fromException f = pure [pretty (Text.show e)]
+  | Just (e :: ErrorCall         ) <- fromException f = pure $ one $ pretty (Text.show e)
+  | Just (e :: SynHoleInfo    m v) <- fromException f = pure $ one $ pretty (Text.show e)
   | otherwise = fail $ "Unrecognized frame: " <> show f
 
 wrapExpr :: NExprF r -> NExpr
 wrapExpr x = Fix (Fix (NSym "<?>") <$ x)
 
 renderEvalFrame
-  :: (MonadReader e m, Has e Options, MonadFile m)
+  :: forall e m v ann
+  . (MonadReader e m, Has e Options, MonadFile m)
   => NixLevel
   -> EvalFrame m v
   -> m [Doc ann]
 renderEvalFrame level f =
   do
     opts :: Options <- asks (view hasLens)
+    let
+      addMetaInfo :: ([Doc ann] -> [Doc ann]) -> SrcSpan -> Doc ann -> m [Doc ann]
+      addMetaInfo trans loc = fmap (trans . one) . renderLocation loc
+
     case f of
-      EvaluatingExpr scope e@(AnnE ann _) ->
-        do
-          let
-            scopeInfo =
-              bool
-                mempty
-                [pretty $ Text.show scope]
-                (showScopes opts)
-          fmap
-            (\x -> scopeInfo <> [x])
-            $ renderLocation ann =<<
-                renderExpr level "While evaluating" "Expression" e
+      EvaluatingExpr scope e@(Ann loc _) ->
+        addMetaInfo
+          (scopeInfo <>)
+          loc
+          =<< renderExpr level "While evaluating" "Expression" e
+         where
+          scopeInfo :: [Doc ann]
+          scopeInfo =
+            one (pretty $ Text.show scope) `whenTrue` showScopes opts
 
-      ForcingExpr _scope e@(AnnE ann _) | thunks opts ->
-        fmap
-          (: mempty)
-          $ renderLocation ann =<<
-              renderExpr level "While forcing thunk from" "Forcing thunk" e
+      ForcingExpr _scope e@(Ann loc _) | thunks opts ->
+        addMetaInfo
+          id
+          loc
+          =<< renderExpr level "While forcing thunk from" "Forcing thunk" e
 
-      Calling name ann ->
-        fmap
-          (: mempty)
-          $ renderLocation ann $
-              "While calling builtins." <> pretty name
+      Calling name loc ->
+        addMetaInfo
+          id
+          loc
+          $ "While calling builtins." <> pretty name
 
       SynHole synfo ->
-        sequence $
-          let e@(AnnE ann _) = _synHoleInfo_expr synfo in
-
-          [ renderLocation ann =<<
+        sequenceA
+          [ renderLocation loc =<<
               renderExpr level "While evaluating" "Syntactic Hole" e
           , pure $ pretty $ Text.show $ _synHoleInfo_scope synfo
           ]
+         where
+          e@(Ann loc _) = _synHoleInfo_expr synfo
 
       ForcingExpr _ _ -> stub
 
@@ -147,18 +151,28 @@
   -> Text
   -> NExprLoc
   -> m (Doc ann)
-renderExpr _level longLabel shortLabel e@(AnnE _ x) = do
+renderExpr _level longLabel shortLabel e@(Ann _ x) = do
   opts :: Options <- asks (view hasLens)
-  let rendered
-          | verbose opts >= DebugInfo =
-              pretty (PS.ppShow (stripAnnotation e))
-          | verbose opts >= Chatty = prettyNix (stripAnnotation e)
-          | otherwise = prettyNix (Fix (Fix (NSym "<?>") <$ x))
+  let
+    lvl :: Verbosity
+    lvl = verbose opts
+
+    expr :: NExpr
+    expr = stripAnnotation e
+
+    concise = prettyNix $ Fix $ Fix (NSym "<?>") <$ x
+
+    chatty =
+      bool
+        (pretty $ PS.ppShow expr)
+        (prettyNix expr)
+        (lvl == Chatty)
+
   pure $
     bool
-      (pretty shortLabel <> fillSep [": ", rendered])
-      (vsep [pretty (longLabel <> ":\n>>>>>>>>"), indent 2 rendered, "<<<<<<<<"])
-      (verbose opts >= Chatty)
+      (pretty shortLabel <> fillSep [": ", concise])
+      (vsep [pretty (longLabel <> ":\n>>>>>>>>"), indent 2 chatty, "<<<<<<<<"])
+      (lvl >= Chatty)
 
 renderValueFrame
   :: forall e t f m ann
@@ -166,7 +180,7 @@
   => NixLevel
   -> ValueFrame t f m
   -> m [Doc ann]
-renderValueFrame level = fmap (: mempty) . \case
+renderValueFrame level = fmap one . \case
   ForcingThunk    _t -> pure "ForcingThunk" -- jww (2019-03-18): NYI
   ConcerningValue _v -> pure "ConcerningValue"
   Comparison     _ _ -> pure "Comparing"
@@ -175,22 +189,23 @@
   Multiplication _ _ -> pure "Multiplying"
 
   Coercion       x y -> pure
-    $ mconcat [desc, pretty (describeValue x), " to ", pretty (describeValue y)]
+    $ fold [desc, pretty (describeValue x), " to ", pretty (describeValue y)]
    where
     desc =
       bool
-      "While coercing "
-      "Cannot coerce "
-      (level <= Error)
+        "While coercing "
+        "Cannot coerce "
+        (level <= Error)
 
   CoercionToJson v ->
-    ("CoercionToJson " <>) <$> renderValue level "" "" v
+    ("CoercionToJson " <>) <$> dumbRenderValue v
   CoercionFromJson _j -> pure "CoercionFromJson"
   Expectation t v     ->
-    (msg <>) <$> renderValue @_ @t @f @m level "" "" v
+    (msg <>) <$> dumbRenderValue v
    where
     msg = "Expected " <> pretty (describeValue t) <> ", but saw "
 
+--  2021-10-28: NOTE: notice it ignores `level`, `longlabel` & `shortlabel`, to underline that `dumbRenderValue` synonym was created
 renderValue
   :: forall e t f m ann
    . (MonadReader e m, Has e Options, MonadFile m, MonadCitedThunks t f m)
@@ -207,37 +222,34 @@
     (values opts)
     <$> removeEffects v
 
+dumbRenderValue
+  :: forall e t f m ann
+   . (MonadReader e m, Has e Options, MonadFile m, MonadCitedThunks t f m)
+   => (NValue t f m -> m (Doc ann))
+dumbRenderValue = renderValue Info mempty mempty
+
 renderExecFrame
   :: (MonadReader e m, Has e Options, MonadFile m, MonadCitedThunks t f m)
   => NixLevel
   -> ExecFrame t f m
   -> m [Doc ann]
-renderExecFrame level =
-  \case
-    Assertion ann v ->
-      fmap
-        (: mempty)
-        (do
-          d <- renderValue level "" "" v
-          renderLocation ann $ fillSep ["Assertion failed:", d]
-        )
+renderExecFrame _level (Assertion ann v) =
+  fmap
+    one
+    $ renderLocation ann . fillSep . on (<>) one "Assertion failed:" =<< dumbRenderValue v
 
 renderThunkLoop
   :: (MonadReader e m, Has e Options, MonadFile m, Show (ThunkId m))
   => NixLevel
   -> ThunkLoop
   -> m [Doc ann]
-renderThunkLoop _level = pure . (: mempty) . \case
-  ThunkLoop n -> pretty $ "Infinite recursion in thunk " <> n
+renderThunkLoop _level (ThunkLoop n) =
+  pure . one . pretty $ "Infinite recursion in thunk " <> n
 
 renderNormalLoop
   :: (MonadReader e m, Has e Options, MonadFile m, MonadCitedThunks t f m)
   => NixLevel
   -> NormalLoop t f m
   -> m [Doc ann]
-renderNormalLoop level =
-  fmap
-    (: mempty)
-    . \case
-      NormalLoop v ->
-        ("Infinite recursion during normalization forcing " <>) <$> renderValue level "" "" v
+renderNormalLoop _level (NormalLoop v) =
+  one . ("Infinite recursion during normalization forcing " <>) <$> dumbRenderValue v
diff --git a/src/Nix/Scope.hs b/src/Nix/Scope.hs
--- a/src/Nix/Scope.hs
+++ b/src/Nix/Scope.hs
@@ -1,26 +1,29 @@
-{-# LANGUAGE AllowAmbiguousTypes #-}
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FunctionalDependencies #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE ScopedTypeVariables #-}
+{-# language AllowAmbiguousTypes #-}
+{-# language ConstraintKinds #-}
+{-# language FunctionalDependencies #-}
+{-# language GeneralizedNewtypeDeriving #-}
 
 module Nix.Scope where
 
 import qualified Data.HashMap.Lazy             as M
 import qualified Text.Show
 import           Lens.Family2
-import           Nix.Utils
+import           Nix.Expr.Types
 
-newtype Scope a = Scope { getScope :: AttrSet a }
-  deriving (Functor, Foldable, Traversable, Eq)
+--  2021-07-19: NOTE: Scopes can gain from sequentiality, HashMap (aka AttrSet) may not be proper to it.
+newtype Scope a = Scope (AttrSet a)
+  deriving
+    ( Eq, Ord, Generic
+    , Typeable, NFData
+    , Read, Hashable
+    , Semigroup, Monoid
+    , Functor, Foldable, Traversable
+    )
 
 instance Show (Scope a) where
   show (Scope m) = show $ M.keys m
 
-newScope :: AttrSet a -> Scope a
-newScope = Scope
-
-scopeLookup :: Text -> [Scope a] -> Maybe a
+scopeLookup :: VarName -> [Scope a] -> Maybe a
 scopeLookup key = foldr go Nothing
  where
   go
@@ -45,14 +48,14 @@
 instance Monoid (Scopes m a) where
   mempty = emptyScopes
 
-emptyScopes :: forall m a . Scopes m a
+emptyScopes :: Scopes m a
 emptyScopes = Scopes mempty mempty
 
 class Scoped a m | m -> a where
   currentScopes :: m (Scopes m a)
   clearScopes   :: m r -> m r
   pushScopes    :: Scopes m a -> m r -> m r
-  lookupVar     :: Text -> m (Maybe a)
+  lookupVar     :: VarName -> m (Maybe a)
 
 currentScopesReader
   :: forall m a e
@@ -73,19 +76,19 @@
 
 pushScope
   :: Scoped a m
-  => AttrSet a
+  => Scope a
   -> m r
   -> m r
-pushScope s = pushScopes $ Scopes [Scope s] mempty
+pushScope scope = pushScopes $ Scopes (one scope) mempty
 
 pushWeakScope
   :: ( Functor m
      , Scoped a m
      )
-  => m (AttrSet a)
+  => m (Scope a)
   -> m r
   -> m r
-pushWeakScope s = pushScopes $ Scopes mempty [Scope <$> s]
+pushWeakScope scope = pushScopes $ Scopes mempty $ one scope
 
 pushScopesReader
   :: ( MonadReader e m
@@ -101,7 +104,7 @@
   . ( MonadReader e m
     , Has e (Scopes m a)
     )
-  => Text
+  => VarName
   -> m (Maybe a)
 lookupVarReader k =
   do
@@ -112,9 +115,9 @@
         ws <- asks $ dynamicScopes . view hasLens
 
         foldr
-          (\ x rest ->
+          (\ weakscope rest ->
             do
-              mres' <- M.lookup k . getScope <$> x
+              mres' <- M.lookup k . coerce @(Scope a) <$> weakscope
 
               maybe
                 rest
@@ -132,4 +135,4 @@
   => Scopes m a
   -> m r
   -> m r
-withScopes scope = clearScopes . pushScopes scope
+withScopes scopes = clearScopes . pushScopes scopes
diff --git a/src/Nix/Standard.hs b/src/Nix/Standard.hs
--- a/src/Nix/Standard.hs
+++ b/src/Nix/Standard.hs
@@ -1,15 +1,13 @@
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE UndecidableInstances #-}
+{-# language TypeFamilies #-}
+{-# language CPP #-}
+{-# language GeneralizedNewtypeDeriving #-}
+{-# language UndecidableInstances #-}
 
-{-# OPTIONS_GHC -Wno-orphans #-}
+{-# options_ghc -Wno-orphans #-}
 
 
 module Nix.Standard where
 
-import           Prelude hiding                 ( force )
 import           Control.Comonad                ( Comonad )
 import           Control.Comonad.Env            ( ComonadEnv )
 import           Control.Monad.Catch            ( MonadThrow
@@ -19,7 +17,7 @@
 #if !MIN_VERSION_base(4,13,0)
 import           Control.Monad.Fail             ( MonadFail )
 #endif
-import           Control.Monad.Free             ( Free(Pure, Free) )
+import           Control.Monad.Free             ( Free(Free) )
 import           Control.Monad.Reader           ( MonadFix )
 import           Control.Monad.Ref              ( MonadRef(newRef)
                                                 , MonadAtomicRef
@@ -39,7 +37,6 @@
 import           Nix.Scope
 import           Nix.Thunk
 import           Nix.Thunk.Basic
-import           Nix.Utils                      ( free )
 import           Nix.Utils.Fix1                 ( Fix1T(Fix1T) )
 import           Nix.Value
 import           Nix.Value.Monad
@@ -47,7 +44,7 @@
 
 newtype StdCited m a =
   StdCited
-    { _stdCited :: Cited (StdThunk m) (StdCited m) m a }
+    (Cited (StdThunk m) (StdCited m) m a)
   deriving
     ( Generic
     , Typeable
@@ -59,13 +56,18 @@
     , ComonadEnv [Provenance m (StdValue m)]
     )
 
-newtype StdThunk (m :: Type -> Type) =
+newtype StdThunk m =
   StdThunk
-    { _stdThunk :: StdCited m (NThunkF m (StdValue m)) }
+    (StdCited m (NThunkF m (StdValue m)))
 
 type StdValue' m = NValue' (StdThunk m) (StdCited m) m (StdValue m)
 type StdValue m = NValue (StdThunk m) (StdCited m) m
 
+-- | Type alias:
+--
+-- > Cited (StdThunk m) (StdCited m) m (NThunkF m (StdValue m))
+type CitedStdThunk m = Cited (StdThunk m) (StdCited m) m (NThunkF m (StdValue m))
+
 instance Show (StdThunk m) where
   show _ = toString thunkStubText
 
@@ -100,13 +102,13 @@
   , Typeable m
   , Scoped (StdValue m) m
   , MonadReader (Context m (StdValue m)) m
-  , MonadState (HashMap FilePath NExprLoc, HashMap Text Text) m
+  , MonadState (HashMap Path NExprLoc, HashMap Text Text) m
   , MonadDataErrorContext (StdThunk m) (StdCited m) m
   , MonadThunk (StdThunk m) m (StdValue m)
   , MonadValue (StdValue m) m
   )
   => MonadEffects (StdThunk m) (StdCited m) m where
-  makeAbsolutePath = defaultMakeAbsolutePath
+  toAbsolutePath   = defaultToAbsolutePath
   findEnvPath      = defaultFindEnvPath
   findPath         = defaultFindPath
   importPath       = defaultImportPath
@@ -114,6 +116,10 @@
   derivationStrict = defaultDerivationStrict
   traceEffect      = defaultTraceEffect
 
+-- 2021-07-24:
+-- This instance currently is to satisfy @MonadThunk@ requirements for @normalForm@ function.
+-- As it is seen from the instance - it does superficial type class jump.
+-- It is just a type boundary for thunking.
 instance
   ( Typeable       m
   , MonadThunkId   m
@@ -126,34 +132,39 @@
   thunkId
     :: StdThunk m
     -> ThunkId  m
-  thunkId = thunkId . _stdCited . _stdThunk
+  thunkId = thunkId @(CitedStdThunk m) . coerce
   {-# inline thunkId #-}
 
   thunk
     :: m (StdValue m)
     -> m (StdThunk m)
-  thunk = fmap (StdThunk . StdCited) . thunk
+  thunk = fmap coerce . thunk @(CitedStdThunk m)
+  {-# inline thunk #-}
 
   query
     :: m (StdValue m)
     ->    StdThunk m
     -> m (StdValue m)
-  query b = query b . _stdCited . _stdThunk
+  query b = query @(CitedStdThunk m) b . coerce
+  {-# inline query #-}
 
   force
     ::    StdThunk m
     -> m (StdValue m)
-  force = force . _stdCited . _stdThunk
+  force = force @(CitedStdThunk m) . coerce
+  {-# inline force #-}
 
   forceEff
     ::    StdThunk m
     -> m (StdValue m)
-  forceEff = forceEff . _stdCited . _stdThunk
+  forceEff = forceEff @(CitedStdThunk m) . coerce
+  {-# inline forceEff #-}
 
   further
     ::    StdThunk m
     -> m (StdThunk m)
-  further = fmap (StdThunk . StdCited) . further . _stdCited . _stdThunk
+  further = fmap coerce . further @(CitedStdThunk m) . coerce
+  {-# inline further #-}
 
 
 -- * @instance MonadThunkF@ (Kleisli functor HOFs)
@@ -175,7 +186,7 @@
     -> m r
     -> StdThunk m
     -> m r
-  queryF k b = queryF k b . _stdCited . _stdThunk
+  queryF k b = queryF @(CitedStdThunk m) k b . coerce
 
   forceF
     :: ( StdValue m
@@ -183,7 +194,7 @@
        )
     -> StdThunk m
     -> m r
-  forceF k = forceF k . _stdCited . _stdThunk
+  forceF k = forceF @(CitedStdThunk m) k . coerce
 
   forceEffF
     :: ( StdValue m
@@ -191,7 +202,7 @@
        )
     -> StdThunk m
     -> m r
-  forceEffF k = forceEffF k . _stdCited . _stdThunk
+  forceEffF k = forceEffF @(CitedStdThunk m) k . coerce
 
   furtherF
     :: ( m (StdValue m)
@@ -199,7 +210,7 @@
        )
     ->    StdThunk m
     -> m (StdThunk m)
-  furtherF k = fmap (StdThunk . StdCited) . furtherF k . _stdCited . _stdThunk
+  furtherF k = fmap coerce . furtherF @(CitedStdThunk m) k . coerce
 
 
 -- * @instance MonadValue (StdValue m) m@
@@ -216,22 +227,29 @@
   defer
     :: m (StdValue m)
     -> m (StdValue m)
-  defer = fmap pure . thunk
+  defer = fmap (pure . coerce) . thunk @(CitedStdThunk m)
 
   demand
     :: StdValue m
     -> m (StdValue m)
-  demand v =
-    free
-      (demand <=< force)
-      (const $ pure v)
-      v
+  demand = go -- lock to ensure no type class jumps.
+   where
+    go :: StdValue m -> m (StdValue m)
+    go =
+      free
+        (go <=< force @(CitedStdThunk m) . coerce)
+        (pure . Free)
 
   inform
     :: StdValue m
     -> m (StdValue m)
-  inform (Pure t) = Pure <$> further t
-  inform (Free v) = Free <$> bindNValue' id inform v
+  inform = go -- lock to ensure no type class jumps.
+   where
+    go :: StdValue m -> m (StdValue m)
+    go =
+      free
+        ((pure . coerce <$>) . (further @(CitedStdThunk m) . coerce))
+        ((Free <$>) . bindNValue' id go)
 
 
 -- * @instance MonadValueF (StdValue m) m@
@@ -271,8 +289,12 @@
 --   withFrame Debug (ForcingThunk @t @f @m) . withFrame Debug frame
 
 newtype StandardTF r m a
-  = StandardTF (ReaderT (Context r (StdValue r))
-                        (StateT (HashMap FilePath NExprLoc, HashMap Text Text) m) a)
+  = StandardTF
+      (ReaderT
+        (Context r (StdValue r))
+        (StateT (HashMap Path NExprLoc, HashMap Text Text) m)
+        a
+      )
   deriving
     ( Functor
     , Applicative
@@ -286,7 +308,7 @@
     , MonadThrow
     , MonadMask
     , MonadReader (Context r (StdValue r))
-    , MonadState (HashMap FilePath NExprLoc, HashMap Text Text)
+    , MonadState (HashMap Path NExprLoc, HashMap Text Text)
     )
 
 instance MonadTrans (StandardTF r) where
@@ -314,27 +336,28 @@
 
 instance MonadTrans (Fix1T StandardTF) where
   lift = Fix1T . lift
+  {-# inline lift #-}
 
 instance MonadThunkId m
-  => MonadThunkId (Fix1T StandardTF m) where
+  => MonadThunkId (StandardT m) where
 
-  type ThunkId (Fix1T StandardTF m) = ThunkId m
+  type ThunkId (StandardT m) = ThunkId m
 
 mkStandardT
   :: ReaderT
       (Context (StandardT m) (StdValue (StandardT m)))
-      (StateT (HashMap FilePath NExprLoc, HashMap Text Text) m)
+      (StateT (HashMap Path NExprLoc, HashMap Text Text) m)
       a
   -> StandardT m a
-mkStandardT = Fix1T . StandardTF
+mkStandardT = coerce
 
 runStandardT
   :: StandardT m a
   -> ReaderT
       (Context (StandardT m) (StdValue (StandardT m)))
-      (StateT (HashMap FilePath NExprLoc, HashMap Text Text) m)
+      (StateT (HashMap Path NExprLoc, HashMap Text Text) m)
       a
-runStandardT (Fix1T (StandardTF m)) = m
+runStandardT = coerce
 
 runWithBasicEffects
   :: (MonadIO m, MonadAtomicRef m)
diff --git a/src/Nix/String.hs b/src/Nix/String.hs
--- a/src/Nix/String.hs
+++ b/src/Nix/String.hs
@@ -1,9 +1,9 @@
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# language GeneralizedNewtypeDeriving #-}
 
 module Nix.String
   ( NixString
   , getContext
-  , makeNixString
+  , mkNixString
   , StringContext(..)
   , ContextFlavor(..)
   , NixLikeContext(..)
@@ -14,8 +14,8 @@
   , intercalateNixString
   , getStringNoContext
   , stringIgnoreContext
-  , makeNixStringWithoutContext
-  , makeNixStringWithSingletonContext
+  , mkNixStringWithoutContext
+  , mkNixStringWithSingletonContext
   , modifyNixContents
   , WithStringContext
   , WithStringContextT(..)
@@ -32,21 +32,24 @@
 
 
 
-import           Nix.Utils
 import           Control.Monad.Writer           ( WriterT(..), MonadWriter(tell))
 import qualified Data.HashMap.Lazy             as M
 import qualified Data.HashSet                  as S
 import qualified Data.Text                     as Text
+import           Nix.Expr.Types                 ( VarName(..)
+                                                , AttrSet
+                                                )
 
 
 -- * Types
 
 -- ** Context
 
+--  2021-07-18: NOTE: it should be ContextFlavor -> Varname.
 -- | A Nix 'StringContext' ...
 data StringContext =
   StringContext
-    { scPath :: !Text
+    { scPath :: !VarName
     , scFlavor :: !ContextFlavor
     }
   deriving (Eq, Ord, Show, Generic)
@@ -101,6 +104,7 @@
 
 -- ** NixString
 
+--  2021-07-18: NOTE: It should be Context -> Contents.
 data NixString =
   NixString
     { nsContents :: !Text
@@ -122,17 +126,17 @@
 -- ** Makers
 
 -- | Constructs NixString without a context
-makeNixStringWithoutContext :: Text -> NixString
-makeNixStringWithoutContext = (`NixString` mempty)
+mkNixStringWithoutContext :: Text -> NixString
+mkNixStringWithoutContext = (`NixString` mempty)
 
 -- | Create NixString using a singleton context
-makeNixStringWithSingletonContext
-  :: Text -> StringContext -> NixString
-makeNixStringWithSingletonContext s c = NixString s $ one c
+mkNixStringWithSingletonContext
+  :: VarName -> StringContext -> NixString
+mkNixStringWithSingletonContext s c = NixString (coerce @VarName @Text s) $ one c
 
 -- | Create NixString from a Text and context
-makeNixString :: Text -> S.HashSet StringContext -> NixString
-makeNixString = NixString
+mkNixString :: Text -> S.HashSet StringContext -> NixString
+mkNixString = NixString
 
 
 -- ** Checkers
@@ -172,7 +176,7 @@
 
 -- this really should be 2 args, then with @toStringContexts path@ laziness it would tail recurse.
 -- for now tuple dissected internaly with laziness preservation.
-toStringContexts :: (Text, NixLikeContextValue) -> [StringContext]
+toStringContexts :: (VarName, NixLikeContextValue) -> [StringContext]
 toStringContexts ~(path, nlcv) =
   go nlcv
  where
@@ -190,13 +194,13 @@
     mkCtxFor = StringContext path
     mkLstCtxFor t c = mkCtxFor t : go c
 
-toNixLikeContextValue :: StringContext -> (Text, NixLikeContextValue)
+toNixLikeContextValue :: StringContext -> (VarName, NixLikeContextValue)
 toNixLikeContextValue sc =
   ( scPath sc
   , case scFlavor sc of
       DirectPath         -> NixLikeContextValue True False mempty
       AllOutputs         -> NixLikeContextValue False True mempty
-      DerivationOutput t -> NixLikeContextValue False False [t]
+      DerivationOutput t -> NixLikeContextValue False False $ one t
   )
 
 toNixLikeContext :: S.HashSet StringContext -> NixLikeContext
diff --git a/src/Nix/String/Coerce.hs b/src/Nix/String/Coerce.hs
--- a/src/Nix/String/Coerce.hs
+++ b/src/Nix/String/Coerce.hs
@@ -1,8 +1,9 @@
-{-# LANGUAGE CPP #-}
+{-# language CPP #-}
 
 module Nix.String.Coerce where
 
 import           Control.Monad.Catch            ( MonadThrow )
+import           GHC.Exception                  ( ErrorCall(ErrorCall) )
 import qualified Data.HashMap.Lazy             as M
 import           Nix.Atoms
 import           Nix.Effects
@@ -58,11 +59,7 @@
         case x' of
           -- TODO Return a singleton for "" and "1"
           NVConstant (NBool b) ->
-            castToNixString $
-              bool
-                ""
-                "1"
-                b
+            castToNixString $ "1" `whenTrue` b
           NVConstant (NInt n) ->
             castToNixString $
               show n
@@ -70,10 +67,10 @@
             castToNixString $
               show n
           NVConstant NNull ->
-            castToNixString ""
+            castToNixString mempty
           -- NVConstant: NAtom (NURI Text) is not matched
           NVList l ->
-            nixStringUnwords <$> traverse (go <=< demand) l
+            nixStringUnwords <$> traverse go l
           v -> coerceStringy v
 
       coerceStringy x' =
@@ -81,11 +78,11 @@
           NVStr ns -> pure ns
           NVPath p ->
             bool
-              (castToNixString . toText)
+              (castToNixString . fromString . coerce)
               (fmap storePathToNixString . addPath)
               (ctsm == CopyToStore)
               p
-          v@(NVSet s _) ->
+          v@(NVSet _ s) ->
             maybe
               (maybe
                 (err v)
@@ -106,15 +103,15 @@
 
           v -> err v
       err v = throwError $ ErrorCall $ "Expected a string, but saw: " <> show v
-      castToNixString = pure . makeNixStringWithoutContext
+      castToNixString = pure . mkNixStringWithoutContext
 
-  nixStringUnwords = intercalateNixString $ makeNixStringWithoutContext " "
+  nixStringUnwords = intercalateNixString $ mkNixStringWithoutContext " "
 
   storePathToNixString :: StorePath -> NixString
   storePathToNixString sp =
-    makeNixStringWithSingletonContext
+    mkNixStringWithSingletonContext
       t
       (StringContext t DirectPath)
    where
-    t = toText $ unStorePath sp
+    t = fromString $ coerce sp
 
diff --git a/src/Nix/TH.hs b/src/Nix/TH.hs
--- a/src/Nix/TH.hs
+++ b/src/Nix/TH.hs
@@ -1,92 +1,113 @@
-{-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE TemplateHaskell #-}
+{-# language QuasiQuotes #-}
+{-# language TemplateHaskell #-}
 
-{-# OPTIONS_GHC -Wno-missing-fields #-}
+{-# options_ghc -Wno-missing-fields #-}
+{-# options_ghc -Wno-name-shadowing #-}
 
 module Nix.TH where
 
-import           Data.Fix                       ( Fix(..) )
+import           Data.Fix                       ( Fix(unFix) )
 import           Data.Generics.Aliases          ( extQ )
 import qualified Data.Set                      as Set
 import           Language.Haskell.TH
 import qualified Language.Haskell.TH.Syntax    as TH
 import           Language.Haskell.TH.Quote
 import           Nix.Atoms
-import           Nix.Expr
+import           Nix.Expr.Types
+import           Nix.Expr.Types.Annotated
 import           Nix.Parser
 
 quoteExprExp :: String -> ExpQ
-quoteExprExp s = do
-  expr <-
-    either
-      (fail . show)
-      pure
-      (parseNixText $ toText s)
-  dataToExpQ
-    (const Nothing `extQ` metaExp (freeVars expr) `extQ` (pure . (TH.lift :: Text -> Q Exp)))
-    expr
+quoteExprExp s =
+  do
+    expr <- parseExpr $ fromString s
+    dataToExpQ
+      (extQOnFreeVars metaExp expr `extQ` (pure . (TH.lift :: Text -> Q Exp)))
+      expr
 
 quoteExprPat :: String -> PatQ
-quoteExprPat s = do
-  expr <-
-    either
-      (fail . show)
-      pure
-      (parseNixText $ toText s)
-  dataToPatQ
-    (const Nothing `extQ` metaPat (freeVars expr))
-    expr
+quoteExprPat s =
+  do
+    expr <- parseExpr $ fromString s
+    dataToPatQ
+      (extQOnFreeVars metaPat expr)
+      expr
 
+-- | Helper function.
+extQOnFreeVars
+  :: ( Typeable b
+    , Typeable loc
+    )
+  => ( Set VarName
+    -> loc
+    -> Maybe q
+    )
+  -> NExpr
+  -> b
+  -> Maybe q
+extQOnFreeVars f = extQ (const Nothing) . f . freeVars
+
+parseExpr :: (MonadFail m) => Text -> m NExpr
+parseExpr =
+  either
+    (fail . show)
+    pure
+    . parseNixText
+
 freeVars :: NExpr -> Set VarName
 freeVars e = case unFix e of
   (NConstant    _               ) -> mempty
   (NStr         string          ) -> mapFreeVars string
   (NSym         var             ) -> one var
   (NList        list            ) -> mapFreeVars list
-  (NSet   NNonRecursive bindings) -> bindFreeVars bindings
-  (NSet   NRecursive    bindings) -> Set.difference (bindFreeVars bindings) (bindDefs bindings)
+  (NSet   NonRecursive  bindings) -> bindFreeVars bindings
+  (NSet   Recursive     bindings) -> diffBetween bindFreeVars bindDefs bindings
   (NLiteralPath _               ) -> mempty
   (NEnvPath     _               ) -> mempty
   (NUnary       _    expr       ) -> freeVars expr
-  (NBinary      _    left right ) -> ((<>) `on` freeVars) left right
-  (NSelect      expr path orExpr) ->
+  (NBinary      _    left right ) -> collectFreeVars left right
+  (NSelect      orExpr expr path) ->
     Set.unions
       [ freeVars expr
       , pathFree path
-      , maybe mempty freeVars orExpr
+      , freeVars `whenJust` orExpr
       ]
   (NHasAttr expr            path) -> freeVars expr <> pathFree path
   (NAbs     (Param varname) expr) -> Set.delete varname (freeVars expr)
-  (NAbs (ParamSet set _ varname) expr) ->
+  (NAbs (ParamSet varname _ pset) expr) ->
     -- Include all free variables from the expression and the default arguments
     freeVars expr <>
     -- But remove the argument name if existing, and all arguments in the parameter set
     Set.difference
-      (Set.unions $ freeVars <$> mapMaybe snd set)
+      (Set.unions $ freeVars <$> mapMaybe snd pset)
       (Set.difference
-        (maybe mempty one varname)
-        (Set.fromList $ fst <$> set)
+        (one `whenJust` varname)
+        (Set.fromList $ fst <$> pset)
       )
   (NLet         bindings expr   ) ->
     freeVars expr <>
-    Set.difference
-      (bindFreeVars bindings)
-      (bindDefs  bindings)
+    diffBetween bindFreeVars bindDefs bindings
   (NIf          cond th   el    ) -> Set.unions $ freeVars <$> [cond, th, el]
   -- Evaluation is needed to find out whether x is a "real" free variable in `with y; x`, we just include it
   -- This also makes sense because its value can be overridden by `x: with y; x`
-  (NWith        set  expr       ) -> ((<>) `on` freeVars) set expr
-  (NAssert      assertion expr  ) -> ((<>) `on` freeVars) assertion expr
+  (NWith        set  expr       ) -> collectFreeVars set expr
+  (NAssert      assertion expr  ) -> collectFreeVars assertion expr
   (NSynHole     _               ) -> mempty
 
  where
 
+  diffBetween :: (a -> Set VarName) -> (a -> Set VarName) -> a -> Set VarName
+  diffBetween g f b = Set.difference (g b) (f b)
+
+  collectFreeVars :: NExpr -> NExpr -> Set VarName
+  collectFreeVars = (<>) `on` freeVars
+
   bindDefs :: Foldable t => t (Binding NExpr) -> Set VarName
   bindDefs = foldMap bind1Def
    where
     bind1Def :: Binding r -> Set VarName
     bind1Def (Inherit   Nothing                  _    _) = mempty
-    bind1Def (Inherit  (Just _                 ) keys _) = Set.fromList $ mapMaybe staticKey keys
+    bind1Def (Inherit  (Just _                 ) keys _) = Set.fromList keys
     bind1Def (NamedVar (StaticKey  varname :| _) _    _) = one varname
     bind1Def (NamedVar (DynamicKey _       :| _) _    _) = mempty
 
@@ -94,14 +115,10 @@
   bindFreeVars = foldMap bind1Free
    where
     bind1Free :: Binding NExpr -> Set VarName
-    bind1Free (Inherit  Nothing     keys _) = Set.fromList $ mapMaybe staticKey keys
+    bind1Free (Inherit  Nothing     keys _) = Set.fromList keys
     bind1Free (Inherit (Just scope) _    _) = freeVars scope
     bind1Free (NamedVar path        expr _) = pathFree path <> freeVars expr
 
-  staticKey :: NKeyName r -> Maybe VarName
-  staticKey (StaticKey  varname) = pure varname
-  staticKey (DynamicKey _      ) = mempty
-
   pathFree :: NAttrPath NExpr -> Set VarName
   pathFree = foldMap mapFreeVars
 
@@ -116,24 +133,24 @@
   toExpr = id
 
 instance ToExpr VarName where
-  toExpr = Fix . NSym_ nullSpan
+  toExpr = NSymAnn nullSpan
 
 instance ToExpr Int where
-  toExpr = Fix . NConstant_ nullSpan . NInt . fromIntegral
+  toExpr = NConstantAnn nullSpan . NInt . fromIntegral
 
 instance ToExpr Integer where
-  toExpr = Fix . NConstant_ nullSpan . NInt
+  toExpr = NConstantAnn nullSpan . NInt
 
 instance ToExpr Float where
-  toExpr = Fix . NConstant_ nullSpan . NFloat
+  toExpr = NConstantAnn nullSpan . NFloat
 
 metaExp :: Set VarName -> NExprLoc -> Maybe ExpQ
-metaExp fvs (Fix (NSym_ _ x)) | x `Set.member` fvs =
+metaExp fvs (NSymAnn _ x) | x `Set.member` fvs =
   pure [| toExpr $(varE (mkName $ toString x)) |]
 metaExp _ _ = Nothing
 
 metaPat :: Set VarName -> NExprLoc -> Maybe PatQ
-metaPat fvs (Fix (NSym_ _ x)) | x `Set.member` fvs =
+metaPat fvs (NSymAnn _ x) | x `Set.member` fvs =
   pure $ varP $ mkName $ toString x
 metaPat _ _ = Nothing
 
diff --git a/src/Nix/Thunk.hs b/src/Nix/Thunk.hs
--- a/src/Nix/Thunk.hs
+++ b/src/Nix/Thunk.hs
@@ -1,6 +1,6 @@
-{-# LANGUAGE DefaultSignatures #-}
-{-# LANGUAGE FunctionalDependencies #-}
-{-# LANGUAGE TypeFamilies #-}
+{-# language DefaultSignatures #-}
+{-# language FunctionalDependencies #-}
+{-# language TypeFamilies #-}
 
 module Nix.Thunk where
 
diff --git a/src/Nix/Thunk/Basic.hs b/src/Nix/Thunk/Basic.hs
--- a/src/Nix/Thunk/Basic.hs
+++ b/src/Nix/Thunk/Basic.hs
@@ -1,7 +1,6 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE UndecidableInstances #-}
-{-# OPTIONS_GHC -Wno-unused-do-bind #-}
+{-# language ConstraintKinds #-}
+{-# language UndecidableInstances #-}
+{-# options_ghc -Wno-unused-do-bind #-}
 
 
 module Nix.Thunk.Basic
@@ -11,8 +10,6 @@
   , MonadBasicThunk
   ) where
 
-import           Prelude                 hiding ( force )
-import           Relude.Extra                   ( dup )
 import           Control.Monad.Ref              ( MonadRef(Ref, newRef, readRef, writeRef)
                                                 , MonadAtomicRef(atomicModifyRef)
                                                 )
diff --git a/src/Nix/Type/Assumption.hs b/src/Nix/Type/Assumption.hs
--- a/src/Nix/Type/Assumption.hs
+++ b/src/Nix/Type/Assumption.hs
@@ -1,6 +1,6 @@
 -- | Basing on the Nix (Hindley–Milner) type system (that provides decidable type inference):
 -- gathering assumptions (inference evidence) about polymorphic types.
-{-# LANGUAGE TypeFamilies #-}
+{-# language TypeFamilies #-}
 module Nix.Type.Assumption
   ( Assumption(..)
   , empty
@@ -9,7 +9,6 @@
   , extend
   , keys
   , merge
-  , mergeAssumptions
   , singleton
   )
 where
@@ -18,9 +17,10 @@
                                                 , empty
                                                 )
 
+import           Nix.Expr.Types
 import           Nix.Type.Type
 
-newtype Assumption = Assumption { assumptions :: [(Name, Type)] }
+newtype Assumption = Assumption [(VarName, Type)]
   deriving (Eq, Show)
 
 -- We pretend that Assumptions can be inconsistent (nonunique keys),
@@ -33,40 +33,40 @@
   mempty = empty
 
 instance One Assumption where
-  type OneItem Assumption = (Name, Type)
-  one (x, y) = Assumption [(x, y)]
+  type OneItem Assumption = (VarName, Type)
+  one vt = Assumption $ one vt
 
 empty :: Assumption
 empty = Assumption mempty
 
-extend :: Assumption -> (Name, Type) -> Assumption
-extend (Assumption a) (x, s) =
-  Assumption $
-    (x, s) : a
+extend :: Assumption -> (VarName, Type) -> Assumption
+extend a vt =
+  one (coerce vt) <> a
 
-remove :: Assumption -> Name -> Assumption
-remove (Assumption a) var =
-  Assumption $
+remove :: Assumption -> VarName -> Assumption
+remove a var =
+  coerce
+    rmVar
+    a
+ where
+  rmVar :: [(VarName, Type)] -> [(VarName, Type)]
+  rmVar =
     filter
-      (\(n, _) -> n /= var)
-      a
+      ((/=) var . fst)
 
-lookup :: Name -> Assumption -> [Type]
-lookup key (Assumption a) =
+lookup :: VarName -> Assumption -> [Type]
+lookup key a =
   snd <$>
     filter
-      (\(n, _) -> n == key)
-      a
+      ((==) key . fst)
+      (coerce a)
 
 merge :: Assumption -> Assumption -> Assumption
-merge (Assumption a) (Assumption b) =
-  Assumption $ a <> b
-
-mergeAssumptions :: [Assumption] -> Assumption
-mergeAssumptions = foldl' (<>) mempty
+merge =
+  coerce ((<>) @[(VarName, Type)])
 
-singleton :: Name -> Type -> Assumption
-singleton x y = Assumption [(x, y)]
+singleton :: VarName -> Type -> Assumption
+singleton = curry one
 
-keys :: Assumption -> [Name]
+keys :: Assumption -> [VarName]
 keys (Assumption a) = fst <$> a
diff --git a/src/Nix/Type/Env.hs b/src/Nix/Type/Env.hs
--- a/src/Nix/Type/Env.hs
+++ b/src/Nix/Type/Env.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE TypeFamilies #-}
+{-# language TypeFamilies #-}
 module Nix.Type.Env
   ( Env(..)
   , empty
@@ -20,6 +20,7 @@
                                                 , fromList
                                                 )
 
+import           Nix.Expr.Types
 import           Nix.Type.Type
 
 import qualified Data.Map                      as Map
@@ -27,7 +28,7 @@
 
 -- * Typing Environment
 
-newtype Env = TypeEnv (Map.Map Name [Scheme])
+newtype Env = TypeEnv (Map VarName [Scheme])
   deriving (Eq, Show)
 
 instance Semigroup Env where
@@ -39,42 +40,42 @@
   mempty = empty
 
 instance One Env where
-  type OneItem Env = (Name, Scheme)
-  one = uncurry singleton
+  type OneItem Env = (VarName, Scheme)
+  one (x, y) = TypeEnv $ one (x, one y)
 
 empty :: Env
 empty = TypeEnv mempty
 
-extend :: Env -> (Name, [Scheme]) -> Env
-extend env (x, s) = TypeEnv $ Map.insert x s $ coerce env
+extend :: Env -> (VarName, [Scheme]) -> Env
+extend env (x, s) = coerce (Map.insert x s) env
 
-remove :: Env -> Name -> Env
-remove (TypeEnv env) var = TypeEnv $ Map.delete var env
+remove :: Env -> VarName -> Env
+remove env var = TypeEnv $ Map.delete var $ coerce env
 
-extends :: Env -> [(Name, [Scheme])] -> Env
-extends env xs = TypeEnv $ Map.fromList xs <> coerce env
+extends :: Env -> [(VarName, [Scheme])] -> Env
+extends env xs = fromList xs <> coerce env
 
-lookup :: Name -> Env -> Maybe [Scheme]
-lookup key (TypeEnv tys) = Map.lookup key tys
+lookup :: VarName -> Env -> Maybe [Scheme]
+lookup key tys = Map.lookup key $ coerce tys
 
 merge :: Env -> Env -> Env
-merge (TypeEnv a) (TypeEnv b) = TypeEnv $ a <> b
+merge a b = TypeEnv $ coerce a <> coerce b
 
 mergeRight :: Env -> Env -> Env
-mergeRight (TypeEnv a) (TypeEnv b) = TypeEnv $ b <> a
+mergeRight = flip merge
 
 mergeEnvs :: [Env] -> Env
 mergeEnvs = foldl' (<>) mempty
 
-singleton :: Name -> Scheme -> Env
-singleton x y = TypeEnv $ one (x, [y])
+singleton :: VarName -> Scheme -> Env
+singleton = curry one
 
-keys :: Env -> [Name]
+keys :: Env -> [VarName]
 keys (TypeEnv env) = Map.keys env
 
-fromList :: [(Name, [Scheme])] -> Env
-fromList xs = TypeEnv $ Map.fromList xs
+fromList :: [(VarName, [Scheme])] -> Env
+fromList xs = coerce $ Map.fromList xs
 
-toList :: Env -> [(Name, [Scheme])]
+toList :: Env -> [(VarName, [Scheme])]
 toList (TypeEnv env) = Map.toList env
 
diff --git a/src/Nix/Type/Infer.hs b/src/Nix/Type/Infer.hs
--- a/src/Nix/Type/Infer.hs
+++ b/src/Nix/Type/Infer.hs
@@ -1,14 +1,13 @@
-{-# LANGUAGE MultiWayIf #-}
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE AllowAmbiguousTypes #-}
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE ExistentialQuantification #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE RankNTypes #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TypeFamilies #-}
+{-# language MultiWayIf #-}
+{-# language CPP #-}
+{-# language AllowAmbiguousTypes #-}
+{-# language ConstraintKinds #-}
+{-# language ExistentialQuantification #-}
+{-# language GeneralizedNewtypeDeriving #-}
+{-# language RankNTypes #-}
+{-# language TypeFamilies #-}
 
-{-# OPTIONS_GHC -Wno-name-shadowing #-}
+{-# options_ghc -Wno-name-shadowing #-}
 
 module Nix.Type.Infer
   ( Constraint(..)
@@ -22,12 +21,11 @@
 import           Control.Monad.Catch            ( MonadThrow(..)
                                                 , MonadCatch(..)
                                                 )
-import           Control.Monad.Except           ( MonadError(..) )
+import           Control.Monad.Except           ( MonadError(throwError,catchError) )
 import           Prelude                 hiding ( Type
                                                 , TVar
                                                 , Constraint
                                                 )
-import           Nix.Utils
 import           Control.Monad.Logic     hiding ( fail )
 import           Control.Monad.Reader           ( MonadFix )
 import           Control.Monad.Ref              ( MonadAtomicRef(..)
@@ -37,7 +35,6 @@
                                                 , runST
                                                 )
 import           Data.Fix                       ( foldFix )
-import           Data.Foldable                  ( foldrM )
 import qualified Data.HashMap.Lazy             as M
 import           Data.List                      ( delete
                                                 , intersect
@@ -56,13 +53,10 @@
                                                 , evalWithAttrSet
                                                 )
 import           Nix.Expr.Types
-import           Nix.Expr.Types.Annotated
 import           Nix.Fresh
 import           Nix.String
 import           Nix.Scope
-import           Nix.Type.Assumption     hiding ( assumptions
-                                                , extend
-                                                )
+import           Nix.Type.Assumption     hiding ( extend )
 import qualified Nix.Type.Assumption           as Assumption
                                                 ( remove
                                                 , lookup
@@ -80,14 +74,14 @@
   ord =
     zip
       (ordNub $ fv body)
-      (TV . toText <$> letters)
+      (TV . fromString <$> letters)
 
-  fv (TVar a  ) = [a]
-  fv (a :~> b ) = fv a <> fv b
+  fv (TVar a  ) = one a
+  fv (a :~> b ) = on (<>) fv a b
   fv (TCon _  ) = mempty
-  fv (TSet _ a) = concatMap fv $ M.elems a
-  fv (TList a ) = concatMap fv a
-  fv (TMany ts) = concatMap fv ts
+  fv (TSet _ a) = foldMap fv $ M.elems a
+  fv (TList a ) = foldMap fv a
+  fv (TMany ts) = foldMap fv ts
 
   normtype (a :~> b ) = normtype a :~> normtype b
   normtype (TCon a  ) = TCon a
@@ -109,6 +103,7 @@
 closeOver :: Type -> Scheme
 closeOver = normalizeScheme . generalize mempty
 
+-- When `[]` becomes `NonEmpty` - function becomes just `all`
 -- | Check if all elements are of the same type.
 allSameType :: [Type] -> Bool
 allSameType = allSame
@@ -122,7 +117,7 @@
 data TypeError
   = UnificationFail Type Type
   | InfiniteType TVar Type
-  | UnboundVariables [Text]
+  | UnboundVariables [VarName]
   | Ambigious [Constraint]
   | UnificationMismatch [Type] [Type]
   deriving (Eq, Show, Ord)
@@ -135,7 +130,7 @@
   | forall s. Exception s => EvaluationError s
 
 typeError :: MonadError InferError m => TypeError -> m ()
-typeError err = throwError $ TypeInferenceErrors [err]
+typeError err = throwError $ TypeInferenceErrors $ one err
 
 -- ** Instances
 
@@ -143,20 +138,27 @@
 instance Exception InferError
 
 instance Semigroup InferError where
-  x <> _ = x
+  (<>) = const
 
 instance Monoid InferError where
   mempty  = TypeInferenceAborted
-  mappend = (<>)
 
 -- * @InferState@: inference state
 
--- | Inference state
-newtype InferState = InferState { count :: Int }
+-- | Inference state (stage).
+newtype InferState = InferState Int
+ deriving
+  (Eq, Num, Enum, Ord)
 
+instance Semigroup InferState where
+  (<>) = (+)
+
+instance Monoid InferState where
+  mempty = 0
+
 -- | Initial inference state
 initInfer :: InferState
-initInfer = InferState { count = 0 }
+initInfer = InferState 0
 
 letters :: [String]
 letters =
@@ -170,18 +172,19 @@
 freshTVar =
   do
     s <- get
-    put s { count = count s + 1 }
-    pure $ TV $ toText $ letters !! count s
+    put $ succ s
+    pure $ TV $ fromString $ letters !! coerce s
 
 fresh :: MonadState InferState m => m Type
 fresh = TVar <$> freshTVar
 
+intoFresh :: (Traversable t, MonadState InferState f) => t a -> f (t Type)
+intoFresh =
+  traverse (const fresh)
+
 instantiate :: MonadState InferState m => Scheme -> m Type
 instantiate (Forall as t) =
-  do
-    as' <- traverse (const fresh) as
-    let s = Subst $ Map.fromList $ zip as as'
-    pure $ apply s t
+  fmap ((`apply` t) . coerce . Map.fromList . zip as) (intoFresh as)
 
 -- * @Constraint@ data type
 
@@ -199,9 +202,9 @@
 
 -- | Compose substitutions
 compose :: Subst -> Subst -> Subst
-Subst s1 `compose` Subst s2 =
-  Subst $
-    apply (Subst s1) <$>
+compose a@(Subst s2) (Subst s1) =
+  coerce $ --
+    apply a <$>
       (s2 <> s1)
 
 -- * class @Substitutable@
@@ -214,15 +217,14 @@
 instance Substitutable TVar where
   apply (Subst s) a = tv
    where
-    t         = TVar a
-    (TVar tv) = Map.findWithDefault t a s
+    (TVar tv) = Map.findWithDefault (TVar a) a s
 
 instance Substitutable Type where
   apply _         (  TCon a   ) = TCon a
   apply s         (  TSet b a ) = TSet b $ apply s <$> a
   apply s         (  TList a  ) = TList  $ apply s <$> a
   apply (Subst s) t@(TVar  a  ) = Map.findWithDefault t a s
-  apply s         (  t1 :~> t2) = apply s t1 :~> apply s t2
+  apply s         (  t1 :~> t2) = ((:~>) `on` apply s) t1 t2
   apply s         (  TMany ts ) = TMany  $ apply s <$> ts
 
 instance Substitutable Scheme where
@@ -231,10 +233,7 @@
     s' = Subst $ foldr Map.delete s as
 
 instance Substitutable Constraint where
-  apply s (EqConst      t1 t2) =
-    EqConst
-      (apply s t1)
-      (apply s t2)
+  apply s (EqConst      t1 t2) = on EqConst (apply s) t1 t2
   apply s (ExpInstConst t  sc) =
     ExpInstConst
       (apply s t)
@@ -252,7 +251,7 @@
   apply = Set.map . apply
 
 
--- * data type @Judgement@
+-- * data type @Judgment@
 
 data Judgment s =
   Judgment
@@ -262,16 +261,22 @@
     }
     deriving Show
 
+inferred :: Type -> Judgment s
+inferred = Judgment mempty mempty
+
 -- * @InferT@: inference monad
 
+type InferTInternals s m a =
+  ReaderT
+    (Set.Set TVar, Scopes (InferT s m) (Judgment s))
+    (StateT InferState (ExceptT InferError m))
+    a
+
 -- | Inference monad
 newtype InferT s m a =
   InferT
     { getInfer ::
-        ReaderT
-          (Set.Set TVar, Scopes (InferT s m) (Judgment s))
-          (StateT InferState (ExceptT InferError m))
-          a
+        InferTInternals s m a
     }
     deriving
       ( Functor
@@ -286,8 +291,11 @@
       , MonadError InferError
       )
 
-extendMSet :: Monad m => TVar -> InferT s m a -> InferT s m a
-extendMSet x = InferT . local (first $ Set.insert x) . getInfer
+extendMSet :: forall s m a . Monad m => TVar -> InferT s m a -> InferT s m a
+extendMSet x = coerce putSetElementM
+ where
+  putSetElementM :: InferTInternals s m a -> InferTInternals s m a
+  putSetElementM = local (first . Set.insert $ x)
 
 -- ** Instances
 
@@ -335,58 +343,48 @@
 instance
   MonadInfer m
   => FromValue ( AttrSet (Judgment s)
-              , AttrSet SourcePos
+              , PositionSet
               ) (InferT s m) (Judgment s)
  where
   fromValueMay (Judgment _ _ (TSet _ xs)) =
     do
-      let sing _ = Judgment mempty mempty
+      let sing = const inferred
       pure $ pure (M.mapWithKey sing xs, mempty)
   fromValueMay _ = stub
   fromValue =
     pure .
-      fromMaybe
-      (mempty, mempty)
+      maybeToMonoid
       <=< fromValueMay
 
-instance MonadInfer m
-  => ToValue (AttrSet (Judgment s), AttrSet SourcePos)
-            (InferT s m) (Judgment s) where
-  toValue (xs, _) =
-    liftA3
-      Judgment
-      (foldrM go mempty xs)
-      (fun concat      typeConstraints)
-      (fun (TSet True) inferredType   )
+foldInitializedWith :: (Traversable t, Applicative f) => (t c -> c) -> (b -> c) -> (a -> f b) -> t a -> f c
+foldInitializedWith fld getter init =
+  -- maybe here is some law?
+  fmap fld . traverse (fmap getter . init)
+
+toJudgment :: forall t m s . (Traversable t, Monad m) => (t Type -> Type) -> t (Judgment s) -> InferT s m (Judgment s)
+toJudgment c xs =
+  liftA3 Judgment
+    (foldWith fold assumptions    )
+    (foldWith fold typeConstraints)
+    (foldWith c    inferredType   )
    where
-    go x rest =
-      do
-        x' <- demand x
-        pure $ assumptions x' <> rest
+    foldWith :: ((t a -> a) -> (Judgment s -> a) -> InferT s m a)
+    foldWith g f = uncurry (foldInitializedWith g f) tpl
 
-    fun :: (AttrSet b -> b1) -> (Judgment s -> b) -> InferT s m b1
-    fun g f =
-      g <$> traverse ((f <$>) . demand) xs
+    tpl :: (Judgment s -> InferT s m (Judgment s), t (Judgment s))
+    tpl = (demand, xs)
 
-instance MonadInfer m => ToValue [Judgment s] (InferT s m) (Judgment s) where
-  toValue xs =
-    liftA3
-      Judgment
-      (foldrM go mempty xs)
-      (fun concat typeConstraints)
-      (fun TList  inferredType   )
-   where
-    go x rest =
-      do
-        x' <- demand x
-        pure $ assumptions x' <> rest
+instance MonadInfer m
+  => ToValue (AttrSet (Judgment s), PositionSet)
+            (InferT s m) (Judgment s) where
+  toValue :: (AttrSet (Judgment s), PositionSet) -> InferT s m (Judgment s)
+  toValue (xs, _) = toJudgment (TSet Variadic) xs -- why variadic? Probably `Closed` (`mempty`)?
 
-    fun :: ([b] -> b1) -> (Judgment s -> b) -> InferT s m b1
-    fun g f =
-      g <$> traverse ((f <$>) . demand) xs
+instance MonadInfer m => ToValue [Judgment s] (InferT s m) (Judgment s) where
+  toValue = toJudgment TList
 
 instance MonadInfer m => ToValue Bool (InferT s m) (Judgment s) where
-  toValue _ = pure $ Judgment mempty mempty typeBool
+  toValue _ = pure $ inferred typeBool
 
 instance
   Monad m
@@ -424,7 +422,7 @@
       -> InferT s m r)
     -> Judgment s
     -> InferT s m r
-  demandF f a = f a
+  demandF f = f
 
   informF
     :: ( InferT s m (Judgment s)
@@ -455,84 +453,84 @@
                            f =<< Judgment mempty mempty <$> fresh
 -}
 
+polymorphicVar :: MonadInfer m => VarName -> InferT s m (Judgment s)
+polymorphicVar var =
+    fmap
+      (join ((`Judgment` mempty) . curry one var))
+      fresh
+
+constInfer :: Applicative f => Type -> b -> f (Judgment s)
+constInfer x = const $ pure $ inferred x
+
 instance MonadInfer m => MonadEval (Judgment s) (InferT s m) where
-  freeVariable var = do
-    tv <- fresh
-    pure $ Judgment (one (var, tv)) mempty tv
+  freeVariable = polymorphicVar
 
-  synHole var = do
-    tv <- fresh
-    pure $ Judgment (one (var, tv)) mempty tv
+  synHole = polymorphicVar
 
   -- If we fail to look up an attribute, we just don't know the type.
-  attrMissing _ _ = Judgment mempty mempty <$> fresh
+  attrMissing _ _ = inferred <$> fresh
 
   evaledSym _ = pure
 
   evalCurPos =
     pure $
-      Judgment
-        mempty
-        mempty
-        (TSet False $
+      inferred $
+        TSet mempty $
           M.fromList
             [ ("file", typePath)
             , ("line", typeInt )
             , ("col" , typeInt )
             ]
-        )
 
-  evalConstant c = pure $ Judgment mempty mempty $ go c
+  evalConstant c = pure $ inferred $ fun c
    where
-    go = \case
+    fun = \case
       NURI   _ -> typeString
       NInt   _ -> typeInt
       NFloat _ -> typeFloat
       NBool  _ -> typeBool
       NNull    -> typeNull
 
-  evalString      = const $ pure $ Judgment mempty mempty typeString
-  evalLiteralPath = const $ pure $ Judgment mempty mempty typePath
-  evalEnvPath     = const $ pure $ Judgment mempty mempty typePath
+  evalString      = constInfer typeString
+  evalLiteralPath = constInfer typePath
+  evalEnvPath     = constInfer typePath
 
-  evalUnary op (Judgment as1 cs1 t1) = do
-    tv <- fresh
-    pure $
-      Judgment
-        as1
-        (cs1 <> unops (t1 :~> tv) op)
-        tv
+  evalUnary op (Judgment as1 cs1 t1) =
+    fmap
+      (join
+        $ Judgment
+            as1
+            . (cs1 <>) . (`unops` op) . (t1 :~>)
+      )
+      fresh
 
   evalBinary op (Judgment as1 cs1 t1) e2 = do
     Judgment as2 cs2 t2 <- e2
-    tv                  <- fresh
-    pure $
-      Judgment
-        (as1 <> as2)
-        ( cs1 <>
-          cs2 <>
-          binops
-            (t1 :~> t2 :~> tv)
-            op
-        )
-        tv
+    fmap
+      (join
+        $ Judgment
+          (as1 <> as2)
+          . (\ cs3 -> cs1 <> cs2 <> cs3) . (`binops` op) . (\ t3 -> t1 :~> t2 :~> t3)
+      )
+      fresh
 
   evalWith = Eval.evalWithAttrSet
 
   evalIf (Judgment as1 cs1 t1) t f = do
     Judgment as2 cs2 t2 <- t
     Judgment as3 cs3 t3 <- f
-    pure $ Judgment
-      (as1 <> as2 <> as3)
-      (cs1 <> cs2 <> cs3 <> [EqConst t1 typeBool, EqConst t2 t3])
-      t2
+    pure $
+      Judgment
+        (as1 <> as2 <> as3)
+        (cs1 <> cs2 <> cs3 <> [EqConst t1 typeBool, EqConst t2 t3])
+        t2
 
   evalAssert (Judgment as1 cs1 t1) body = do
     Judgment as2 cs2 t2 <- body
     pure $
       Judgment
         (as1 <> as2)
-        (cs1 <> cs2 <> [EqConst t1 typeBool])
+        (cs1 <> cs2 <> one (EqConst t1 typeBool))
         t2
 
   evalApp (Judgment as1 cs1 t1) e2 = do
@@ -541,7 +539,7 @@
     pure $
       Judgment
         (as1 <> as2)
-        (cs1 <> cs2 <> [EqConst t1 (t2 :~> tv)])
+        (cs1 <> cs2 <> one (EqConst t1 (t2 :~> tv)))
         tv
 
   evalAbs (Param x) k = do
@@ -565,27 +563,19 @@
         (cs <> [ EqConst t' tv | t' <- Assumption.lookup x as ])
         (tv :~> t)
 
-  evalAbs (ParamSet ps variadic _mname) k = do
-    js <-
-      concat <$>
-        traverse
-          (\(name, _) ->
-            do
-              tv <- fresh
-              pure [(name, tv)]
-          )
-          ps
+  evalAbs (ParamSet _mname variadic pset) k = do
+    js <- foldInitializedWith fold one intoFresh pset
 
     let
       f (as1, t1) (k, t) = (as1 <> one (k, t), M.insert k t t1)
-      (env, tys) = foldl' f (mempty, mempty) js
-      arg   = pure $ Judgment env mempty $ TSet True tys
+      (env, tys) = foldl' f mempty js
+      arg   = pure $ Judgment env mempty $ TSet Variadic tys
       call  = k arg $ \args b -> (args, ) <$> b
       names = fst <$> js
 
     (args, Judgment as cs t) <- foldr (\(_, TVar a) -> extendMSet a) call js
 
-    ty <- TSet variadic <$> traverse (inferredType <$>) args
+    ty <- foldInitializedWith (TSet variadic) inferredType id args
 
     pure $
       Judgment
@@ -651,7 +641,7 @@
 runInfer' =
   runExceptT
     . (`evalStateT` initInfer)
-    . (`runReaderT` (mempty, mempty))
+    . (`runReaderT` mempty)
     . getInfer
 
 runInfer :: (forall s . InferT s (FreshIdT Int (ST s)) a) -> Either InferError a
@@ -667,6 +657,7 @@
   do
     Judgment as cs t <- infer ex
     let
+      unbounds :: Set VarName
       unbounds =
         (Set.difference `on` Set.fromList)
           (Assumption.keys as )
@@ -683,30 +674,32 @@
             , s       <- ss
             , t       <- Assumption.lookup x as
         ]
-      eres = (`evalState` inferState) $ runSolver $
-        do
-          subst <- solve $ cs <> cs'
-          pure (subst, subst `apply` t)
+      evalResult =
+        (`evalState` inferState) . runSolver $ second (`apply` t) . join (,) <$> solve (cs <> cs')
 
     either
       (throwError . TypeInferenceErrors)
       pure
-      eres
+      evalResult
 
 -- | Solve for the toplevel type of an expression in a given environment
 inferExpr :: Env -> NExpr -> Either InferError [Scheme]
 inferExpr env ex =
-  (\ (subst, ty) -> closeOver $ subst `apply` ty) <<$>>
-    runInfer (inferType env ex)
+  closeOver . uncurry apply <<$>> runInfer (inferType env ex)
 
 unops :: Type -> NUnaryOp -> [Constraint]
 unops u1 op =
-  [ EqConst u1
-   (case op of
-      NNot -> typeFun [typeBool                   , typeBool                       ]
-      NNeg -> TMany   [typeFun  [typeInt, typeInt], typeFun  [typeFloat, typeFloat]]
-    )
-  ]
+  one $
+    EqConst u1 $
+      case op of
+        NNot -> mkUnaryConstr typeBool
+        NNeg -> TMany $ mkUnaryConstr <$> [typeInt, typeFloat]
+ where
+  mkUnaryConstr :: Type -> Type
+  mkUnaryConstr = typeFun . mk2same
+   where
+    mk2same :: a -> NonEmpty a
+    mk2same a = a :| one a
 
 binops :: Type -> NBinaryOp -> [Constraint]
 binops u1 op =
@@ -724,47 +717,55 @@
 
  where
 
-  gate          = eqCnst [typeBool, typeBool, typeBool]
-  concatenation = eqCnst [typeList, typeList, typeList]
+  mk3 :: a -> a -> a -> NonEmpty a
+  mk3 a b c = a :| [b, c]
 
-  eqCnst l = [EqConst u1 $ typeFun l]
+  mk3same :: a -> NonEmpty a
+  mk3same a = a :| [a, a]
 
+  allConst :: Type -> [Constraint]
+  allConst = one . EqConst u1 . typeFun . mk3same
+
+  gate          = allConst typeBool
+  concatenation = allConst typeList
+
+  eqConstrMtx :: [NonEmpty Type] -> [Constraint]
+  eqConstrMtx = one . EqConst u1 . TMany . fmap typeFun
+
   inequality =
-    eqCnstMtx
-      [ [typeInt  , typeInt  , typeBool]
-      , [typeFloat, typeFloat, typeBool]
-      , [typeInt  , typeFloat, typeBool]
-      , [typeFloat, typeInt  , typeBool]
+    eqConstrMtx
+      [ mk3 typeInt   typeInt   typeBool
+      , mk3 typeFloat typeFloat typeBool
+      , mk3 typeInt   typeFloat typeBool
+      , mk3 typeFloat typeInt   typeBool
       ]
 
   arithmetic =
-    eqCnstMtx
-      [ [typeInt  , typeInt  , typeInt  ]
-      , [typeFloat, typeFloat, typeFloat]
-      , [typeInt  , typeFloat, typeFloat]
-      , [typeFloat, typeInt  , typeFloat]
+    eqConstrMtx
+      [ mk3same typeInt
+      , mk3same typeFloat
+      , mk3 typeInt   typeFloat typeFloat
+      , mk3 typeFloat typeInt   typeFloat
       ]
 
   rUnion =
-    eqCnstMtx
-      [ [typeSet , typeSet , typeSet]
-      , [typeSet , typeNull, typeSet]
-      , [typeNull, typeSet , typeSet]
+    eqConstrMtx
+      [ mk3same typeSet
+      , mk3 typeSet  typeNull typeSet
+      , mk3 typeNull typeSet  typeSet
       ]
 
   addition =
-    eqCnstMtx
-      [ [typeInt   , typeInt   , typeInt   ]
-      , [typeFloat , typeFloat , typeFloat ]
-      , [typeInt   , typeFloat , typeFloat ]
-      , [typeFloat , typeInt   , typeFloat ]
-      , [typeString, typeString, typeString]
-      , [typePath  , typePath  , typePath  ]
-      , [typeString, typeString, typePath  ]
+    eqConstrMtx
+      [ mk3same typeInt
+      , mk3same typeFloat
+      , mk3 typeInt    typeFloat  typeFloat
+      , mk3 typeFloat  typeInt    typeFloat
+      , mk3same typeString
+      , mk3same typePath
+      , mk3 typeString typeString typePath
       ]
 
-  eqCnstMtx mtx = [EqConst u1 $ TMany $ typeFun <$> mtx]
-
 liftInfer :: Monad m => m a -> InferT s m a
 liftInfer = InferT . lift . lift . lift
 
@@ -773,13 +774,11 @@
 infer :: MonadInfer m => NExpr -> InferT s m (Judgment s)
 infer = foldFix Eval.eval
 
-inferTop :: Env -> [(Text, NExpr)] -> Either InferError Env
+inferTop :: Env -> [(VarName, NExpr)] -> Either InferError Env
 inferTop env []                = pure env
 inferTop env ((name, ex) : xs) =
-  either
-    Left
-    (\ ty -> inferTop (extend env (name, ty)) xs)
-    (inferExpr env ex)
+  (\ ty -> inferTop (extend env (name, ty)) xs)
+    =<< inferExpr env ex
 
 -- * Other
 
@@ -787,13 +786,15 @@
     deriving (Functor, Applicative, Alternative, Monad, MonadPlus,
               MonadLogic, MonadState [TypeError])
 
-runSolver :: Monad m => Solver m a -> m (Either [TypeError] [a])
-runSolver (Solver s) = do
-  res <- runStateT (observeAllT s) mempty
-  pure $
-    case res of
-      (x : xs, _ ) -> pure (x : xs)
-      (_     , es) -> Left (ordNub es)
+runSolver :: forall m a . Monad m => Solver m a -> m (Either [TypeError] [a])
+runSolver (Solver s) =
+  uncurry report <$> runStateT (observeAllT s) mempty
+ where
+  report :: [a] -> [TypeError] -> Either [TypeError] [a]
+  report xs e =
+    if null xs
+      then Left (ordNub e)
+      else pure xs
 
 -- ** Instances
 
@@ -827,13 +828,9 @@
 -- Putting a statement that lists of different lengths containing various types would not
 -- be unified.
 unifies t1@(TList _    ) t2@(TList _    ) = throwError $ UnificationFail t1 t2
-unifies (   TSet True _) (   TSet True _) = stub
-unifies (TSet False b) (TSet True s)
-  | M.keys b `intersect` M.keys s == M.keys s = stub
-unifies (TSet True s) (TSet False b)
-  | M.keys b `intersect` M.keys s == M.keys b = stub
-unifies (TSet False s) (TSet False b)
-  | null (M.keys b \\ M.keys s) = stub
+unifies (TSet Variadic _) (TSet Variadic _)                                 = stub
+unifies (TSet Closed   s) (TSet Closed   b) | null (M.keys b \\ M.keys s)   = stub
+unifies (TSet _ a) (TSet _ b) | (M.keys a `intersect` M.keys b) == M.keys b = stub
 unifies (t1 :~> t2) (t3 :~> t4) = unifyMany [t1, t2] [t3, t4]
 unifies (TMany t1s) t2          = considering t1s >>- (`unifies` t2)
 unifies t1          (TMany t2s) = considering t2s >>- unifies t1
@@ -844,17 +841,15 @@
 unifyMany (t1 : ts1) (t2 : ts2) = do
   su1 <- unifies t1 t2
   su2 <-
-    unifyMany
-      (apply su1 ts1)
-      (apply su1 ts2)
-  pure $ su2 `compose` su1
+    (unifyMany `on` apply su1) ts1 ts2
+  pure $ compose su1 su2
 unifyMany t1 t2 = throwError $ UnificationMismatch t1 t2
 
 nextSolvable :: [Constraint] -> (Constraint, [Constraint])
-nextSolvable xs = fromJust $ find solvable $ takeFirstOnes xs
+nextSolvable = fromJust . find solvable . pickFirstOne
  where
-  takeFirstOnes :: Eq a => [a] -> [(a, [a])]
-  takeFirstOnes xs = [ (x, ys) | x <- xs, let ys = delete x xs ]
+  pickFirstOne :: Eq a => [a] -> [(a, [a])]
+  pickFirstOne xs = [ (x, ys) | x <- xs, let ys = delete x xs ]
 
   solvable :: (Constraint, [Constraint]) -> Bool
   solvable (EqConst{}     , _) = True
@@ -862,18 +857,23 @@
   solvable (ImpInstConst _t1 ms t2, cs) =
     Set.null $ (ms `Set.difference` ftv t2) `Set.intersection` atv cs
 
-solve :: MonadState InferState m => [Constraint] -> Solver m Subst
+solve :: forall m . MonadState InferState m => [Constraint] -> Solver m Subst
 solve [] = stub
 solve cs = solve' $ nextSolvable cs
  where
-  solve' (EqConst t1 t2, cs) =
-    unifies t1 t2 >>-
-      \su1 -> solve (apply su1 cs) >>-
-          \su2 -> pure $ su2 `compose` su1
-
   solve' (ImpInstConst t1 ms t2, cs) =
     solve (ExpInstConst t1 (generalize ms t2) : cs)
+  solve' (ExpInstConst t s, cs) =
+    do
+      s' <- lift $ instantiate s
+      solve (EqConst t s' : cs)
+  solve' (EqConst t1 t2, cs) =
+    (\ su1 ->
+      (pure . compose su1) -<< solve ((`apply` cs) su1)
+    ) -<<
+    unifies t1 t2
 
-  solve' (ExpInstConst t s, cs) = do
-    s' <- lift $ instantiate s
-    solve (EqConst t s' : cs)
+infixr 1 -<<
+-- | @LogicT@ fair conjunction, since library has only @>>-@
+(-<<) :: Monad m => (a -> Solver m b) -> Solver m a -> Solver m b
+(-<<) = flip (>>-)
diff --git a/src/Nix/Type/Type.hs b/src/Nix/Type/Type.hs
--- a/src/Nix/Type/Type.hs
+++ b/src/Nix/Type/Type.hs
@@ -3,11 +3,8 @@
 --   Therefore -> from this the type inference follows.
 module Nix.Type.Type where
 
-import           Prelude                 hiding ( Type, TVar )
-import           Data.Foldable                  ( foldr1 )
-import           Nix.Utils                      ( AttrSet )
-
-type Name = Text
+import           Prelude                 hiding (Type, TVar)
+import           Nix.Expr.Types
 
 -- | Hindrey-Milner type interface
 
@@ -19,7 +16,7 @@
 data Type
   = TVar TVar                -- ^ Type variable in the Nix type system.
   | TCon Text                -- ^ Concrete (non-polymorphic, constant) type in the Nix type system.
-  | TSet Bool (AttrSet Type) -- ^ Heterogeneous map in the Nix type system. @True@ -> variadic.
+  | TSet Variadic (AttrSet Type) -- ^ Heterogeneous map in the Nix type system. @True@ -> variadic.
   | TList [Type]             -- ^ Heterogeneous list in the Nix type system.
   | (:~>) Type Type          -- ^ Type arrow (@Type -> Type@) in the Nix type system.
   | TMany [Type]             -- ^ Variant type (term). Since relating to Nix type system, more precicely -
@@ -34,22 +31,21 @@
 data Scheme = Forall [TVar] Type -- ^ @Forall [TVar] Type@: the Nix type system @forall vars. type@.
   deriving (Show, Eq, Ord)
 
+-- | Concrete types in the Nix type system.
+typeNull, typeBool, typeInt, typeFloat, typeString, typePath :: Type
+typeNull   = TCon "null"
+typeBool   = TCon "boolean"
+typeInt    = TCon "integer"
+typeFloat  = TCon "float"
+typeString = TCon "string"
+typePath   = TCon "path"
+
 -- This models a set that unifies with any other set.
 typeSet :: Type
-typeSet = TSet True mempty
+typeSet = TSet mempty mempty
 
 typeList :: Type
 typeList = TList mempty
 
-typeFun :: [Type] -> Type
--- Please, replace with safe analog to `foldr1`
-typeFun = foldr1 (:~>)
-
--- | Concrete types in the Nix type system.
-typeInt, typeFloat, typeBool, typeString, typePath, typeNull :: Type
-typeInt    = TCon "integer"
-typeFloat  = TCon "float"
-typeBool   = TCon "boolean"
-typeString = TCon "string"
-typePath   = TCon "path"
-typeNull   = TCon "null"
+typeFun :: NonEmpty Type -> Type
+typeFun (head_ :| tail_) = foldr (:~>) head_ tail_
diff --git a/src/Nix/Unused.hs b/src/Nix/Unused.hs
new file mode 100644
--- /dev/null
+++ b/src/Nix/Unused.hs
@@ -0,0 +1,82 @@
+{-# language FunctionalDependencies #-}
+{-# language TemplateHaskell #-}
+
+{-# options_ghc -Wno-missing-signatures #-}
+
+-- | This module holds unused code.
+-- So, if someone wants something - look here, use it & move to appropriate place.
+module Nix.Unused
+ where
+
+import           Control.Monad.Free             ( Free(..) )
+import           Data.Fix                       ( Fix(..) )
+import           Lens.Family2.TH                ( makeLensesBy )
+
+-- * From "Nix.Utils"
+
+-- | > type AlgM f m a = f a -> m a
+type AlgM f m a = f a -> m a
+
+whenFree :: (Monoid b)
+  => (f (Free f a) -> b) -> Free f a -> b
+whenFree =
+  free
+    mempty
+{-# inline whenFree #-}
+
+whenPure :: (Monoid b)
+  => (a -> b) -> Free f a -> b
+whenPure f =
+  free
+    f
+    mempty
+{-# inline whenPure #-}
+
+-- | Replace:
+--  @Pure a -> a@
+--  @Free -> Fix@
+freeToFix :: Functor f => (a -> Fix f) -> Free f a -> Fix f
+freeToFix f = go
+ where
+  go =
+    free
+      f
+      $ Fix . (go <$>)
+
+-- | Replace:
+--  @a -> Pure a@
+--  @Fix -> Free@
+fixToFree :: Functor f => Fix f -> Free f a
+fixToFree = Free . go
+ where
+  go (Fix f) = Free . go <$> f
+
+
+loeb :: Functor f => f (f a -> a) -> f a
+loeb x = go
+ where
+  go = ($ go) <$> x
+
+adiM
+  :: ( Traversable t
+     , Monad m
+     )
+  => Transform t (m a)
+  -> AlgM t m a
+  -> Fix t
+  -> m a
+adiM g f = g $ f <=< traverse (adiM g f) . unFix
+
+para :: Functor f => (f (Fix f, a) -> a) -> Fix f -> a
+para f = f . fmap (id &&& para f) . unFix
+
+paraM :: (Traversable f, Monad m) => (f (Fix f, a) -> m a) -> Fix f -> m a
+paraM f = f <=< traverse (\x -> (x, ) <$> paraM f x) . unFix
+
+cataP :: Functor f => (Fix f -> f a -> a) -> Fix f -> a
+cataP f x = f x . fmap (cataP f) . unFix $ x
+
+cataPM :: (Traversable f, Monad m) => (Fix f -> f a -> m a) -> Fix f -> m a
+cataPM f x = f x <=< traverse (cataPM f) . unFix $ x
+
+$(makeLensesBy (\n -> pure $ "_" <> n) ''Fix)
diff --git a/src/Nix/Utils.hs b/src/Nix/Utils.hs
--- a/src/Nix/Utils.hs
+++ b/src/Nix/Utils.hs
@@ -1,56 +1,168 @@
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE FunctionalDependencies #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TemplateHaskell #-}
+{-# language NoImplicitPrelude #-}
+{-# language CPP #-}
+{-# language GeneralizedNewtypeDeriving #-}
 
-{-# OPTIONS_GHC -Wno-missing-signatures #-}
+-- | This is a module of custom "Prelude" code.
+-- It is for import for projects other then @HNix@.
+-- For @HNix@ - this module gets reexported by "Prelude", so for @HNix@ please fix-up pass-through there.
+module Nix.Utils
+  ( KeyMap
+  , TransformF
+  , Transform
+  , Alg
 
-module Nix.Utils (module Nix.Utils, module X) where
+  , Path(..)
+  , isAbsolute
+  , (</>)
+  , joinPath
+  , splitDirectories
+  , takeDirectory
+  , takeFileName
+  , takeBaseName
+  , takeExtension
+  , takeExtensions
+  , addExtension
+  , dropExtensions
+  , replaceExtension
 
+  , Has(..)
+  , trace
+  , traceM
+  , stub
+  , pass
+  , whenTrue
+  , whenFalse
+  , list
+  , whenText
+  , free
+  , whenJust
+  , dup
+  , mapPair
+  , both
+  , readFile
+  , traverseM
+  , lifted
+  , loebM
+  , adi
+  , module X
+  )
+ where
+
+import           Relude                  hiding ( pass
+                                                , force
+                                                , readFile
+                                                , whenJust
+                                                , whenNothing
+                                                , trace
+                                                , traceM
+                                                )
+
+import           Data.Binary                    ( Binary )
+import           Data.Data                      ( Data )
+import           Codec.Serialise                ( Serialise )
 import           Control.Monad.Fix              ( MonadFix(..) )
 import           Control.Monad.Free             ( Free(..) )
 import           Control.Monad.Trans.Control    ( MonadTransControl(..) )
 import qualified Data.Aeson                    as A
-import qualified Data.Aeson.Encoding           as A
-#if MIN_VERSION_aeson(2,0,0)
-import qualified Data.Aeson.KeyMap             as AKM
-#else
-import qualified Data.HashMap.Strict           as HM
-#endif
 import           Data.Fix                       ( Fix(..) )
-import qualified Data.HashMap.Lazy             as M
 import qualified Data.Text                     as Text
-import qualified Data.Vector                   as V
-import           Lens.Family2                  as X hiding ((&))
+import qualified Data.Text.IO                 as Text
+import           Lens.Family2                  as X
+                                                ( view
+                                                , over
+                                                , LensLike'
+                                                , Lens'
+                                                )
 import           Lens.Family2.Stock             ( _1
                                                 , _2
                                                 )
-import           Lens.Family2.TH                ( makeLensesBy )
+import qualified System.FilePath              as FilePath
 
 #if ENABLE_TRACING
-import           Debug.Trace as X
+import qualified Relude.Debug                 as X
 #else
 -- Well, since it is currently CPP intermingled with Debug.Trace, required to use String here.
 trace :: String -> a -> a
 trace = const id
 {-# inline trace #-}
 traceM :: Monad m => String -> m ()
-traceM = const pass
+traceM = const stub
 {-# inline traceM #-}
 #endif
 
-$(makeLensesBy (\n -> pure $ "_" <> n) ''Fix)
+-- | To have explicit type boundary between FilePath & String.
+newtype Path = Path FilePath
+  deriving
+    ( Eq, Ord, Generic
+    , Typeable, Data, NFData, Serialise, Binary, A.ToJSON, A.FromJSON
+    , Show, Read, Hashable
+    , Semigroup, Monoid
+    )
 
+instance ToText Path where
+  toText = toText @String . coerce
+
+instance IsString Path where
+  fromString = coerce
+
+-- This set of @Path@ funcs is to control system filepath types & typesafety and to easy migrate from FilePath to anything suitable (like @path@ or so).
+
+-- | @isAbsolute@ specialized to @Path@.
+isAbsolute :: Path -> Bool
+isAbsolute = coerce FilePath.isAbsolute
+
+-- | @(</>)@ specialized to @Path@
+(</>) :: Path -> Path -> Path
+(</>) = coerce (FilePath.</>)
+infixr 5 </>
+
+-- | @joinPath@ specialized to @Path@
+joinPath :: [Path] -> Path
+joinPath = coerce FilePath.joinPath
+
+-- | @splitDirectories@ specialized to @Path@
+splitDirectories :: Path -> [Path]
+splitDirectories = coerce FilePath.splitDirectories
+
+-- | @takeDirectory@ specialized to @Path@
+takeDirectory :: Path -> Path
+takeDirectory = coerce FilePath.takeDirectory
+
+-- | @takeFileName@ specialized to @Path@
+takeFileName :: Path -> Path
+takeFileName = coerce FilePath.takeFileName
+
+-- | @takeBaseName@ specialized to @Path@
+takeBaseName :: Path -> String
+takeBaseName = coerce FilePath.takeBaseName
+
+-- | @takeExtension@ specialized to @Path@
+takeExtension :: Path -> String
+takeExtension = coerce FilePath.takeExtensions
+
+-- | @takeExtensions@ specialized to @Path@
+takeExtensions :: Path -> String
+takeExtensions = coerce FilePath.takeExtensions
+
+addExtension :: Path -> String -> Path
+addExtension = coerce FilePath.addExtension
+
+-- | @dropExtensions@ specialized to @Path@
+dropExtensions :: Path -> Path
+dropExtensions = coerce FilePath.dropExtensions
+
+-- | @replaceExtension@ specialized to @Path@
+replaceExtension :: Path -> String -> Path
+replaceExtension = coerce FilePath.replaceExtension
+
+
 -- | > Hashmap Text -- type synonym
-type AttrSet = HashMap Text
+type KeyMap = HashMap Text
 
 -- | F-algebra defines how to reduce the fixed-point of a functor to a value.
 -- > type Alg f a = f a -> a
 type Alg f a = f a -> a
 
--- | > type AlgM f m a = f a -> m a
-type AlgM f m a = f a -> m a
-
 -- | Do according transformation.
 --
 -- It is a transformation of a recursion scheme.
@@ -63,28 +175,25 @@
 -- You got me, it is a natural transformation.
 type TransformF f a = (f -> a) -> f -> a
 
-loeb :: Functor f => f (f a -> a) -> f a
-loeb x = go
- where
-  go = ($ go) <$> x
+class Has a b where
+  hasLens :: Lens' a b
 
+instance Has a a where
+  hasLens f = f
+
+instance Has (a, b) a where
+  hasLens = _1
+
+instance Has (a, b) b where
+  hasLens = _2
+
 loebM :: (MonadFix m, Traversable t) => t (t a -> m a) -> m (t a)
 -- Sectioning here insures optimization happening.
 loebM f = mfix $ \a -> (`traverse` f) ($ a)
 {-# inline loebM #-}
 
-para :: Functor f => (f (Fix f, a) -> a) -> Fix f -> a
-para f = f . fmap (id &&& para f) . unFix
-
-paraM :: (Traversable f, Monad m) => (f (Fix f, a) -> m a) -> Fix f -> m a
-paraM f = f <=< traverse (\x -> (x, ) <$> paraM f x) . unFix
-
-cataP :: Functor f => (Fix f -> f a -> a) -> Fix f -> a
-cataP f x = f x . fmap (cataP f) . unFix $ x
-
-cataPM :: (Traversable f, Monad m) => (Fix f -> f a -> m a) -> Fix f -> m a
-cataPM f x = f x <=< traverse (cataPM f) . unFix $ x
-
+--  2021-08-21: NOTE: Someone needs to put in normal words, what this does.
+-- This function is pretty spefic & used only once, in "Nix.Normal".
 lifted
   :: (MonadTransControl u, Monad (u m), Monad m)
   => ((a -> m (StT u b)) -> m (StT u b))
@@ -95,29 +204,12 @@
     lftd <- liftWith (\run -> f (run . k))
     restoreT $ pure lftd
 
--- | Replace:
---  @Pure a -> a@
---  @Free -> Fix@
-freeToFix :: Functor f => (a -> Fix f) -> Free f a -> Fix f
-freeToFix f = go
- where
-  go =
-    free
-      f
-      $ Fix . (go <$>)
-
--- | Replace:
---  @a -> Pure a@
---  @Fix -> Free@
-fixToFree :: Functor f => Fix f -> Free f a
-fixToFree = Free . go
- where
-  go (Fix f) = Free . go <$> f
-
 -- | adi is Abstracting Definitional Interpreters:
 --
 --     https://arxiv.org/abs/1707.04755
 --
+--   All ADI does is interleaves every layer of evaluation by inserting intermitten layers between them, in that way the evaluation can be extended/embelished in any way wanted. Look at its use to see great examples.
+--
 --   Essentially, it does for evaluation what recursion schemes do for
 --   representation: allows threading layers through existing structure, only
 --   in this case through behavior.
@@ -129,72 +221,7 @@
   -> a
 adi g f = g $ f . (adi g f <$>) . unFix
 
-adiM
-  :: ( Traversable t
-     , Monad m
-     )
-  => Transform t (m a)
-  -> AlgM t m a
-  -> Fix t
-  -> m a
-adiM g f = g $ f <=< traverse (adiM g f) . unFix
 
-
-class Has a b where
-  hasLens :: Lens' a b
-
-instance Has a a where
-  hasLens f = f
-
-instance Has (a, b) a where
-  hasLens = _1
-
-instance Has (a, b) b where
-  hasLens = _2
-
-toEncodingSorted :: A.Value -> A.Encoding
-toEncodingSorted = \case
-  A.Object m ->
-    A.pairs
-      . mconcat
-      . ((\(k, v) -> A.pair k $ toEncodingSorted v) <$>)
-      . sortWith fst $
-#if MIN_VERSION_aeson(2,0,0)
-          AKM.toList
-#else
-          HM.toList
-#endif
-          m
-  A.Array l -> A.list toEncodingSorted $ V.toList l
-  v         -> A.toEncoding v
-
-data NixPathEntryType = PathEntryPath | PathEntryURI deriving (Show, Eq)
-
--- | @NIX_PATH@ is colon-separated, but can also contain URLs, which have a colon
--- (i.e. @https://...@)
-uriAwareSplit :: Text -> [(Text, NixPathEntryType)]
-uriAwareSplit txt =
-  case Text.break (== ':') txt of
-    (e1, e2)
-      | Text.null e2                              -> [(e1, PathEntryPath)]
-      | "://" `Text.isPrefixOf` e2      ->
-        let ((suffix, _) : path) = uriAwareSplit (Text.drop 3 e2) in
-        (e1 <> "://" <> suffix, PathEntryURI) : path
-      | otherwise                                 -> (e1, PathEntryPath) : uriAwareSplit (Text.drop 1 e2)
-
-alterF
-  :: (Eq k, Hashable k, Functor f)
-  => (Maybe v -> f (Maybe v))
-  -> k
-  -> HashMap k v
-  -> f (HashMap k v)
-alterF f k m =
-  maybe
-    (M.delete k m)
-    (\ v -> M.insert k v m)
-    <$> f (M.lookup k m)
-
-
 -- | Analog for @bool@ or @maybe@, for list-like cons structures.
 list
   :: Foldable t
@@ -206,6 +233,14 @@
     (null l)
 {-# inline list #-}
 
+whenText
+  :: a -> (Text -> a) -> Text -> a
+whenText e f t =
+  bool
+    (f t)
+    e
+    (Text.null t)
+
 -- | Lambda analog of @maybe@ or @either@ for Free monad.
 free :: (a -> b) -> (f (Free f a) -> b) -> Free f a -> b
 free fP fF fr =
@@ -230,20 +265,15 @@
     mempty
 {-# inline whenFalse #-}
 
-whenFree :: (Monoid b)
-  => (f (Free f a) -> b) -> Free f a -> b
-whenFree =
-  free
-    mempty
-{-# inline whenFree #-}
-
-whenPure :: (Monoid b)
-  => (a -> b) -> Free f a -> b
-whenPure f =
-  free
-    f
+whenJust
+  :: Monoid b
+  => (a -> b)
+  -> Maybe a
+  -> b
+whenJust =
+  maybe
     mempty
-{-# inline whenPure #-}
+{-# inline whenJust #-}
 
 
 -- | Apply a single function to both components of a pair.
@@ -271,3 +301,23 @@
 stub :: (Applicative f, Monoid a) => f a
 stub = pure mempty
 {-# inline stub #-}
+
+-- | Alias for @stub@, since @Relude@ has more specialized @pure ()@.
+pass :: (Applicative f) => f ()
+pass = stub
+{-# inline pass #-}
+
+readFile :: Path -> IO Text
+readFile = Text.readFile . coerce
+
+traverseM
+  :: ( Applicative m
+     , Applicative f
+     , Traversable t
+     )
+  => ( a
+     -> m (f b)
+     )
+  -> t a
+  -> m (f (t b))
+traverseM f x = sequenceA <$> traverse f x
diff --git a/src/Nix/Utils/Fix1.hs b/src/Nix/Utils/Fix1.hs
--- a/src/Nix/Utils/Fix1.hs
+++ b/src/Nix/Utils/Fix1.hs
@@ -1,9 +1,9 @@
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE PolyKinds #-}
-{-# LANGUAGE UndecidableInstances #-}
+{-# language TypeFamilies #-}
+{-# language ConstraintKinds #-}
+{-# language CPP #-}
+{-# language GeneralizedNewtypeDeriving #-}
+{-# language PolyKinds #-}
+{-# language UndecidableInstances #-}
 
 module Nix.Utils.Fix1 where
 
@@ -87,8 +87,11 @@
   type Ref (Fix1T t m) = Ref m
 
   newRef  = lift . newRef
+  {-# inline newRef #-}
   readRef = lift . readRef
+  {-# inline readRef #-}
   writeRef r = lift . writeRef r
+  {-# inline writeRef #-}
 
 
 instance
@@ -98,6 +101,7 @@
   => MonadAtomicRef (Fix1T t m)
  where
   atomicModifyRef r = lift . atomicModifyRef r
+  {-# inline atomicModifyRef #-}
 
 {-
 
diff --git a/src/Nix/Value.hs b/src/Nix/Value.hs
--- a/src/Nix/Value.hs
+++ b/src/Nix/Value.hs
@@ -1,22 +1,18 @@
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE DeriveAnyClass #-}
-{-# LANGUAGE KindSignatures #-}
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE PatternSynonyms #-}
-{-# LANGUAGE RankNTypes #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TemplateHaskell #-}
-{-# LANGUAGE ViewPatterns #-}
+{-# language CPP #-}
+{-# language DeriveAnyClass #-}
+{-# language KindSignatures #-}
+{-# language ConstraintKinds #-}
+{-# language PatternSynonyms #-}
+{-# language RankNTypes #-}
+{-# language TemplateHaskell #-}
 
-{-# OPTIONS_GHC -Wno-missing-signatures #-}
-{-# OPTIONS_GHC -Wno-missing-pattern-synonym-signatures #-}
+{-# options_ghc -Wno-missing-signatures #-}
+{-# options_ghc -Wno-missing-pattern-synonym-signatures #-}
 
 -- | The core of the type system, Nix language values
 module Nix.Value
 where
 
-import           Prelude                 hiding ( force )
-import           Nix.Utils
 import           Control.Comonad                ( Comonad
                                                 , extract
                                                 )
@@ -36,13 +32,12 @@
                                                 , showString
                                                 , showParen
                                                 )
-import           Lens.Family2.Stock             ( _1 )
+import           Lens.Family2.Stock             ( _2 )
 import           Lens.Family2.TH                ( makeTraversals
                                                 , makeLenses
                                                 )
 import           Nix.Atoms
 import           Nix.Expr.Types
-import           Nix.Expr.Types.Annotated
 import           Nix.String
 import           Nix.Thunk
 
@@ -118,14 +113,13 @@
      -- | A string has a value and a context, which can be used to record what a
      -- string has been build from
     | NVStrF NixString
-    | NVPathF FilePath
+    | NVPathF Path
     | NVListF [r]
-    --  2021-05-22: NOTE: Please flip this and dependent functions.
-    -- Quite frequently actions/processing happens with values
-    -- (for example - forcing of values & recreation of the monad),
-    -- but SourcePos does not change then
-    -- That would be good to flip all 'AttrSet.* AttrSet SourcePos'
-    | NVSetF (AttrSet r) (AttrSet SourcePos)
+    | NVSetF PositionSet (AttrSet r)
+      -- ^
+      --   Quite frequently actions/processing happens with values
+      --   (for example - forcing of values & recreation of the monad),
+      --   but @SourcePos@ does not change then.
     | NVClosureF (Params ()) (p -> m r)
       -- ^ A function is a closed set of parameters representing the "call
       --   signature", used at application time to check the type of arguments
@@ -138,7 +132,7 @@
       --   Note that 'm r' is being used here because effectively a function
       --   and its set of default arguments is "never fully evaluated". This
       --   enforces in the type that it must be re-evaluated for each call.
-    | NVBuiltinF Text (p -> m r)
+    | NVBuiltinF VarName (p -> m r)
       -- ^ A builtin function is itself already in normal form. Also, it may
       --   or may not choose to evaluate its argument in the production of a
       --   result.
@@ -151,8 +145,8 @@
   liftEq _  (NVConstantF x) (NVConstantF y) = x == y
   liftEq _  (NVStrF      x) (NVStrF      y) = x == y
   liftEq eq (NVListF     x) (NVListF     y) = liftEq eq x y
-  liftEq eq (NVSetF x _   ) (NVSetF y _   ) = liftEq eq x y
-  liftEq _  (NVPathF x    ) (NVPathF y    ) = x == y
+  liftEq eq (NVSetF  _   x) (NVSetF _    y) = liftEq eq x y
+  liftEq _  (NVPathF     x) (NVPathF     y) = x == y
   liftEq _  _               _               = False
 
 
@@ -166,7 +160,7 @@
       (NVConstantF atom     ) -> showsCon1 "NVConstant" atom
       (NVStrF      ns       ) -> showsCon1 "NVStr"      (stringIgnoreContext ns)
       (NVListF     lst      ) -> showsCon1 "NVList"     lst
-      (NVSetF      attrs  _ ) -> showsCon1 "NVSet"      attrs
+      (NVSetF      _   attrs) -> showsCon1 "NVSet"      attrs
       (NVClosureF  params _ ) -> showsCon1 "NVClosure"  params
       (NVPathF     path     ) -> showsCon1 "NVPath"     path
       (NVBuiltinF  name   _ ) -> showsCon1 "NVBuiltin"  name
@@ -185,7 +179,7 @@
     NVStrF      _  -> mempty
     NVPathF     _  -> mempty
     NVListF     l  -> foldMap f l
-    NVSetF     s _ -> foldMap f s
+    NVSetF     _ s -> foldMap f s
     NVClosureF _ _ -> mempty
     NVBuiltinF _ _ -> mempty
 
@@ -203,11 +197,7 @@
   NVStrF      s  -> pure $ NVStrF s
   NVPathF     p  -> pure $ NVPathF p
   NVListF     l  -> NVListF <$> sequenceA l
-  NVSetF     s p ->
-    liftA2
-      NVSetF
-      (sequenceA s)
-      (pure p)
+  NVSetF     p s -> NVSetF p <$> sequenceA s
   NVClosureF p g -> pure $ NVClosureF p (transform <=< g)
   NVBuiltinF s g -> pure $ NVBuiltinF s (transform <=< g)
 
@@ -226,11 +216,7 @@
   NVStrF      s  -> pure $ NVStrF s
   NVPathF     p  -> pure $ NVPathF p
   NVListF     l  -> NVListF <$> traverse f l
-  NVSetF     s p ->
-    liftA2
-      NVSetF
-      (traverse f s)
-      (pure p)
+  NVSetF     p s -> NVSetF p <$> traverse f s
   NVClosureF p g -> pure $ NVClosureF p (transform . f <=< g)
   NVBuiltinF s g -> pure $ NVBuiltinF s (transform . f <=< g)
 
@@ -268,13 +254,13 @@
     --   , NVStrF s
     --   , NVPathF p
     --   , NVListF l
-    --   , NVSetF s p
+    --   , NVSetF p s
     --   ]
     NVConstantF a  -> NVConstantF a
     NVStrF      s  -> NVStrF s
     NVPathF     p  -> NVPathF p
     NVListF     l  -> NVListF l
-    NVSetF     s p -> NVSetF s p
+    NVSetF     p s -> NVSetF p s
     NVBuiltinF s g -> NVBuiltinF s (lft . g)
     NVClosureF p g -> NVClosureF p (lft . g)
 {-# inline hoistNValueF #-}
@@ -303,7 +289,7 @@
     NVStr' ns ->
       showsUnaryWith showsPrec "NVStrF" p (stringIgnoreContext ns)
     NVList' lst       -> showsUnaryWith (liftShowsPrec sp sl) "NVListF" p lst
-    NVSet' attrs _    -> showsUnaryWith (liftShowsPrec sp sl) "NVSetF" p attrs
+    NVSet'  _   attrs -> showsUnaryWith (liftShowsPrec sp sl) "NVSetF" p attrs
     NVPath' path      -> showsUnaryWith showsPrec "NVPathF" p path
     NVClosure' c    _ -> showsUnaryWith showsPrec "NVClosureF" p c
     NVBuiltin' name _ -> showsUnaryWith showsPrec "NVBuiltinF" p name
@@ -330,7 +316,7 @@
   NVStrF      s  -> NVStrF s
   NVPathF     p  -> NVPathF p
   NVListF     l  -> NVListF l
-  NVSetF     s p -> NVSetF s p
+  NVSetF     p s -> NVSetF p s
   NVClosureF p g -> NVClosureF p (g . f)
   NVBuiltinF s g -> NVBuiltinF s (g . f)
 
@@ -429,11 +415,11 @@
 nvStr' = NValue' . pure . NVStrF
 
 
--- | Haskell @FilePath@ to the Nix path,
+-- | Haskell @Path@ to the Nix path,
 nvPath' :: Applicative f
-  => FilePath
+  => Path
   -> NValue' t f m r
-nvPath' = NValue' . pure . NVPathF
+nvPath' = NValue' . pure . NVPathF . coerce
 
 
 -- | Haskell @[]@ to the Nix @[]@,
@@ -445,10 +431,11 @@
 
 -- | Haskell key-value to the Nix key-value,
 nvSet' :: Applicative f
-  => AttrSet SourcePos
+  => PositionSet
   -> AttrSet r
   -> NValue' t f m r
-nvSet' x s = NValue' $ pure $ NVSetF s x
+--  2021-07-16: NOTE: that the arguments are flipped.
+nvSet' p s = NValue' $ pure $ NVSetF p s
 
 
 -- | Haskell closure to the Nix closure,
@@ -463,7 +450,7 @@
 
 -- | Haskell functions to the Nix functions!
 nvBuiltin' :: (Applicative f, Functor m)
-  => Text
+  => VarName
   -> (NValue t f m -> m r)
   -> NValue' t f m r
 nvBuiltin' name f = NValue' $ pure $ NVBuiltinF name f
@@ -485,10 +472,10 @@
 pattern NVStr' ns <- NValue' (extract -> NVStrF ns)
 pattern NVPath' x <- NValue' (extract -> NVPathF x)
 pattern NVList' l <- NValue' (extract -> NVListF l)
-pattern NVSet' s x <- NValue' (extract -> NVSetF s x)
+pattern NVSet' p s <- NValue' (extract -> NVSetF p s)
 pattern NVClosure' x f <- NValue' (extract -> NVClosureF x f)
 pattern NVBuiltin' name f <- NValue' (extract -> NVBuiltinF name f)
-{-# COMPLETE NVConstant', NVStr', NVPath', NVList', NVSet', NVClosure', NVBuiltin' #-}
+{-# complete NVConstant', NVStr', NVPath', NVList', NVSet', NVClosure', NVBuiltin' #-}
 
 
 -- * @__NValue__@: Nix language values
@@ -610,12 +597,12 @@
 nvStrWithoutContext :: Applicative f
   => Text
   -> NValue t f m
-nvStrWithoutContext = nvStr . makeNixStringWithoutContext
+nvStrWithoutContext = nvStr . mkNixStringWithoutContext
 
 
 -- | Life of a Haskell FilePath to the life of a Nix path
 nvPath :: Applicative f
-  => FilePath
+  => Path
   -> NValue t f m
 nvPath = Free . nvPath'
 
@@ -627,11 +614,10 @@
 
 
 nvSet :: Applicative f
-  => AttrSet SourcePos
+  => PositionSet
   -> AttrSet (NValue t f m)
   -> NValue t f m
-nvSet x s = Free $ nvSet' x s
-
+nvSet p s = Free $ nvSet' p s
 
 nvClosure :: (Applicative f, Functor m)
   => Params ()
@@ -643,7 +629,7 @@
 
 
 nvBuiltin :: (Applicative f, Functor m)
-  => Text
+  => VarName
   -> (NValue t f m
     -> m (NValue t f m)
     )
@@ -654,7 +640,7 @@
 builtin
   :: forall m f t
    . (MonadThunk t m (NValue t f m), MonadDataContext f m)
-  => Text
+  => VarName
   -> ( NValue t f m
     -> m (NValue t f m)
     )
@@ -664,7 +650,7 @@
 
 builtin2
   :: (MonadThunk t m (NValue t f m), MonadDataContext f m)
-  => Text
+  => VarName
   -> ( NValue t f m
     -> NValue t f m
     -> m (NValue t f m)
@@ -675,7 +661,7 @@
 
 builtin3
   :: (MonadThunk t m (NValue t f m), MonadDataContext f m)
-  => Text
+  => VarName
   -> ( NValue t f m
     -> NValue t f m
     -> NValue t f m
@@ -689,7 +675,7 @@
 
 pattern NVThunk t <- Pure t
 pattern NVValue v <- Free v
-{-# COMPLETE NVThunk, NVValue #-}
+{-# complete NVThunk, NVValue #-}
 pattern NVConstant x <- Free (NVConstant' x)
 pattern NVStr ns <- Free (NVStr' ns)
 pattern NVPath x <- Free (NVPath' x)
@@ -697,29 +683,37 @@
 pattern NVSet s x <- Free (NVSet' s x)
 pattern NVClosure x f <- Free (NVClosure' x f)
 pattern NVBuiltin name f <- Free (NVBuiltin' name f)
-{-# COMPLETE NVThunk, NVConstant, NVStr, NVPath, NVList, NVSet, NVClosure, NVBuiltin #-}
+{-# complete NVThunk, NVConstant, NVStr, NVPath, NVList, NVSet, NVClosure, NVBuiltin #-}
 
 
 
 -- * @TStringContext@
 
 data TStringContext = NoContext | HasContext
-  deriving Show
+ deriving Show
 
+instance Semigroup TStringContext where
+  (<>) NoContext NoContext = NoContext
+  (<>) _         _         = HasContext
+
+
+instance Monoid TStringContext where
+  mempty = NoContext
+
 -- * @ValueType@
 
 data ValueType
-    = TInt
-    | TFloat
-    | TBool
-    | TNull
-    | TString TStringContext
-    | TList
-    | TSet
-    | TClosure
-    | TPath
-    | TBuiltin
-    deriving Show
+  = TInt
+  | TFloat
+  | TBool
+  | TNull
+  | TString TStringContext
+  | TList
+  | TSet
+  | TClosure
+  | TPath
+  | TBuiltin
+ deriving Show
 
 
 -- | Determine type of a value
@@ -728,17 +722,14 @@
   \case
     NVConstantF a ->
       case a of
-        NURI   _ -> TString NoContext
+        NURI   _ -> TString mempty
         NInt   _ -> TInt
         NFloat _ -> TFloat
         NBool  _ -> TBool
         NNull    -> TNull
     NVStrF ns  ->
       TString $
-        bool
-          NoContext
-          HasContext
-          (stringHasContext ns)
+        HasContext `whenTrue` stringHasContext ns
     NVListF{}    -> TList
     NVSetF{}     -> TSet
     NVClosureF{} -> TClosure
@@ -754,8 +745,8 @@
     TFloat             -> "a float"
     TBool              -> "a boolean"
     TNull              -> "a null"
-    TString NoContext  -> "a string"
-    TString HasContext -> "a string with context"
+    TString NoContext  -> "a string with no context"
+    TString HasContext -> "a string"
     TList              -> "a list"
     TSet               -> "an attr set"
     TClosure           -> "a function"
@@ -822,4 +813,4 @@
   :: (Traversable f, Applicative g)
   => VarName
   -> LensLike' g (NValue' t f m a) (Maybe a)
-key k = nValue . traverse . _NVSetF . _1 . hashAt k
+key k = nValue . traverse . _NVSetF . _2 . hashAt k
diff --git a/src/Nix/Value/Equal.hs b/src/Nix/Value/Equal.hs
--- a/src/Nix/Value/Equal.hs
+++ b/src/Nix/Value/Equal.hs
@@ -1,17 +1,12 @@
-{-# LANGUAGE AllowAmbiguousTypes #-}
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE ViewPatterns #-}
+{-# language AllowAmbiguousTypes #-}
+{-# language ConstraintKinds #-}
+{-# language TypeFamilies #-}
 
-{-# OPTIONS_GHC -Wno-missing-pattern-synonym-signatures #-}
+{-# options_ghc -Wno-missing-pattern-synonym-signatures #-}
 
 module Nix.Value.Equal where
 
-import           Prelude                 hiding ( Comparison
-                                                , force
-                                                )
-import           Nix.Utils
+import           Prelude                 hiding ( Comparison )
 import           Control.Comonad                ( Comonad(extract))
 import           Control.Monad.Free             ( Free(Pure,Free) )
 import           Control.Monad.Trans.Except     ( throwE )
@@ -25,6 +20,7 @@
 import           Nix.String
 import           Nix.Thunk
 import           Nix.Value
+import           Nix.Expr.Types                 ( AttrSet )
 
 checkComparable
   :: ( Framed e m
@@ -35,12 +31,12 @@
   -> m ()
 checkComparable x y =
   case (x, y) of
-    (NVConstant (NFloat _), NVConstant (NInt   _)) -> pass
-    (NVConstant (NInt   _), NVConstant (NFloat _)) -> pass
-    (NVConstant (NInt   _), NVConstant (NInt   _)) -> pass
-    (NVConstant (NFloat _), NVConstant (NFloat _)) -> pass
-    (NVStr       _        , NVStr       _        ) -> pass
-    (NVPath      _        , NVPath      _        ) -> pass
+    (NVConstant (NFloat _), NVConstant (NInt   _)) -> stub
+    (NVConstant (NInt   _), NVConstant (NFloat _)) -> stub
+    (NVConstant (NInt   _), NVConstant (NInt   _)) -> stub
+    (NVConstant (NFloat _), NVConstant (NFloat _)) -> stub
+    (NVStr       _        , NVStr       _        ) -> stub
+    (NVPath      _        , NVPath      _        ) -> stub
     _                                              -> throwError $ Comparison x y
 
 -- | Checks whether two containers are equal, using the given item equality
@@ -79,15 +75,16 @@
 isDerivationM f m =
   maybe
     (pure False)
-    (\ t ->
-      maybe
-        -- We should probably really make sure the context is empty here
-        -- but the C++ implementation ignores it.
-        False
-        ((==) "derivation" . stringIgnoreContext)
-        <$> f t
-    )
+    p
     (HashMap.Lazy.lookup "type" m)
+ where
+  p t =
+    maybe
+      -- We should probably really make sure the context is empty here
+      -- but the C++ implementation ignores it.
+      False
+      ((==) "derivation" . stringIgnoreContext)
+      <$> f t
 
 isDerivation
   :: Monad m
@@ -119,7 +116,7 @@
       (NVConstantF lc        , NVConstantF rc        ) -> pure $            lc == rc
       (NVStrF      ls        , NVStrF      rs        ) -> pure $  (\ i -> i ls == i rs) stringIgnoreContext
       (NVListF     ls        , NVListF     rs        ) ->          alignEqM eq ls rs
-      (NVSetF      lm _      , NVSetF      rm _      ) ->          attrsEq lm rm
+      (NVSetF      _      lm , NVSetF      _      rm ) ->          attrsEq lm rm
       (NVPathF     lp        , NVPathF     rp        ) ->             pure $ lp == rp
       _                                                -> pure False
 
diff --git a/src/Nix/Var.hs b/src/Nix/Var.hs
--- a/src/Nix/Var.hs
+++ b/src/Nix/Var.hs
@@ -1,9 +1,8 @@
-{-# LANGUAGE AllowAmbiguousTypes #-}
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE ScopedTypeVariables #-}
+{-# language AllowAmbiguousTypes #-}
+{-# language ConstraintKinds #-}
 
-{-# OPTIONS_GHC -Wno-orphans #-}
-{-# OPTIONS_GHC -Wno-unused-top-binds #-}
+{-# options_ghc -Wno-orphans #-}
+{-# options_ghc -Wno-unused-top-binds #-}
 
 module Nix.Var ()
 where
diff --git a/src/Nix/XML.hs b/src/Nix/XML.hs
--- a/src/Nix/XML.hs
+++ b/src/Nix/XML.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE ScopedTypeVariables #-}
 
 module Nix.XML
   ( toXML )
@@ -21,13 +20,14 @@
  where
   cyc = pure $ mkEVal "string" "<expr>"
 
+  pp :: Element -> Text
   pp e =
     heading
-    <> toText
+    <> fromString
         (ppElement $
           mkE
             "expr"
-            [Elem e]
+            (one $ Elem e)
         )
     <> "\n"
    where
@@ -38,80 +38,73 @@
     NVConstant' a ->
       pure $
         case a of
-          NURI   t -> mkEVal "string" $ toString t
+          NURI   t -> mkEVal "string" t
           NInt   n -> mkEVal "int"    $ show n
           NFloat f -> mkEVal "float"  $ show f
           NBool  b -> mkEVal "bool"   $ if b then "true" else "false"
           NNull    -> mkE    "null"     mempty
 
     NVStr' str ->
-      mkEVal "string" . toString <$> extractNixString str
+      mkEVal "string" <$> extractNixString str
     NVList' l ->
-      do
-        els <- sequence l
-        pure $
-          mkE
-            "list"
-            (Elem <$> els)
+      mkE "list" . fmap Elem <$> sequenceA l
 
-    NVSet' s _ ->
-      do
-        kvs <- sequence s
-        pure $
-          mkE
-            "attrs"
-            ((\ (k, v) ->
-                Elem $
-                  Element
-                    (unqual "attr")
-                    [Attr (unqual "name") (toString k)]
-                    [Elem v]
-                    Nothing
-              ) <$>
-                sortWith fst (M.toList kvs)
-            )
+    NVSet' _ s ->
+      mkE
+        "attrs"
+        . fmap
+            mkElem'
+            . sortWith fst . M.toList
+        <$> sequenceA s
+     where
+      mkElem' :: (VarName, Element) -> Content
+      mkElem' (k, v) =
+        Elem $
+          Element
+            (unqual "attr")
+            (one $ Attr (unqual "name") $ toString k)
+            (one $ Elem v)
+            Nothing
 
     NVClosure' p _ ->
       pure $
         mkE
           "function"
           (paramsXML p)
-    NVPath' fp        -> pure $ mkEVal "path" fp
-    NVBuiltin' name _ -> pure $ mkEName "function" $ toString name
+    NVPath' fp        -> pure $ mkEVal "path" $ fromString $ coerce fp
+    NVBuiltin' name _ -> pure $ mkEName "function" name
 
-mkE :: String -> [Content] -> Element
-mkE n c =
+mkE :: Text -> [Content] -> Element
+mkE (toString -> n) c =
   Element
     (unqual n)
     mempty
     c
     Nothing
 
-mkElem :: String -> String -> String -> Element
-mkElem n a v =
+mkElem :: Text -> Text -> Text -> Element
+mkElem (toString -> n) (toString -> a) (toString -> v) =
   Element
     (unqual n)
-    [Attr (unqual a) v]
+    (one $ Attr (unqual a) v)
     mempty
     Nothing
 
-mkEVal :: String -> String -> Element
+mkEVal :: Text -> Text -> Element
 mkEVal = (`mkElem` "value")
 
-mkEName :: String -> String -> Element
-mkEName = (`mkElem` "name")
+mkEName :: Text -> VarName -> Element
+mkEName x (coerce -> y) = (`mkElem` "name") x y
 
 paramsXML :: Params r -> [Content]
-paramsXML (Param name) = [Elem $ mkEName "varpat" (toString name)]
-paramsXML (ParamSet s b mname) =
-  [Elem $ Element (unqual "attrspat") (battr <> nattr) (paramSetXML s) Nothing]
+paramsXML (Param name) = one $ Elem $ mkEName "varpat" name
+paramsXML (ParamSet mname variadic pset) =
+  one $ Elem $ Element (unqual "attrspat") (battr <> nattr) (paramSetXML pset) Nothing
  where
-  battr = [ Attr (unqual "ellipsis") "1" | b ]
+  battr =
+    one (Attr (unqual "ellipsis") "1") `whenTrue` (variadic == Variadic)
   nattr =
-    maybe
-      mempty
-      ((: mempty) . Attr (unqual "name") . toString)
-      mname
+    (one . Attr (unqual "name") . toString) `whenJust` mname
 
 paramSetXML :: ParamSet r -> [Content]
-paramSetXML = fmap (\(k, _) -> Elem $ mkEName "attr" (toString k))
+paramSetXML = fmap (Elem . mkEName "attr" . fst)
diff --git a/src/Prelude.hs b/src/Prelude.hs
new file mode 100644
--- /dev/null
+++ b/src/Prelude.hs
@@ -0,0 +1,20 @@
+-- | This is a @Prelude@, but, please, do not put things in here,
+-- put them into "Nix.Utils". This module is a pass-through-multiplexer,
+-- between our custom code ("Nix.Utils") that shadows over the outside prelude that is in use ("Relude")
+-- "Prelude" module has a problem of being imported & used by other projects.
+-- "Nix.Utils" as a module with a regular name does not have that problem.
+module Prelude
+    ( module Nix.Utils
+    , module Relude
+    ) where
+
+import           Nix.Utils
+import           Relude                  hiding ( pass
+                                                , force
+                                                , readFile
+                                                , whenJust
+                                                , whenNothing
+                                                , trace
+                                                , traceM
+                                                )
+
diff --git a/tests/EvalTests.hs b/tests/EvalTests.hs
--- a/tests/EvalTests.hs
+++ b/tests/EvalTests.hs
@@ -1,13 +1,11 @@
-{-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TemplateHaskell #-}
+{-# language QuasiQuotes #-}
+{-# language TemplateHaskell #-}
 
-{-# OPTIONS_GHC -Wno-missing-signatures #-}
+{-# options_ghc -Wno-missing-signatures #-}
 
 
 module EvalTests (tests, genEvalCompareTests) where
 
-import           Prelude hiding (lookupEnv)
 import           Control.Monad.Catch
 import           Data.List ((\\))
 import qualified Data.Set as S
@@ -17,136 +15,189 @@
 import           Nix.Standard
 import           Nix.TH
 import           Nix.Value.Equal
-import           Nix.Utils
 import qualified System.Directory as D
-import           System.Environment (lookupEnv)
-import           System.FilePath
 import           Test.Tasty
 import           Test.Tasty.HUnit
 import           Test.Tasty.TH
 import           TestCommon
 
 case_basic_sum =
-    constantEqualText "2" "1 + 1"
+  constantEqualText
+    "2"
+    "1 + 1"
 
 case_basic_div =
-    constantEqualText "3" "builtins.div 6 2"
+  constantEqualText
+    "3"
+    "builtins.div 6 2"
 
-case_zero_div = do
-  assertNixEvalThrows "builtins.div 1 0"
-  assertNixEvalThrows "builtins.div 1.0 0"
-  assertNixEvalThrows "builtins.div 1 0.0"
-  assertNixEvalThrows "builtins.div 1.0 0.0"
+case_zero_div =
+  traverse_ assertNixEvalThrows
+    [ "builtins.div 1 0"
+    , "builtins.div 1.0 0"
+    , "builtins.div 1 0.0"
+    , "builtins.div 1.0 0.0"
+    ]
 
-case_bit_ops = do
-    -- mic92 (2018-08-20): change to constantEqualText,
-    -- when hnix's nix fork supports bitAnd/bitOr/bitXor
-    constantEqualText' "0" "builtins.bitAnd 1 0"
-    constantEqualText' "1" "builtins.bitOr 1 1"
-    constantEqualText' "3" "builtins.bitXor 1 2"
+case_bit_ops =
+  traverse_ (uncurry constantEqualText)
+    [ ("0", "builtins.bitAnd 1 0")
+    , ("1", "builtins.bitOr 1 1")
+    , ("3", "builtins.bitXor 1 2")
+    ]
 
 case_basic_function =
-    constantEqualText "2" "(a: a) 2"
+  constantEqualText
+    "2"
+    "(a: a) 2"
 
 case_set_attr =
-    constantEqualText "2" "{ a = 2; }.a"
+  constantEqualText
+    "2"
+    "{ a = 2; }.a"
 
 case_function_set_arg =
-    constantEqualText "2" "({ a }: 2) { a = 1; }"
+  constantEqualText
+    "2"
+    "({ a }: 2) { a = 1; }"
 
 case_function_set_two_arg =
-    constantEqualText "2" "({ a, b ? 3 }: b - a) { a = 1; }"
+  constantEqualText
+    "2"
+    "({ a, b ? 3 }: b - a) { a = 1; }"
 
 case_function_set_two_arg_default_scope =
-    constantEqualText "2" "({ x ? 1, y ? x * 3 }: y - x) {}"
+  constantEqualText
+    "2"
+    "({ x ? 1, y ? x * 3 }: y - x) {}"
 
 case_function_default_env =
-    constantEqualText "2" "let default = 2; in ({ a ? default }: a) {}"
+  constantEqualText
+    "2"
+    "let default = 2; in ({ a ? default }: a) {}"
 
 case_function_definition_uses_environment =
-    constantEqualText "3" "let f = (let a=1; in x: x+a); in f 2"
+  constantEqualText
+    "3"
+    "let f = (let a=1; in x: x+a); in f 2"
 
 case_function_atpattern =
-    -- jww (2018-05-09): This should be constantEqualText
-    constantEqualText' "2" "(({a}@attrs:attrs) {a=2;}).a"
+  -- jww (2018-05-09): This should be constantEqualText
+  constantEqualText'
+    "2"
+    "(({a}@attrs:attrs) {a=2;}).a"
 
 case_function_ellipsis =
-    -- jww (2018-05-09): This should be constantEqualText
-    constantEqualText' "2" "(({a, ...}@attrs:attrs) {a=0; b=2;}).b"
+  -- jww (2018-05-09): This should be constantEqualText
+  constantEqualText'
+    "2"
+    "(({a, ...}@attrs:attrs) {a=0; b=2;}).b"
 
 case_function_default_value_not_in_atpattern =
-    constantEqualText "false" "({a ? 2}@attrs: attrs ? a) {}"
+  constantEqualText
+    "false"
+    "({a ? 2}@attrs: attrs ? a) {}"
 
 case_function_arg_shadowing =
-    constantEqualText "6" "(y: y: x: x: x + y) 1 2 3 4"
+  constantEqualText
+    "6"
+    "(y: y: x: x: x + y) 1 2 3 4"
 
 case_function_recursive_args =
-    constantEqualText "2" "({ x ? 1, y ? x * 3}: y - x) {}"
+  constantEqualText
+    "2"
+    "({ x ? 1, y ? x * 3}: y - x) {}"
 
 case_function_recursive_sets =
-    constantEqualText "[ [ 6 4 100 ] 4 ]" [text|
-        let x = rec {
+  constantEqualText
+    "[ [ 6 4 100 ] 4 ]"
+    [text|
+      let x = rec {
 
-          y = 2;
-          z = { w = 4; };
-          v = rec {
-            u = 6;
-            t = [ u z.w s ];
-          };
+        y = 2;
+        z = { w = 4; };
+        v = rec {
+          u = 6;
+          t = [ u z.w s ];
+        };
 
-        }; s = 100; in [ x.v.t x.z.w ]
+      }; s = 100; in [ x.v.t x.z.w ]
     |]
 
 case_nested_with =
-    constantEqualText "2" "with { x = 1; }; with { x = 2; }; x"
+  constantEqualText
+    "2"
+    "with { x = 1; }; with { x = 2; }; x"
 
 case_with_strictness =
-    constantEqualText "5" "let x = with x; with { a = 5; }; a; in x"
+  constantEqualText
+    "5"
+    "let x = with x; with { a = 5; }; a; in x"
 
 case_match_failure_null =
-    constantEqualText "null" "builtins.match \"ab\" \"abc\""
+  constantEqualText
+    "null"
+    "builtins.match \"ab\" \"abc\""
 
 case_find_file_success_no_prefix =
-    constantEqualText "./tests/files/findFile.nix"
-                      "builtins.findFile [{ path=\"./tests/files\"; prefix=\"\"; }] \"findFile.nix\""
+  constantEqualText
+    "./tests/files/findFile.nix"
+    "builtins.findFile [{ path=\"./tests/files\"; prefix=\"\"; }] \"findFile.nix\""
 
 case_find_file_success_with_prefix =
-    constantEqualText "./tests/files/findFile.nix"
-                      "builtins.findFile [{ path=\"./tests/files\"; prefix=\"nix\"; }] \"nix/findFile.nix\""
+  constantEqualText
+    "./tests/files/findFile.nix"
+    "builtins.findFile [{ path=\"./tests/files\"; prefix=\"nix\"; }] \"nix/findFile.nix\""
 
 case_find_file_success_folder =
-    constantEqualText "./tests/files"
-                      "builtins.findFile [{ path=\"./tests\"; prefix=\"\"; }] \"files\""
+  constantEqualText
+    "./tests/files"
+    "builtins.findFile [{ path=\"./tests\"; prefix=\"\"; }] \"files\""
 
 case_find_file_failure_not_found =
-    assertNixEvalThrows "builtins.findFile [{ path=\"./tests/files\"; prefix=\"\"; }] \"not_found.nix\""
+  assertNixEvalThrows
+    "builtins.findFile [{ path=\"./tests/files\"; prefix=\"\"; }] \"not_found.nix\""
 
 case_find_file_failure_invalid_arg_1 =
-    assertNixEvalThrows "builtins.findFile 1 \"files\""
+  assertNixEvalThrows
+    "builtins.findFile 1 \"files\""
 
 case_find_file_failure_invalid_arg_2 =
-    assertNixEvalThrows "builtins.findFile [{ path=\"./tests/files\"; prefix=\"\"; }] 2"
+  assertNixEvalThrows
+    "builtins.findFile [{ path=\"./tests/files\"; prefix=\"\"; }] 2"
 
 case_find_file_failure_invalid_arg_no_path =
-    assertNixEvalThrows "builtins.findFile [{ prefix=\"\"; }] \"files\""
+  assertNixEvalThrows
+    "builtins.findFile [{ prefix=\"\"; }] \"files\""
 
 case_infinite_recursion =
-    assertNixEvalThrows "let foo = a: bar a; bar = a: foo a; in foo 3"
+  assertNixEvalThrows
+    "let foo = a: bar a; bar = a: foo a; in foo 3"
 
 case_nested_let =
-    constantEqualText "3" "let a = 3; x.x = 2; in a"
+  constantEqualText
+    "3"
+    "let a = 3; x.x = 2; in a"
 
 case_nested_nested_let =
-    constantEqualText "3" "let a = 3; x.x = let b = a; in b; c = x.x; in c"
+  constantEqualText
+    "3"
+    "let a = 3; x.x = let b = a; in b; c = x.x; in c"
 
 case_inherit_in_rec_set =
-    constantEqualText "1" "let x = 1; in (rec { inherit x; }).x"
+  constantEqualText
+    "1"
+    "let x = 1; in (rec { inherit x; }).x"
 
 case_lang_version =
-    constantEqualText "5" "builtins.langVersion"
+  constantEqualText
+    "5"
+    "builtins.langVersion"
 
 case_rec_set_attr_path_simpl =
-    constantEqualText "123" [text|
+  constantEqualText
+    "123"
+    [text|
       let x = rec {
         foo.number = 123;
         foo.function = y: foo.number;
@@ -154,7 +205,9 @@
     |]
 
 case_inherit_from_set_has_no_scope =
-    constantEqualText' "false" [text|
+  constantEqualText'
+    "false"
+    [text|
       (builtins.tryEval (
         let x = 1;
             y = { z = 2; };
@@ -163,7 +216,7 @@
     |]
 
 -- github/orblivion (2018-08-05): Adding these failing tests so we fix this feature
-
+--
 -- case_overrides =
 --     constantEqualText' "2" [text|
 --       let
@@ -190,171 +243,198 @@
 --       }.__overrides.a)
 --     |]
 
-case_unsafegetattrpos1 =
-    constantEqualText "[ 5 14 ]" [text|
-      let e = 1;
-          f = 1;
-          t = {};
-          s = {
-            inherit t e f;
-            a = 1;
-            "b" = 2;
-            c.d = 3;
-          };
-          p = builtins.unsafeGetAttrPos "e" s; in
-      [ p.line p.column ]
-    |]
+case_unsafegetattrpos =
+  traverse_ (uncurry constantEqualText)
+    [ ( "[ 5 14 ]"
+      , [text|
+          let e = 1;
+              f = 1;
+              t = {};
+              s = {
+                inherit t e f;
+                a = 1;
+                "b" = 2;
+                c.d = 3;
+              };
+              p = builtins.unsafeGetAttrPos "e" s; in
+          [ p.line p.column ]
+          |]
+      )
+    , ( "[ 5 14 ]"
+      , [text|
+          let e = 1;
+              f = 1;
+              t = {};
+              s = {
+                inherit t e f;
+                a = 1;
+                "b" = 2;
+                c.d = 3;
+              };
+              p = builtins.unsafeGetAttrPos "f" s; in
+          [ p.line p.column ]
+        |]
+      )
+    , ( "[ 6 7 ]"
+      , [text|
+          let e = 1;
+              f = 1;
+              t = {};
+              s = {
+                inherit t e f;
+                a = 1;
+                "b" = 2;
+                c.d = 3;
+              };
+              p = builtins.unsafeGetAttrPos "a" s; in
+            [ p.line p.column ]
+          |]
+      )
+    , ( "[ 7 7 ]"
+      , [text|
+        let e = 1;
+            f = 1;
+            t = {};
+            s = {
+              inherit t e f;
+              a = 1;
+              "b" = 2;
+              c.d = 3;
+            };
+            p = builtins.unsafeGetAttrPos "b" s; in
+          [ p.line p.column ]
+        |]
+      )
+    -- jww (2018-05-09): These two are failing but they shouldn't be
+    --
+    -- , ( "[ 7 13 ]"
+    --   , [text|
+    --       let e = 1;
+    --           f = 1;
+    --           t = {};
+    --           s = {
+    --             inherit t e f;
+    --             a = 1;
+    --             "b" = 2;
+    --             c.d = 3;
+    --           };
+    --           p = builtins.unsafeGetAttrPos "c.d" s; in
+    --         [ p.line p.column ]
+    --       |]
+    --   )
 
-case_unsafegetattrpos2 =
-    constantEqualText "[ 5 14 ]" [text|
-      let e = 1;
-          f = 1;
-          t = {};
-          s = {
-            inherit t e f;
-            a = 1;
-            "b" = 2;
-            c.d = 3;
-          };
-          p = builtins.unsafeGetAttrPos "f" s; in
-      [ p.line p.column ]
-    |]
+    -- , ( "[ 7 13 ]"
+    --   , [text|
+    --       let e = 1;
+    --           f = 1;
+    --           t = {};
+    --           s = {
+    --             inherit t e f;
+    --             a = 1;
+    --             "b" = 2;
+    --             c.d = 3;
+    --           };
+    --           p = builtins.unsafeGetAttrPos "d" s; in
+    --         [ p.line p.column ]
+    --       |]
+    --   )
+    ]
 
-case_unsafegetattrpos3 =
-    constantEqualText "[ 6 7 ]" [text|
-      let e = 1;
-          f = 1;
-          t = {};
-          s = {
-            inherit t e f;
-            a = 1;
-            "b" = 2;
-            c.d = 3;
-          };
-          p = builtins.unsafeGetAttrPos "a" s; in
-      [ p.line p.column ]
+case_fixed_points =
+  constantEqualText
+    [text|
+      [
+        {
+          foobar = "foobar";
+          foo = "foo";
+          bar = "bar";
+        }
+        {
+          foobar = "foo + bar";
+          foo = "foo + ";
+          bar = "bar";
+        }
+      ]
     |]
-
-case_unsafegetattrpos4 =
-    constantEqualText "[ 7 7 ]" [text|
-      let e = 1;
-          f = 1;
-          t = {};
-          s = {
-            inherit t e f;
-            a = 1;
-            "b" = 2;
-            c.d = 3;
-          };
-          p = builtins.unsafeGetAttrPos "b" s; in
-      [ p.line p.column ]
+    [text|
+      let
+        fix = f: let x = f x; in x;
+        extends = f: rattrs: self:
+          let super = rattrs self; in super // f self super;
+        f = self: { foo = "foo";
+                    bar = "bar";
+                    foobar = self.foo + self.bar; };
+        g = self: super: { foo = super.foo + " + "; };
+      in [ (fix f) (fix (extends g f)) ]
     |]
 
--- jww (2018-05-09): These two are failing but they shouldn't be
-
--- case_unsafegetattrpos5 =
---     constantEqualText "[ 7 13 ]" [text|
---       let e = 1;
---           f = 1;
---           t = {};
---           s = {
---             inherit t e f;
---             a = 1;
---             "b" = 2;
---             c.d = 3;
---           };
---           p = builtins.unsafeGetAttrPos "c.d" s; in
---       [ p.line p.column ]
---     |]
-
--- case_unsafegetattrpos6 =
---     constantEqualText "[ 7 13 ]" [text|
---       let e = 1;
---           f = 1;
---           t = {};
---           s = {
---             inherit t e f;
---             a = 1;
---             "b" = 2;
---             c.d = 3;
---           };
---           p = builtins.unsafeGetAttrPos "d" s; in
---       [ p.line p.column ]
---     |]
-
-case_fixed_points =
-    constantEqualText [text|[
-  {
-    foobar = "foobar";
-    foo = "foo";
-    bar = "bar";
-  }
-  {
-    foobar = "foo + bar";
-    foo = "foo + ";
-    bar = "bar";
-  }
-]|] [text|
-    let
-      fix = f: let x = f x; in x;
-      extends = f: rattrs: self:
-        let super = rattrs self; in super // f self super;
-      f = self: { foo = "foo";
-                  bar = "bar";
-                  foobar = self.foo + self.bar; };
-      g = self: super: { foo = super.foo + " + "; };
-    in [ (fix f) (fix (extends g f)) ]
-|]
-
 case_fixed_points_and_fold =
-    constantEqualText [text|[ {} {} ]|] [text|
-let
-  extends = f: rattrs: self:
-    let super = rattrs self; in super // f self super;
-  flip = f: a: b: f b a;
-  toFixFold = builtins.foldl' (flip extends) (self: {}) ([(self: super: {})]);
-  toFix = extends (self: super: {}) (self: {});
-  fix = f: let x = f x; in x;
-in [ (fix toFixFold) (fix toFix) ]
-|]
+  constantEqualText
+    [text|
+      [ {} {} ]
+    |]
+    [text|
+      let
+        extends = f: rattrs: self:
+          let super = rattrs self; in super // f self super;
+        flip = f: a: b: f b a;
+        toFixFold = builtins.foldl' (flip extends) (self: {}) ([(self: super: {})]);
+        toFix = extends (self: super: {}) (self: {});
+        fix = f: let x = f x; in x;
+      in [ (fix toFixFold) (fix toFix) ]
+    |]
 
 case_fixed_points_attrsets =
-    constantEqualText "{ x = { y = { z = 100; }; z = { y = 100; }; }; }" [text|
+  constantEqualText
+    "{ x = { y = { z = 100; }; z = { y = 100; }; }; }"
+    [text|
       let fix = f: let x = f x; in x;
           f = self: { x.z.y = 100; x.y.z = self.x.z.y; };
       in fix f
     |]
 
--- case_function_equals1 =
---     constantEqualText "true" "{f = x: x;} == {f = x: x;}"
-
--- case_function_equals2 =
---     constantEqualText "true" "[(x: x)] == [(x: x)]"
-
-case_function_equals3 =
-    constantEqualText "false" "(let a = (x: x); in a == a)"
-
-case_function_equals4 =
-    constantEqualText "true" "(let a = {f = x: x;}; in a == a)"
-
-case_function_equals5 =
-    constantEqualText "true" "(let a = [(x: x)]; in a == a)"
+case_function_equals =
+  traverse_ (uncurry constantEqualText)
+    [ -- ( "true"
+      -- , "{f = x: x;} == {f = x: x;}"
+      -- )
+      -- ( "true"
+      -- , "[(x: x)] == [(x: x)]"
+      -- )
+      ( "false"
+      , "(let a = (x: x); in a == a)"
+      )
+    , ( "true"
+      , "(let a = {f = x: x;}; in a == a)"
+      )
+    , ( "true"
+      , "(let a = [(x: x)]; in a == a)"
+      )
+    , ( "false"
+      , "builtins.pathExists \"/var/empty/invalid-directory\""
+      )
+    ]
 
 case_directory_pathexists =
-    constantEqualText "false" "builtins.pathExists \"/var/empty/invalid-directory\""
+  constantEqualText
+    "false"
+    "builtins.pathExists \"/var/empty/invalid-directory\""
 
 -- jww (2018-05-02): This constantly changes!
--- case_placeholder =
---   constantEqualText
---       "\"/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9\""
---       "builtins.placeholder \"out\""
+case_placeholder =
+  constantEqualText
+    "\"/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9\""
+    "builtins.placeholder \"out\""
 
 case_rec_path_attr =
-    constantEqualText "10"
-        "let src = 10; x = rec { passthru.src = src; }; in x.passthru.src"
+  constantEqualText
+    "10"
+    "let src = 10; x = rec { passthru.src = src; }; in x.passthru.src"
 
 case_mapattrs_builtin =
-    constantEqualText' "{ a = \"afoo\"; b = \"bbar\"; }" [text|
+  constantEqualText'
+    "{ a = \"afoo\"; b = \"bbar\"; }"
+    [text|
       (builtins.mapAttrs (x: y: x + y) {
         a = "foo";
         b = "bar";
@@ -363,10 +443,18 @@
 
 -- Regression test for #373
 case_regression_373 :: Assertion
-case_regression_373 = do
-  freeVarsEqual "{ inherit a; }" ["a"]
-  freeVarsEqual "rec {inherit a; }" ["a"]
-  freeVarsEqual "let inherit a; in { }" ["a"]
+case_regression_373 =
+  traverse_ (uncurry freeVarsEqual)
+    [ ( "{ inherit a; }"
+      , one "a"
+      )
+    , ("rec {inherit a; }"
+      , one "a"
+      )
+    , ( "let inherit a; in { }"
+      , one "a"
+      )
+    ]
 
 case_expression_split =
   constantEqualText
@@ -374,91 +462,145 @@
     "(x: builtins.deepSeq x x) (builtins.split \"(a)b\" \"abc\")"
 
 case_empty_string_equal_null_is_false =
-  constantEqualText "false" "\"\" == null"
+  constantEqualText
+    "false"
+    "\"\" == null"
 
 case_null_equal_empty_string_is_false =
-  constantEqualText "false" "null == \"\""
+  constantEqualText
+    "false"
+    "null == \"\""
 
 case_empty_string_not_equal_null_is_true =
-  constantEqualText "true" "\"\" != null"
+  constantEqualText
+    "true"
+    "\"\" != null"
 
 case_null_equal_not_empty_string_is_true =
-  constantEqualText "true" "null != \"\""
+  constantEqualText
+    "true"
+    "null != \"\""
 
 case_list_nested_bottom_diverges =
-  assertNixEvalThrows "let nested = [(let x = x; in x)]; in nested == nested"
+  assertNixEvalThrows
+    "let nested = [(let x = x; in x)]; in nested == nested"
 
 case_attrset_nested_bottom_diverges =
-  assertNixEvalThrows "let nested = { y = (let x = x; in x); }; in nested == nested"
+  assertNixEvalThrows
+    "let nested = { y = (let x = x; in x); }; in nested == nested"
 
 case_list_list_nested_bottom_equal =
-  constantEqualText "true" "let nested = [[(let x = x; in x)]]; in nested == nested"
+  constantEqualText
+    "true"
+    "let nested = [[(let x = x; in x)]]; in nested == nested"
 
 case_list_attrset_nested_bottom_equal =
-  constantEqualText "true" "let nested = [{ y = (let x = x; in x); }]; in nested == nested"
+  constantEqualText
+    "true"
+    "let nested = [{ y = (let x = x; in x); }]; in nested == nested"
 
 case_list_function_nested_bottom_equal =
-  constantEqualText "true" "let nested = [(_: let x = x; in x)]; in nested == nested"
+  constantEqualText
+    "true"
+    "let nested = [(_: let x = x; in x)]; in nested == nested"
 
 case_attrset_list_nested_bottom_equal =
-  constantEqualText "true" "let nested = { y = [(let x = x; in x)];}; in nested == nested"
+  constantEqualText
+    "true"
+    "let nested = { y = [(let x = x; in x)];}; in nested == nested"
 
 case_attrset_attrset_nested_bottom_equal =
-  constantEqualText "true" "let nested = { y = { y = (let x = x; in x); }; }; in nested == nested"
+  constantEqualText
+    "true"
+    "let nested = { y = { y = (let x = x; in x); }; }; in nested == nested"
 
 case_attrset_function_nested_bottom_equal =
-  constantEqualText "true" "let nested = { y = _: (let x = x; in x); }; in nested == nested"
+  constantEqualText
+    "true"
+    "let nested = { y = _: (let x = x; in x); }; in nested == nested"
 
 -- Regression test for #527
 
 case_add_string_thunk_left =
-  constantEqualText [text|"cygwin"|] [text|builtins.head ["cyg"] + "win"|]
+  constantEqualText
+    [text|
+      "cygwin"
+    |]
+    [text|
+      builtins.head ["cyg"] + "win"
+    |]
 
 case_add_string_thunk_right =
-  constantEqualText [text|"cygwin"|] [text|"cyg" + builtins.head ["win"]|]
+  constantEqualText
+    [text|
+      "cygwin"
+    |]
+    [text|
+      "cyg" + builtins.head ["win"]
+    |]
 
 case_add_int_thunk_left =
-  constantEqualText "3" "builtins.head [1] + 2"
+  constantEqualText
+    "3"
+    "builtins.head [1] + 2"
 
 case_add_int_thunk_right =
-  constantEqualText "3" "1 + builtins.head [2]"
+  constantEqualText
+    "3"
+    "1 + builtins.head [2]"
 
 case_concat_thunk_left =
-  constantEqualText "[1 2 3]" "builtins.tail [0 1 2] ++ [3]"
+  constantEqualText
+    "[1 2 3]"
+    "builtins.tail [0 1 2] ++ [3]"
 
 case_concat_thunk_rigth =
-  constantEqualText "[1 2 3]" "[1] ++ builtins.tail [1 2 3]"
+  constantEqualText
+    "[1 2 3]"
+    "[1] ++ builtins.tail [1 2 3]"
 
 ---------------------------------------------------------------------------------
 
 tests :: TestTree
 tests = $testGroupGenerator
 
-genEvalCompareTests = do
-    td <- D.listDirectory testDir
+genEvalCompareTests =
+  do
+    (coerce -> files :: [Path]) <- D.listDirectory $ coerce testDir
 
-    let unmaskedFiles = filter ((==".nix") . takeExtension) td
-    let files = unmaskedFiles \\ maskedFiles
+    let
+      unmaskedFiles :: [Path]
+      unmaskedFiles = filter ((== ".nix") . takeExtension) files
 
-    pure $ testGroup "Eval comparison tests" $ fmap (mkTestCase testDir) files
+      testFiles :: [Path]
+      testFiles = unmaskedFiles \\ maskedFiles
+
+    pure $ testGroup "Eval comparison tests" $ fmap (mkTestCase testDir) testFiles
   where
-    mkTestCase td f = testCase f $ assertEvalFileMatchesNix (td </> f)
+    mkTestCase :: Path -> Path -> TestTree
+    mkTestCase dir f = testCase (coerce f :: TestName) $ assertEvalFileMatchesNix $ dir </> f
 
 constantEqual :: NExprLoc -> NExprLoc -> Assertion
-constantEqual expected actual = do
+constantEqual expected actual =
+  do
     time <- getCurrentTime
     let opts = defaultOptions time
     -- putStrLn =<< lint (stripAnnotation a)
-    (eq, expectedNF, actualNF) <- runWithBasicEffectsIO opts $ do
-        expectedNF <- normalForm =<< nixEvalExprLoc mempty expected
-        actualNF <- normalForm =<< nixEvalExprLoc mempty actual
-        eq <- valueEqM expectedNF actualNF
-        pure (eq, expectedNF, actualNF)
-    let message =
-                "Inequal normal forms:\n"
-            <>  "Expected: " <> printNix expectedNF <> "\n"
-            <>  "Actual:   " <> printNix actualNF
-    assertBool message eq
+    (eq, expectedNF, actualNF) <-
+      runWithBasicEffectsIO opts $
+        do
+          expectedNF <- getNormForm expected
+          actualNF <- getNormForm actual
+          eq <- valueEqM expectedNF actualNF
+          pure (eq, expectedNF, actualNF)
+    let
+      message =
+        "Inequal normal forms:\n"
+        <> "Expected: " <> printNix expectedNF <> "\n"
+        <>  "Actual:   " <> printNix actualNF
+    assertBool (toString message) eq
+ where
+  getNormForm = normalForm <=< nixEvalExprLoc mempty
 
 constantEqualText' :: Text -> Text -> Assertion
 constantEqualText' expected actual =
@@ -467,11 +609,11 @@
     constantEqual expected' actual'
 
 constantEqualText :: Text -> Text -> Assertion
-constantEqualText expected actual = do
-  constantEqualText' expected actual
-  mres <- liftIO $ lookupEnv "ALL_TESTS" <|> lookupEnv "MATCHING_TESTS"
-  when (isJust mres) $
-      assertEvalMatchesNix actual
+constantEqualText expected actual =
+  do
+    constantEqualText' expected actual
+    mres <- liftIO $ on (<|>) lookupEnv "ALL_TESTS" "MATCHING_TESTS"
+    whenJust (const $ assertEvalMatchesNix actual) mres
 
 assertNixEvalThrows :: Text -> Assertion
 assertNixEvalThrows a =
@@ -497,11 +639,10 @@
       Right a' = parseNixText a
       xs' = S.fromList xs
       free' = freeVars a'
-    assertEqual "" xs' free'
+    assertEqual mempty xs' free'
 
-maskedFiles :: [FilePath]
-maskedFiles =
-  [ "builtins.fetchurl-01.nix" ]
+maskedFiles :: [Path]
+maskedFiles = one "builtins.fetchurl-01.nix"
 
-testDir :: FilePath
+testDir :: Path
 testDir = "tests/eval-compare"
diff --git a/tests/Main.hs b/tests/Main.hs
--- a/tests/Main.hs
+++ b/tests/Main.hs
@@ -1,8 +1,8 @@
-{-# LANGUAGE QuasiQuotes #-}
+{-# language QuasiQuotes #-}
 
 module Main where
 
-import           Prelude hiding (lookupEnv)
+import           Relude (force)
 import           Relude.Unsafe (read)
 import qualified Control.Exception as Exc
 import           GHC.Err (errorWithoutStackTrace)
@@ -25,9 +25,9 @@
 import qualified ReduceExprTests
 import qualified PrettyParseTests
 import           System.Directory
-import           System.Environment (setEnv, lookupEnv)
-import           System.FilePath.Glob
-import           System.Posix.Files
+import           System.Environment (setEnv)
+import           System.FilePath.Glob (compile, globDir1)
+import           System.PosixCompat.Files
 import           Test.Tasty
 import           Test.Tasty.HUnit
 
@@ -43,9 +43,9 @@
 ensureNixpkgsCanParse :: Assertion
 ensureNixpkgsCanParse =
   consider "default.nix" (parseNixFile "default.nix") $ \case
-    Fix (NAbs (ParamSet params _ _) _) -> do
-      let rev    = getString "rev" params
-          sha256 = getString "sha256" params
+    Fix (NAbs (ParamSet _ _ pset) _) -> do
+      let rev    = getString "rev" pset
+          sha256 = getString "sha256" pset
       consider "fetchTarball expression" (pure $ parseNixTextLoc [text|
         builtins.fetchTarball {
           url    = "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz";
@@ -64,26 +64,27 @@
         when (null files) $
           errorWithoutStackTrace $
             "Directory " <> show dir <> " does not have any files"
-        for_ files $ \file -> do
+        for_ files $ \file ->
           unless ("azure-cli/default.nix" `isSuffixOf` file ||
                   "os-specific/linux/udisks/2-default.nix"  `isSuffixOf` file) $ do
             -- Parse and deepseq the resulting expression tree, to ensure the
             -- parser is fully executed.
-            _ <- consider file (parseNixFileLoc file) $ Exc.evaluate . force
-            pass
+            _ <- consider (coerce file) (parseNixFileLoc (coerce file)) $ Exc.evaluate . force
+            stub
     v -> fail $ "Unexpected parse from default.nix: " <> show v
  where
-  getExpr   k m = let Just (Just r) = lookup k m in r
+  getExpr   k m =
+    let Just (Just r) = lookup k m in
+    r
   getString k m =
-      let Fix (NStr (DoubleQuoted [Plain str])) = getExpr k m in str
+    let Fix (NStr (DoubleQuoted [Plain str])) = getExpr k m in
+    str
 
   consider path action k =
-    do
-      x <- action
-      either
-        (\ err -> errorWithoutStackTrace $ "Parsing " <> path <> " failed: " <> show err)
-        k
-        x
+    either
+      (\ err -> errorWithoutStackTrace $ "Parsing " <> coerce @Path path <> " failed: " <> show err)
+      k
+      =<< action
 
 main :: IO ()
 main = do
@@ -94,18 +95,18 @@
   prettyTestsEnv      <- lookupEnv "PRETTY_TESTS"
 
   pwd <- getCurrentDirectory
-  setEnv "NIX_REMOTE" (pwd <> "/real-store")
-  setEnv "NIX_DATA_DIR" (pwd <> "/data")
+  setEnv "NIX_REMOTE" $ pwd <> "/real-store"
+  setEnv "NIX_DATA_DIR" $ pwd <> "/data"
 
   defaultMain $ testGroup "hnix" $
     [ ParserTests.tests
     , EvalTests.tests
     , PrettyTests.tests
-    , ReduceExprTests.tests] <>
-    [ PrettyParseTests.tests
-        (fromIntegral (read (fromMaybe "0" prettyTestsEnv) :: Int)) ] <>
-    [ evalComparisonTests ] <>
-    [ testCase "Nix language tests present" ensureLangTestsPresent
+    , ReduceExprTests.tests
+    , PrettyParseTests.tests $ fromIntegral $ read @Int $ fromMaybe "0" prettyTestsEnv
+    , evalComparisonTests
+    , testCase "Nix language tests present" ensureLangTestsPresent
     , nixLanguageTests ] <>
     [ testCase "Nixpkgs parses without errors" ensureNixpkgsCanParse
-      | isJust nixpkgsTestsEnv ]
+      | isJust nixpkgsTestsEnv
+    ]
diff --git a/tests/NixLanguageTests.hs b/tests/NixLanguageTests.hs
--- a/tests/NixLanguageTests.hs
+++ b/tests/NixLanguageTests.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE ScopedTypeVariables #-}
 
 module NixLanguageTests (genTests) where
 
@@ -11,7 +10,6 @@
 import qualified Data.Set                      as Set
 import qualified Data.String                   as String
 import qualified Data.Text                     as Text
-import qualified Data.Text.IO                  as Text
 import           Data.Time
 import           GHC.Exts
 import           Nix.Lint
@@ -22,8 +20,7 @@
 import           Nix.String
 import           Nix.XML
 import qualified Options.Applicative           as Opts
-import           System.Environment
-import           System.FilePath
+import           System.Environment             ( setEnv )
 import           System.FilePath.Glob           ( compile
                                                 , globDir1
                                                 )
@@ -61,118 +58,173 @@
 newFailingTests = Set.fromList
   [ "eval-okay-hash"
   , "eval-okay-hashfile"
-  , "eval-okay-path"
+  , "eval-okay-path"  -- #128
   , "eval-okay-types"
   , "eval-okay-fromTOML"
-  , "eval-okay-context-introspection"
   ]
 
+-- | Upstream tests that test cases that HNix disaded as a misfeature that is used so rarely
+-- that it more effective to fix it & lint it out of existance.
+deprecatedRareNixQuirkTests :: Set String
+deprecatedRareNixQuirkTests = Set.fromList $
+  one
+    -- A rare quirk of Nix that is proper to fix&enforce then to support (see git commit history)
+    "eval-okay-strings-as-attrs-names"
+
 genTests :: IO TestTree
-genTests = do
-  testFiles <-
-    sort
-        -- jww (2018-05-07): Temporarily disable this test until #128 is fixed.
-    .   filter ((`Set.notMember` newFailingTests) . takeBaseName)
-    .   filter ((/= ".xml") . takeExtension)
-    <$> globDir1 (compile "*-*-*.*") "data/nix/tests/lang"
-  let testsByName = groupBy (takeFileName . dropExtensions) testFiles
-  let testsByType = groupBy testType (Map.toList testsByName)
-  let testGroups  = fmap mkTestGroup (Map.toList testsByType)
-  pure $ localOption (mkTimeout 2000000) $ testGroup
-    "Nix (upstream) language tests"
-    testGroups
+genTests =
+  do
+    testFiles <- getTestFiles
+    let
+      testsGroupedByName :: Map Path [Path]
+      testsGroupedByName = groupBy (takeFileName . dropExtensions) testFiles
+
+      testsGroupedByTypeThenName :: Map [String] [(Path, [Path])]
+      testsGroupedByTypeThenName = groupBy testType $ Map.toList testsGroupedByName
+
+      testTree :: [TestTree]
+      testTree = mkTestGroup <$> Map.toList testsGroupedByTypeThenName
+
+    pure $
+      localOption
+        (mkTimeout 2000000)
+        $ testGroup
+            "Nix (upstream) language tests"
+            testTree
  where
-  testType (fullpath, _files) = take 2 $ splitOn "-" $ takeFileName fullpath
-  mkTestGroup (kind, tests) =
-    testGroup (String.unwords kind) $ fmap (mkTestCase kind) tests
-  mkTestCase kind (basename, files) = testCase (takeFileName basename) $ do
-    time <- liftIO getCurrentTime
-    let opts = defaultOptions time
-    case kind of
-      ["parse", "okay"] -> assertParse opts $ the files
-      ["parse", "fail"] -> assertParseFail opts $ the files
-      ["eval" , "okay"] -> assertEval opts files
-      ["eval" , "fail"] -> assertEvalFail $ the files
-      _                 -> fail $ "Unexpected: " <> show kind
 
-assertParse :: Options -> FilePath -> Assertion
+  getTestFiles :: IO [Path]
+  getTestFiles = sortTestFiles <$> collectTestFiles
+   where
+    collectTestFiles :: IO [Path]
+    collectTestFiles = coerce (globDir1 (compile "*-*-*.*") nixTestDir)
+
+    sortTestFiles :: [Path] -> [Path]
+    sortTestFiles =
+      sort
+        -- Disabling the not yet done tests cases.
+        . filter withoutDisabledTests
+        . filter withoutXml
+     where
+      withoutDisabledTests :: Path -> Bool
+      withoutDisabledTests = (`Set.notMember` (newFailingTests `Set.union` deprecatedRareNixQuirkTests)) . takeBaseName
+
+      withoutXml :: Path -> Bool
+      withoutXml = (/= ".xml") . takeExtension
+
+  testType :: (Path, b) -> [String]
+  testType (fullpath, _files) = coerce (take 2 . splitOn "-") $ takeFileName fullpath
+
+  mkTestGroup :: ([String], [(Path, [Path])]) -> TestTree
+  mkTestGroup (tType, tests) =
+    testGroup (String.unwords tType) $ mkTestCase <$> tests
+   where
+    mkTestCase :: (Path, [Path]) -> TestTree
+    mkTestCase (basename, files) =
+      testCase
+        (coerce $ takeFileName basename)
+        $ do
+            time <- liftIO getCurrentTime
+            let opts = defaultOptions time
+            case tType of
+              ["parse", "okay"] -> assertParse opts $ the files
+              ["parse", "fail"] -> assertParseFail opts $ the files
+              ["eval" , "okay"] -> assertEval opts files
+              ["eval" , "fail"] -> assertEvalFail $ the files
+              _                 -> fail $ "Unexpected: " <> show tType
+
+
+assertParse :: Options -> Path -> Assertion
 assertParse _opts file =
-  do
-    x <- parseNixFileLoc file
-    either
-      (\ err -> assertFailure $ "Failed to parse " <> file <> ":\n" <> show err)
-      (const pass)  -- pure $! runST $ void $ lint opts expr
-      x
+  either
+    (\ err -> assertFailure $ "Failed to parse " <> coerce file <> ":\n" <> show err)
+    (const stub)  -- pure $! runST $ void $ lint opts expr
+    =<< parseNixFileLoc file
 
-assertParseFail :: Options -> FilePath -> Assertion
-assertParseFail opts file = do
-  eres <- parseNixFileLoc file
-  (`catch` \(_ :: SomeException) -> pass)
-    (either
-      (const pass)
+assertParseFail :: Options -> Path -> Assertion
+assertParseFail opts file =
+  (`catch` \(_ :: SomeException) -> stub) $
+    either
+      (const stub)
       (\ expr ->
         do
           _ <- pure $! runST $ void $ lint opts expr
-          assertFailure $ "Unexpected success parsing `" <> file <> ":\nParsed value: " <> show expr
+          assertFailure $ "Unexpected success parsing `" <> coerce file <> ":\nParsed value: " <> show expr
       )
-      eres
-    )
+      =<< parseNixFileLoc file
 
-assertLangOk :: Options -> FilePath -> Assertion
-assertLangOk opts file = do
-  actual   <- printNix <$> hnixEvalFile opts (file <> ".nix")
-  expected <- Text.readFile $ file <> ".exp"
-  assertEqual "" expected $ toText (actual <> "\n")
+assertLangOk :: Options -> Path -> Assertion
+assertLangOk opts fileBaseName =
+  do
+    actual   <- printNix <$> hnixEvalFile opts (addNixExt fileBaseName)
+    expected <- read fileBaseName ".exp"
+    assertEqual mempty expected (actual <> "\n")
 
-assertLangOkXml :: Options -> FilePath -> Assertion
-assertLangOkXml opts file = do
-  actual <- stringIgnoreContext . toXML <$> hnixEvalFile
-    opts
-    (file <> ".nix")
-  expected <- Text.readFile $ file <> ".exp.xml"
-  assertEqual "" expected actual
+assertLangOkXml :: Options -> Path -> Assertion
+assertLangOkXml opts fileBaseName =
+  do
+    actual <- stringIgnoreContext . toXML <$> hnixEvalFile opts (addNixExt fileBaseName)
+    expected <- read fileBaseName ".exp.xml"
+    assertEqual mempty expected actual
 
-assertEval :: Options -> [FilePath] -> Assertion
-assertEval _opts files = do
-  time <- liftIO getCurrentTime
-  let opts = defaultOptions time
-  case delete ".nix" $ sort $ fmap takeExtensions files of
-    []                 -> () <$ hnixEvalFile opts (name <> ".nix")
-    [".exp"         ]  -> assertLangOk opts name
-    [".exp.xml"     ]  -> assertLangOkXml opts name
-    [".exp.disabled"]  -> pass
-    [".exp-disabled"]  -> pass
-    [".exp", ".flags"] -> do
-      liftIO $ setEnv "NIX_PATH" "lang/dir4:lang/dir5"
-      flags <- Text.readFile (name <> ".flags")
-      let flags' | Text.last flags == '\n' = Text.init flags
-                 | otherwise               = flags
-      case
-          Opts.execParserPure
-            Opts.defaultPrefs
-            (nixOptionsInfo time)
-            (fixup (fmap toString (Text.splitOn " " flags')))
-        of
-          Opts.Failure err   -> errorWithoutStackTrace $ "Error parsing flags from " <> name <> ".flags: " <> show err
-          Opts.Success opts' -> assertLangOk opts' name
-          Opts.CompletionInvoked _ -> fail "unused"
-    _ -> assertFailure $ "Unknown test type " <> show files
+assertEval :: Options -> [Path] -> Assertion
+assertEval _opts files =
+  do
+    time <- liftIO getCurrentTime
+    let opts = defaultOptions time
+    case delete ".nix" $ sort $ fromString @Text . takeExtensions <$> files of
+      []                  -> void $ hnixEvalFile opts (addNixExt name)
+      [".exp"          ]  -> assertLangOk    opts name
+      [".exp.xml"      ]  -> assertLangOkXml opts name
+      [".exp.disabled" ]  -> stub
+      [".exp-disabled" ]  -> stub
+      [".exp", ".flags"]  ->
+        do
+          liftIO $ setEnv "NIX_PATH" "lang/dir4:lang/dir5"
+          flags <- read name ".flags"
+          let
+            flags' :: Text
+            flags' =
+              bool
+                id
+                Text.init
+                (Text.last flags == '\n')
+                flags
+          case runParserGetResult time flags' of
+            Opts.Failure           err   -> errorWithoutStackTrace $ "Error parsing flags from " <> coerce name <> ".flags: " <> show err
+            Opts.CompletionInvoked _     -> fail "unused"
+            Opts.Success           opts' -> assertLangOk opts' name
+      _ -> assertFailure $ "Unknown test type " <> show files
  where
-  name =
-    "data/nix/tests/lang/" <> the (fmap (takeFileName . dropExtensions) files)
+  runParserGetResult :: UTCTime -> Text -> Opts.ParserResult Options
+  runParserGetResult time flags' =
+    Opts.execParserPure
+      Opts.defaultPrefs
+      (nixOptionsInfo time)
+      (fmap toString $ fixup $ Text.splitOn " " flags')
 
-  fixup ("--arg"    : x : y : rest) = "--arg" : (x <> "=" <> y) : fixup rest
+  name :: Path
+  name = coerce nixTestDir <> the (takeFileName . dropExtensions <$> files)
+
+  fixup :: [Text] -> [Text]
+  fixup ("--arg"    : x : y : rest) = "--arg"    : (x <> "=" <> y) : fixup rest
   fixup ("--argstr" : x : y : rest) = "--argstr" : (x <> "=" <> y) : fixup rest
-  fixup (x                  : rest) = x : fixup rest
+  fixup (x                  : rest) =                          x  : fixup rest
   fixup []                          = mempty
 
-assertEvalFail :: FilePath -> Assertion
-assertEvalFail file = (`catch` (\(_ :: SomeException) -> pass)) $ do
-  time       <- liftIO getCurrentTime
-  evalResult <- printNix <$> hnixEvalFile (defaultOptions time) file
-  evalResult `seq`
-    assertFailure $
-      file
-      <> " should not evaluate.\nThe evaluation result was `"
-      <> evalResult
-      <> "`."
+assertEvalFail :: Path -> Assertion
+assertEvalFail file =
+  (`catch` (\(_ :: SomeException) -> stub)) $
+  do
+    time       <- liftIO getCurrentTime
+    evalResult <- printNix <$> hnixEvalFile (defaultOptions time) file
+    evalResult `seq` assertFailure $ "File: ''" <> coerce file <> "'' should not evaluate.\nThe evaluation result was `" <> toString evalResult <> "`."
+
+nixTestDir :: FilePath
+nixTestDir = "data/nix/tests/lang/"
+
+addNixExt :: Path -> Path
+addNixExt path = addExtension path ".nix"
+
+read :: Path -> String -> IO Text
+read path ext = readFile $ addExtension path ext
diff --git a/tests/ParserTests.hs b/tests/ParserTests.hs
--- a/tests/ParserTests.hs
+++ b/tests/ParserTests.hs
@@ -1,13 +1,16 @@
-{-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE TemplateHaskell #-}
-{-# LANGUAGE RankNTypes #-}
+{-# language QuasiQuotes #-}
+{-# language TemplateHaskell #-}
+{-# language RankNTypes #-}
+{-# language ExtendedDefaultRules #-}
 
-{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
-{-# OPTIONS_GHC -Wno-missing-signatures #-}
+{-# options_ghc -fno-warn-name-shadowing #-}
+{-# options_ghc -Wno-missing-signatures #-}
+{-# options_ghc -Wno-type-defaults #-}
 
 
 module ParserTests (tests) where
 
+import Prelude hiding (($<))
 import Data.Fix
 import NeatInterpolation (text)
 import Nix.Atoms
@@ -19,314 +22,523 @@
 import Test.Tasty
 import Test.Tasty.HUnit
 import Test.Tasty.TH
+import Prettyprinter.Render.String (renderString)
+import Prettyprinter.Util (reflow)
 
-case_constant_int = assertParseText "234" $ mkInt 234
+default (NixLang)
 
-case_constant_bool = do
-  assertParseText "true" $ mkBool True
-  assertParseText "false" $ mkBool False
+-- * Tests
 
-case_constant_bool_respects_attributes = do
-  assertParseText "true-foo"  $ mkSym "true-foo"
-  assertParseText "false-bar" $ mkSym "false-bar"
 
-case_constant_path = do
-  assertParseText "./." $ mkPath False "./."
-  assertParseText "./+-_/cdef/09ad+-" $ mkPath False "./+-_/cdef/09ad+-"
-  assertParseText "/abc" $ mkPath False "/abc"
-  assertParseText "../abc" $ mkPath False "../abc"
-  assertParseText "<abc>" $ mkPath True "abc"
-  assertParseText "<../cdef>" $ mkPath True "../cdef"
-  assertParseText "a//b" $ mkOper2 NUpdate (mkSym "a") (mkSym "b")
-  assertParseText "rec+def/cdef" $ mkPath False "rec+def/cdef"
-  assertParseText "a/b//c/def//<g> < def/d" $ mkOper2 NLt
-    (mkOper2 NUpdate (mkPath False "a/b") $ mkOper2 NUpdate
-      (mkPath False "c/def") (mkPath True "g"))
-    (mkPath False "def/d")
-  assertParseText "a'b/c" $ Fix $ NBinary NApp (mkSym "a'b") (mkPath False "/c")
-  assertParseText "a/b" $ mkPath False "a/b"
-  assertParseText "4/2" $ mkPath False "4/2"
-  assertParseFail "."
-  assertParseFail ".."
-  assertParseFail "/"
-  assertParseFail "a/"
-  assertParseFail "a/def/"
-  assertParseFail "~"
-  assertParseFail "~/"
-  assertParseText "~/a" $ mkPath False "~/a"
-  assertParseText "~/a/b" $ mkPath False "~/a/b"
+-- ** Literals
 
-case_constant_uri = do
-  assertParseText "a:a" $ mkStr "a:a"
-  assertParseText "http://foo.bar" $ mkStr "http://foo.bar"
-  assertParseText "a+de+.adA+-:%%%ads%5asdk&/" $ mkStr "a+de+.adA+-:%%%ads%5asdk&/"
-  assertParseText "rec+def:c" $ mkStr "rec+def:c"
-  assertParseText "f.foo:bar" $ mkStr "f.foo:bar"
-  assertParseFail "http://foo${\"bar\"}"
-  assertParseFail ":bcdef"
-  assertParseFail "a%20:asda"
-  assertParseFail ".:adasd"
-  assertParseFail "+:acdcd"
+case_constant_int =
+  checks
+    ( mkInt 234
+    , "234"
+    )
 
-case_simple_set = do
-  assertParseText "{ a = 23; b = 4; }" $ Fix $ NSet NNonRecursive
-    [ NamedVar (mkSelector "a") (mkInt 23) nullPos
-    , NamedVar (mkSelector "b") (mkInt 4) nullPos
-    ]
-  assertParseFail "{ a = 23 }"
+case_constant_bool =
+  checks
+    ( mkBool True
+    , "true"
+    )
+    ( mkBool False
+    , "false"
+    )
 
-case_set_inherit = do
-  assertParseText "{ e = 3; inherit a b; }" $ Fix $ NSet NNonRecursive
-    [ NamedVar (mkSelector "e") (mkInt 3) nullPos
-    , Inherit Nothing (StaticKey <$> ["a", "b"]) nullPos
-    ]
-  assertParseText "{ inherit; }" $ Fix $ NSet NNonRecursive [ Inherit Nothing mempty nullPos ]
+case_constant_bool_respects_attributes =
+  invariantVals
+    "true-foo"
+    "false-bar"
 
-case_set_scoped_inherit = assertParseText "{ inherit (a) b c; e = 4; inherit(a)b c; }" $ Fix $ NSet NNonRecursive
-  [ Inherit (pure (mkSym "a")) (StaticKey <$> ["b", "c"]) nullPos
-  , NamedVar (mkSelector "e") (mkInt 4) nullPos
-  , Inherit (pure (mkSym "a")) (StaticKey <$> ["b", "c"]) nullPos
-  ]
+case_constant_path_invariants =
+  knownAs (staysInvariantUnder (mkRelPath . toString))
+    "./."
+    "./+-_/cdef/09ad+-"
+    "/abc"
+    "../abc"
+    "~/a"
+    "~/a/b"
+    "a/b"
+    "4/2"
+    "rec+def/cdef"
 
-case_set_rec = assertParseText "rec { a = 3; b = a; }" $ Fix $ NSet NRecursive
-  [ NamedVar (mkSelector "a") (mkInt 3) nullPos
-  , NamedVar (mkSelector "b") (mkSym "a") nullPos
-  ]
+case_constant_path =
+  checks
+    ( var "a'b" @@ mkRelPath "/c"
+    , "a'b/c"
+    )
+    ( mkRelPath "a/b" $// mkRelPath "c/def" $// mkEnvPath "g" $<  mkRelPath "def/d"
+    , "a/b//c/def//<g> < def/d"
+    )
+    ( mkEnvPath "abc"
+    , "<abc>"
+    )
+    ( mkEnvPath "../cdef"
+    , "<../cdef>"
+    )
+    ( var "a" $// var "b"
+    , "a//b"
+    )
 
-case_set_complex_keynames = do
-  assertParseText "{ \"\" = null; }" $ Fix $ NSet NNonRecursive
-    [ NamedVar (DynamicKey (Plain (DoubleQuoted mempty)) :| mempty) mkNull nullPos ]
-  assertParseText "{ a.b = 3; a.c = 4; }" $ Fix $ NSet NNonRecursive
-    [ NamedVar (StaticKey "a" :| [StaticKey "b"]) (mkInt 3) nullPos
-    , NamedVar (StaticKey "a" :| [StaticKey "c"]) (mkInt 4) nullPos
-    ]
-  assertParseText "{ ${let a = \"b\"; in a} = 4; }" $ Fix $ NSet NNonRecursive
-    [ NamedVar (DynamicKey (Antiquoted letExpr) :| mempty) (mkInt 4) nullPos ]
-  assertParseText "{ \"a${let a = \"b\"; in a}c\".e = 4; }" $ Fix $ NSet NNonRecursive
-    [ NamedVar (DynamicKey (Plain str) :| [StaticKey "e"]) (mkInt 4) nullPos ]
+case_constant_path_syntax_mistakes =
+  mistakes
+    "."
+    ".."
+    "/"
+    "a/"
+    "a/def/"
+    "~"
+    "~/"
+
+case_constant_uri =
+  knownAs (staysInvariantUnder mkStr)
+    "a:a"
+    "http://foo.bar"
+    "a+de+.adA+-:%%%ads%5asdk&/"
+    "rec+def:c"
+    "f.foo:bar"
+
+
+case_constant_uri_syntax_mistakes =
+  mistakes
+    "http://foo${\"bar\"}"
+    ":bcdef"
+    "a%20:asda"
+    ".:adasd"
+    "+:acdcd"
+
+
+-- *** Special chars in vals
+
+case_identifier_special_chars =
+  invariantVals
+    "_a"
+    "a_b"
+    "a'b"
+    "a''b"
+    "a-b"
+    "a--b"
+    "a12a"
+
+case_identifier_special_chars_syntax_mistakes =
+  mistakes
+    ".a"
+    "'a"
+
+-- ** Sets
+
+-- *** Non-recursive sets
+
+case_simple_set =
+  checks
+    ( mkNonRecSet
+        [ "a" $= mkInt 23
+        , "b" $= mkInt  4
+        ]
+    , "{ a = 23; b = 4; }"
+    )
+
+case_simple_set_syntax_mistakes =
+  mistakes
+    "{ a = 23 }"
+
+case_set_complex_keynames =
+  checks
+    ( mkNonRecSet $
+        one (NamedVar (one (DynamicKey (Plain (DoubleQuoted mempty)))) mkNull nullPos)
+    , "{ \"\" = null; }"
+    )
+    ( mkNonRecSet
+        [ NamedVar (StaticKey "a" :| one (StaticKey "b")) (mkInt 3) nullPos
+        , NamedVar (StaticKey "a" :| one (StaticKey "c")) (mkInt 4) nullPos
+        ]
+    , "{ a.b = 3; a.c = 4; }"
+    )
+    ( mkNonRecSet $
+        one (NamedVar (one (DynamicKey (Antiquoted letExpr))) (mkInt 4) nullPos)
+    , "{ ${let a = \"b\"; in a} = 4; }"
+    )
+    ( mkNonRecSet $
+        one (NamedVar (DynamicKey (Plain str) :| one (StaticKey "e")) (mkInt 4) nullPos)
+    , "{ \"a${let a = \"b\"; in a}c\".e = 4; }"
+    )
  where
-  letExpr = Fix $ NLet [NamedVar (mkSelector "a") (mkStr "b") nullPos] (mkSym "a")
+  letExpr = mkLets (one ("a" $= mkStr "b")) (var "a")
   str = DoubleQuoted [Plain "a", Antiquoted letExpr, Plain "c"]
 
-case_set_inherit_direct = assertParseText "{ inherit ({a = 3;}); }" $ Fix $ NSet NNonRecursive
-  [ Inherit (pure $ Fix $ NSet NNonRecursive [NamedVar (mkSelector "a") (mkInt 3) nullPos]) mempty nullPos
-  ]
 
-case_inherit_selector = do
-  assertParseText "{ inherit \"a\"; }" $ Fix $ NSet NNonRecursive
-    [Inherit Nothing [DynamicKey (Plain (DoubleQuoted [Plain "a"]))] nullPos]
-  assertParseFail "{ inherit a.x; }"
+-- *** Recursivity in sets
 
-case_int_list = assertParseText "[1 2 3]" $ Fix $ NList
-  [ mkInt i | i <- [1,2,3] ]
+case_set_rec =
+  checks
+    ( mkRecSet
+        [ "a" $= mkInt 3
+        , "b" $= var "a"
+        ]
+    , "rec { a = 3; b = a; }"
+    )
 
-case_int_null_list = assertParseText "[1 2 3 null 4]" $ Fix (NList (fmap (Fix . NConstant) [NInt 1, NInt 2, NInt 3, NNull, NInt 4]))
 
-case_mixed_list = do
-  assertParseText "[{a = 3;}.a (if true then null else false) null false 4 [] c.d or null]" $ Fix $ NList
-    [ Fix (NSelect (Fix (NSet NNonRecursive [NamedVar (mkSelector "a") (mkInt 3) nullPos]))
-                   (mkSelector "a") Nothing)
-    , Fix (NIf (mkBool True) mkNull (mkBool False))
-    , mkNull, mkBool False, mkInt 4, Fix (NList mempty)
-    , Fix (NSelect (mkSym "c") (mkSelector "d") (pure mkNull))
-    ]
-  assertParseFail "[if true then null else null]"
-  assertParseFail "[a ? b]"
-  assertParseFail "[a : a]"
-  assertParseFail "[${\"test\")]"
+-- *** Inheritance
 
-case_simple_lambda = assertParseText "a: a" $ Fix $ NAbs (Param "a") (mkSym "a")
+case_set_inherit =
+  checks
+    ( mkNonRecSet
+        [ "e" $= mkInt 3
+        , inherit ["a", "b"]
+        ]
+    , "{ e = 3; inherit a b; }"
+    )
+    ( mkNonRecSet $ one $ inherit mempty
+    , "{ inherit; }"
+    )
 
-case_lambda_or_uri = do
-  assertParseText "a :b" $ Fix $ NAbs (Param "a") (mkSym "b")
-  assertParseText "a c:def" $ Fix $ NBinary NApp (mkSym "a") (mkStr "c:def")
-  assertParseText "c:def: c" $ Fix $ NBinary NApp (mkStr "c:def:") (mkSym "c")
-  assertParseText "a:{}" $ Fix $ NAbs (Param "a") $ Fix $ NSet NNonRecursive mempty
-  assertParseText "a:[a]" $ Fix $ NAbs (Param "a") $ Fix $ NList [mkSym "a"]
-  assertParseFail "def:"
+case_set_scoped_inherit =
+  checks
+    ( mkNonRecSet $
+        (\ x -> [x, "e" $= mkInt 4, x]) $
+          inheritFrom (var "a") ["b", "c"]
+    , "{ inherit (a) b c; e = 4; inherit(a)b c; }"
+    )
 
-case_lambda_pattern = do
-  assertParseText "{b, c ? 1}: b" $
-    Fix $ NAbs (fixed args mempty) (mkSym "b")
-  assertParseText "{ b ? x: x  }: b" $
-    Fix $ NAbs (fixed args2 mempty) (mkSym "b")
-  assertParseText "a@{b,c ? 1}: b" $
-    Fix $ NAbs (fixed args (pure "a")) (mkSym "b")
-  assertParseText "{b,c?1}@a: c" $
-    Fix $ NAbs (fixed args (pure "a")) (mkSym "c")
-  assertParseText "{b,c?1,...}@a: c" $
-    Fix $ NAbs (variadic vargs (pure "a")) (mkSym "c")
-  assertParseText "{...}: 1" $
-    Fix $ NAbs (variadic mempty mempty) (mkInt 1)
-  assertParseFail "a@b: a"
-  assertParseFail "{a}@{b}: a"
+case_set_inherit_direct =
+  checks
+    ( mkNonRecSet $ one (inheritFrom (mkNonRecSet $ one ("a" $= mkInt 3)) mempty)
+    , "{ inherit ({a = 3;}); }"
+    )
+
+case_inherit_selector_syntax_mistakes =
+  mistakes
+    "{ inherit a.x; }"
+    -- A rare quirk of Nix that is proper to fix then to support (see git commit history)
+    -- (old parser test result was):
+    -- mkNonRecSet [inherit [DynamicKey (Plain (DoubleQuoted [Plain "a"]))]],
+    "{ inherit \"a\"; }"
+
+
+-- ** Lists
+
+case_int_list =
+  checks
+    ( mkList $ mkInt <$> [1,2,3]
+    , "[1 2 3]"
+    )
+
+case_int_null_list =
+  checks
+    ( mkList (mkConst <$> [NInt 1, NInt 2, NInt 3, NNull, NInt 4])
+    , "[1 2 3 null 4]"
+    )
+
+case_mixed_list =
+  checks
+    ( mkList
+        [ mkNonRecSet (one $ "a" $= mkInt 3) @. "a"
+        , mkIf (mkBool True) mkNull (mkBool False)
+        , mkNull
+        , mkBool False
+        , mkInt 4
+        , emptyList
+        , (@.<|>) (var "c") "d" mkNull
+        ]
+    , "[{a = 3;}.a (if true then null else false) null false 4 [] c.d or null]"
+    )
+
+case_mixed_list_syntax_mistakes =
+  mistakes
+    "[if true then null else null]"
+    "[a ? b]"
+    "[a : a]"
+    "[${\"test\")]"
+
+
+-- ** Lambdas
+
+case_simple_lambda =
+  checks
+    ( mkFunction (Param "a") (var "a")
+    , "a: a"
+    )
+
+case_lambda_or_uri =
+  checks
+    ( mkFunction (Param "a") $ var "b"
+    , "a :b"
+    )
+    ( var "a" @@ mkStr "c:def"
+    , "a c:def"
+    )
+    ( mkStr "c:def:" @@ var "c"
+    , "c:def: c"
+    )
+    ( mkFunction (Param "a") emptySet
+    , "a:{}"
+    )
+    ( mkFunction (Param "a") $ mkList $ one $ var "a"
+    , "a:[a]"
+    )
+
+case_lambda_or_uri_syntax_mistakes =
+  mistakes
+    "def:"
+
+case_lambda_pattern =
+  checks
+    ( mkFunction (mkParamSet args) $ var "b"
+    , "{b, c ? 1}: b"
+    -- Fix (NAbs (ParamSet [("b",Nothing),("c",Just (Fix (NConstant (NInt 1))))] False Nothing) (Fix (NSym "b")))
+    )
+    ( mkFunction (mkParamSet args2) $ var "b"
+    , "{ b ? x: x  }: b"
+    )
+    ( mkFunction (mkNamedParamSet "a" args) $ var "b"
+    , "a@{b,c ? 1}: b"
+    )
+    ( mkFunction (mkNamedParamSet "a" args) $ var "c"
+    , "{b,c?1}@a: c"
+    )
+    ( mkFunction (mkNamedVariadicParamSet "a" vargs) $ var "c"
+    , "{b,c?1,...}@a: c"
+    )
+    ( mkFunction (mkVariadicParamSet mempty) $ mkInt 1
+    , "{...}: 1"
+    )
  where
-  fixed args = ParamSet args False
-  variadic args = ParamSet args True
-  args = [("b", Nothing), ("c", pure $ mkInt 1)]
+  args  = [("b", Nothing), ("c", pure $ mkInt 1)]
   vargs = [("b", Nothing), ("c", pure $ mkInt 1)]
-  args2 = [("b", pure lam)]
-  lam = Fix $ NAbs (Param "x") (mkSym "x")
+  args2 = one ("b", pure lam)
+  lam = mkFunction (Param "x") $ var "x"
 
-case_lambda_app_int = assertParseText "(a: a) 3" $ Fix (NBinary NApp lam int) where
-  int = mkInt 3
-  lam = Fix (NAbs (Param "a") asym)
-  asym = mkSym "a"
+case_lambda_pattern_syntax_mistakes =
+  mistakes
+    "a@b: a"
+    "{a}@{b}: a"
 
-case_simple_let = do
-  assertParseText "let a = 4; in a" $ Fix (NLet binds $ mkSym "a")
-  assertParseFail "let a = 4 in a"
- where
-  binds = [NamedVar (mkSelector "a") (mkInt 4) nullPos]
+case_lambda_app_int =
+  checks
+    ( mkFunction (Param "a") (var "a") @@ mkInt 3
+    , "(a: a) 3"
+    )
 
-case_let_body = assertParseText "let { body = 1; }" letBody
-  where
-    letBody = Fix $ NSelect aset (mkSelector "body") Nothing
-    aset = Fix $ NSet NRecursive [NamedVar (mkSelector "body") (mkInt 1) nullPos]
 
-case_nested_let = do
-  assertParseText "let a = 4; in let b = 5; in a" $ Fix $ NLet
-    [NamedVar (mkSelector "a") (mkInt 4) nullPos]
-    (Fix $ NLet [NamedVar (mkSelector "b") (mkInt 5) nullPos] $ mkSym "a")
-  assertParseFail "let a = 4; let b = 3; in b"
+-- ** Let
 
-case_let_scoped_inherit = do
-  assertParseText "let a = null; inherit (b) c; in c" $ Fix $ NLet
-    [ NamedVar (mkSelector "a") mkNull nullPos
-    , Inherit (pure $ mkSym "b") [StaticKey "c"] nullPos ]
-    (mkSym "c")
-  assertParseFail "let inherit (b) c in c"
+case_simple_let =
+  checks
+    ( mkLets (one $ "a" $= mkInt 4) $ var "a"
+    , "let a = 4; in a"
+    )
 
-case_if = do
-  assertParseText "if true then true else false" $
-      Fix $ NIf (mkBool True) (mkBool True) (mkBool False)
-  assertParseFail "if true then false"
-  assertParseFail "else"
-  assertParseFail "if true then false else"
-  assertParseFail "if true then false else false else"
-  assertParseFail "1 + 2 then"
+case_simple_let_syntax_mistakes =
+  mistakes
+    "let a = 4 in a"
 
-case_identifier_special_chars = do
-  assertParseText "_a" $ mkSym "_a"
-  assertParseText "a_b" $ mkSym "a_b"
-  assertParseText "a'b" $ mkSym "a'b"
-  assertParseText "a''b" $ mkSym "a''b"
-  assertParseText "a-b" $ mkSym "a-b"
-  assertParseText "a--b" $ mkSym "a--b"
-  assertParseText "a12a" $ mkSym "a12a"
-  assertParseFail ".a"
-  assertParseFail "'a"
+case_let_body =
+  checks
+    ( mkRecSet (one $ "body" $= mkInt 1) @. "body"
+    , "let { body = 1; }"
+    )
 
-case_identifier_keyword_prefix = do
-  assertParseText "true-name" $ mkSym "true-name"
-  assertParseText "trueName" $ mkSym "trueName"
-  assertParseText "null-name" $ mkSym "null-name"
-  assertParseText "nullName" $ mkSym "nullName"
-  assertParseText "[ null-name ]" $ mkList [ mkSym "null-name" ]
+case_nested_let =
+  checks
+    ( mkLets (one $ "a" $= mkInt 4) $
+        mkLets (one $ "b" $= mkInt 5) $ var "a"
+    , "let a = 4; in let b = 5; in a"
+    )
 
-makeTextParseTest str = assertParseText ("\"" <> str <> "\"") $ mkStr str
+case_nested_let_syntax_mistakes =
+  mistakes
+    "let a = 4; let b = 3; in b"
 
-case_simple_string = traverse_ makeTextParseTest ["abcdef", "a", "A", "   a a  ", ""]
+case_let_scoped_inherit =
+  checks
+    ( mkLets
+        [ "a" $= mkNull
+        , inheritFrom (var "b") $ one "c"
+        ]
+        $ var "c"
+    , "let a = null; inherit (b) c; in c"
+    )
 
-case_string_dollar = traverse_ makeTextParseTest ["a$b", "a$$b", "$cdef", "gh$i"]
+case_let_scoped_inherit_syntax_mistakes =
+  mistakes
+    "let inherit (b) c in c"
 
-case_string_escape = do
-  assertParseText "\"\\$\\n\\t\\r\\\\\"" $ mkStr "$\n\t\r\\"
-  assertParseText "\" \\\" \\' \"" $ mkStr " \" ' "
 
-case_string_antiquote = do
-  assertParseText "\"abc${  if true then \"def\" else \"abc\"  } g\"" $
-    Fix $ NStr $ DoubleQuoted
-      [ Plain "abc"
-      , Antiquoted $ Fix $ NIf (mkBool True) (mkStr "def") (mkStr "abc")
-      , Plain " g"
-      ]
-  assertParseText "\"\\${a}\"" $ mkStr "${a}"
-  assertParseFail "\"a"
-  assertParseFail "${true}"
-  assertParseFail "\"${true\""
+-- ** If
 
-case_select = do
-  assertParseText "a .  e .di. f" $ Fix $ NSelect (mkSym "a")
-    (StaticKey "e" :| [StaticKey "di", StaticKey "f"])
-    Nothing
-  assertParseText "a.e . d    or null" $ Fix $ NSelect (mkSym "a")
-    (StaticKey "e" :| [StaticKey "d"])
-    (pure mkNull)
-  assertParseText "{}.\"\"or null" $ Fix $ NSelect (Fix (NSet NNonRecursive mempty))
-    (DynamicKey (Plain (DoubleQuoted mempty)) :| mempty) (pure mkNull)
-  assertParseText "{ a = [1]; }.a or [2] ++ [3]" $ Fix $ NBinary NConcat
-      (Fix (NSelect
-                (Fix (NSet NNonRecursive [NamedVar (StaticKey "a" :| mempty)
-                                     (Fix (NList [Fix (NConstant (NInt 1))]))
-                                     nullPos]))
-                (StaticKey "a" :| mempty)
-                (pure (Fix (NList [Fix (NConstant (NInt 2))])))))
-      (Fix (NList [Fix (NConstant (NInt 3))]))
+case_if =
+  checks
+    ( mkIf (mkBool True) (mkBool True) (mkBool False)
+    , "if true then true else false"
+    )
 
-case_select_path = do
-  assertParseText "f ./." $ Fix $ NBinary NApp (mkSym "f") (mkPath False "./.")
-  assertParseText "f.b ../a" $ Fix $ NBinary NApp select (mkPath False "../a")
-  assertParseText "{}./def" $ Fix $ NBinary NApp (Fix (NSet NNonRecursive mempty)) (mkPath False "./def")
-  assertParseText "{}.\"\"./def" $ Fix $ NBinary NApp
-    (Fix $ NSelect (Fix (NSet NNonRecursive mempty)) (DynamicKey (Plain (DoubleQuoted mempty)) :| mempty) Nothing)
-    (mkPath False "./def")
- where select = Fix $ NSelect (mkSym "f") (mkSelector "b") Nothing
+case_if_syntax_mistakes =
+  mistakes
+    "if true then false"
+    "else"
+    "if true then false else"
+    "if true then false else false else"
+    "1 + 2 then"
 
-case_select_keyword = do
-  assertParseText "{ false = \"foo\"; }" $ Fix $ NSet NNonRecursive [NamedVar (mkSelector "false") (mkStr "foo") nullPos]
 
-case_fun_app = do
-  assertParseText "f a b" $ Fix $ NBinary NApp (Fix $ NBinary NApp (mkSym "f") (mkSym "a")) (mkSym "b")
-  assertParseText "f a.x or null" $ Fix $ NBinary NApp (mkSym "f") $ Fix $
-    NSelect (mkSym "a") (mkSelector "x") (pure mkNull)
-  assertParseFail "f if true then null else null"
+-- ** Literal expressions in vals
 
-case_indented_string = do
-  assertParseText "''a''" $ mkIndentedStr 0 "a"
-  assertParseText "''\n  foo\n  bar''" $ mkIndentedStr 2 "foo\nbar"
-  assertParseText "''        ''" $ mkIndentedStr 0 ""
-  assertParseText "'''''''" $ mkIndentedStr 0 "''"
-  assertParseText "''   ${null}\n   a${null}''" $ Fix $ NStr $ Indented 3
-    [ Antiquoted mkNull
-    , Plain "\na"
-    , Antiquoted mkNull
-    ]
-  assertParseFail "'''''"
-  assertParseFail "''   '"
+case_identifier_keyword_prefix_invariants =
+  invariantVals
+    "true-name"
+    "trueName"
+    "null-name"
+    "nullName"
 
-case_indented_string_escape = assertParseText
-  "'' ''\\n ''\\t ''\\\\ ''${ \\ \\n ' ''' ''" $
-  mkIndentedStr 1 "\n \t \\ ${ \\ \\n ' '' "
+case_identifier_keyword_prefix =
+  checks
+    ( mkList $ one $ var "null-name"
+    , "[ null-name ]"
+    )
 
-case_operator_fun_app = do
-  assertParseText "a ++ b" $ mkOper2 NConcat (mkSym "a") (mkSym "b")
-  assertParseText "a ++ f b" $ mkOper2 NConcat (mkSym "a") $ Fix $ NBinary NApp
-    (mkSym "f") (mkSym "b")
 
-case_operators = do
-  assertParseText "1 + 2 - 3" $ mkOper2 NMinus
-    (mkOper2 NPlus (mkInt 1) (mkInt 2)) (mkInt 3)
-  assertParseFail "1 + if true then 1 else 2"
-  assertParseText "1 + (if true then 2 else 3)" $ mkOper2 NPlus (mkInt 1) $ Fix $ NIf
-   (mkBool True) (mkInt 2) (mkInt 3)
-  assertParseText "{ a = 3; } // rec { b = 4; }" $ mkOper2 NUpdate
-    (Fix $ NSet NNonRecursive [NamedVar (mkSelector "a") (mkInt 3) nullPos])
-    (Fix $ NSet NRecursive [NamedVar (mkSelector "b") (mkInt 4) nullPos])
-  assertParseText "--a" $ mkOper NNeg $ mkOper NNeg $ mkSym "a"
-  assertParseText "a - b - c" $ mkOper2 NMinus
-    (mkOper2 NMinus (mkSym "a") (mkSym "b")) $
-    mkSym "c"
-  assertParseText "foo<bar" $ mkOper2 NLt (mkSym "foo") (mkSym "bar")
-  assertParseFail "+ 3"
-  assertParseFail "foo +"
+-- ** Strings
 
-case_comments =
-  do
-    Right expected <- parseNixFile "data/let.nix"
-    assertParseFile "let-comments-multiline.nix" expected
-    assertParseFile "let-comments.nix" expected
+invariantString str =
+  checks
+    ( mkStr str
+    , "\"" <> str <> "\""
+    )
 
+case_simple_string =
+  knownAs invariantString
+    "abcdef"
+    "a"
+    "A"
+    "   a a  "
+    ""
+
+case_string_dollar =
+  knownAs invariantString
+    "a$b"
+    "a$$b"
+    "$cdef"
+    "gh$i"
+
+case_string_escape =
+  checks
+    ( mkStr "$\n\t\r\\"
+    , "\"\\$\\n\\t\\r\\\\\""
+    )
+    ( mkStr " \" ' "
+    , "\" \\\" \\' \""
+    )
+
+case_string_antiquote =
+  checks
+    ( Fix $ NStr $ DoubleQuoted
+        [ Plain "abc"
+        , Antiquoted $ mkIf (mkBool True) (mkStr "def") (mkStr "abc")
+        , Plain " g"
+        ]
+    , "\"abc${  if true then \"def\" else \"abc\"  } g\""
+    )
+    ( mkStr "${a}"
+    , "\"\\${a}\""
+    )
+
+case_string_antiquote_syntax_mistakes =
+  mistakes
+    "\"a"
+    "${true}"
+    "\"${true\""
+
+
+-- *** Indented string
+
+case_indented_string =
+  checks
+    ( mkIndentedStr 0 "a"
+    , "''a''"
+    )
+    ( mkIndentedStr 2 "foo\nbar"
+    , "''\n  foo\n  bar''"
+    )
+    ( mkIndentedStr 0 mempty
+    , "''        ''"
+    )
+    ( mkIndentedStr 0 "''"
+    , "'''''''"
+    )
+    ( Fix $ NStr $ Indented 3
+        [ Antiquoted mkNull
+        , Plain "\na"
+        , Antiquoted mkNull
+        ]
+    , "''   ${null}\n   a${null}''"
+    )
+
+case_indented_string_syntax_mistakes =
+  mistakes
+    "'''''"
+    "''   '"
+
+case_indented_string_escape =
+  checks
+    ( mkIndentedStr 1 "\n \t \\ ${ \\ \\n ' '' "
+    , "'' ''\\n ''\\t ''\\\\ ''${ \\ \\n ' ''' ''"
+    )
+
+-- ** Selection
+
+case_select =
+  checks
+    ( Fix $ NSelect Nothing (var "a") (StaticKey "e" :| [StaticKey "di", StaticKey "f"])
+    , "a .  e .di. f"
+    )
+    ( Fix $ NSelect (pure mkNull) (var "a")
+        (StaticKey "e" :| one (StaticKey "d"))
+    , "a.e . d    or null"
+    )
+    ( Fix $ NSelect (pure mkNull) emptySet
+        (one $ DynamicKey (Plain $ DoubleQuoted mempty))
+    , "{}.\"\"or null"
+    )
+    ( Fix $ NBinary NConcat
+        ((@.<|>)
+          (mkNonRecSet $
+            one $
+              NamedVar
+                (mkSelector "a")
+                (mkList $ one $ mkInt 1)
+                nullPos
+          )
+          "a"
+          (mkList $ one $ mkInt 2)
+        )
+        (mkList $ one $ mkInt 3)
+    , "{ a = [1]; }.a or [2] ++ [3]"
+    )
+
+case_select_path =
+  checks
+    ( var "f" @@ mkRelPath "./."
+    , "f ./."
+    )
+    ( var "f" @. "b" @@ mkRelPath "../a"
+    , "f.b ../a"
+    )
+    ( emptySet @@ mkRelPath "./def"
+    , "{}./def"
+    )
+    ( Fix (NSelect Nothing emptySet $ one $ DynamicKey $ Plain $ DoubleQuoted mempty) @@ mkRelPath "./def"
+    , "{}.\"\"./def"
+    )
+
+case_select_keyword =
+  checks
+    ( mkNonRecSet $ one $ "false" $= mkStr "foo"
+    , "{ false = \"foo\"; }"
+    )
+
 case_select_or_precedence =
     assertParsePrint [text|let
   matchDef = def:   matcher:
@@ -357,39 +569,101 @@
         in (matcher.case or null).foo (v.case);
 in null|]
 
+-- ** Function application
+
+case_fun_app =
+  checks
+    ( var "f" @@ var "a" @@ var "b"
+    , "f a b"
+    )
+    ( var "f" @@ (@.<|>) (var "a") "x" mkNull
+    , "f a.x or null"
+    )
+
+case_fun_app_syntax_mistakes =
+  mistakes
+   "f if true then null else null"
+
+
+-- ** Operators
+
+case_operator_fun_app =
+  checks
+    ( var "a" $++ var "b"
+    , "a ++ b"
+    )
+    ( var "a" $++ var "f" @@ var "b"
+    , "a ++ f b"
+    )
+
+case_operators =
+  checks
+    ( mkInt 1 $+ mkInt 2 $- mkInt 3
+    , "1 + 2 - 3"
+    )
+    ( mkInt 1 $+ mkIf (mkBool True) (mkInt 2) (mkInt 3)
+    , "1 + (if true then 2 else 3)"
+    )
+    ( mkNonRecSet (one $ "a" $= mkInt 3) $// mkRecSet (one $ "b" $= mkInt 4)
+    , "{ a = 3; } // rec { b = 4; }"
+    )
+    ( mkNeg $ mkNeg $ var "a"
+    , "--a"
+    )
+    ( var "a" $- var "b" $- var "c"
+    , "a - b - c"
+    )
+    ( var "foo" $< var "bar"
+    , "foo<bar"
+    )
+
+case_operators_syntax_mistakes =
+  mistakes
+    "+ 3"
+    "foo +"
+    "1 + if true then 1 else 2"
+
+
+-- ** Comments
+
+case_comments =
+  do
+    Right expected <- parseNixFile "data/let.nix"
+    assertParseFile "let-comments-multiline.nix" expected
+    assertParseFile "let-comments.nix" expected
+
+
+-- ** Location
+
 case_simpleLoc =
   let
-    mkSPos l c = SourcePos "<string>" (mkPos l) (mkPos c)
-    mkSpan l1 c1 l2 c2 = SrcSpan (mkSPos l1 c1) (mkSPos l2 c2)
+    mkSPos = on (SourcePos "<string>") mkPos
+    mkSpan = on SrcSpan (uncurry mkSPos)
   in
     assertParseTextLoc [text|let
     foo = bar
          baz "qux";
     in foo
     |]
-    (Fix
-      (NLet_
-        (mkSpan 1 1 4 7)
-        [ NamedVar
-            (StaticKey "foo" :| [])
-            (Fix
-              (NBinary_
-                (mkSpan 2 7 3 15)
-                NApp
-                (Fix
-                  (NBinary_ (mkSpan 2 7 3 9)
-                            NApp
-                            (Fix (NSym_ (mkSpan 2 7 2 10) "bar"))
-                            (Fix (NSym_ (mkSpan 3 6 3 9) "baz"))
-                  )
-                )
-                (Fix (NStr_ (mkSpan 3 10 3 15) (DoubleQuoted [Plain "qux"])))
-              )
+    (NLetAnn
+      (mkSpan (1, 1) (4, 7))
+      (one $
+        NamedVar
+          (one $ StaticKey "foo")
+          (NBinaryAnn
+            (mkSpan (2, 7) (3, 15))
+            NApp
+            (NBinaryAnn
+              (mkSpan (2, 7) (3, 9))
+              NApp
+              (NSymAnn (mkSpan (2, 7) (2, 10)) "bar")
+              (NSymAnn (mkSpan (3, 6) (3, 9 )) "baz")
             )
-            (mkSPos 2 1)
-        ]
-        (Fix (NSym_ (mkSpan 4 4 4 7) "foo"))
+            (NStrAnn (mkSpan (3, 10) (3, 15)) $ DoubleQuoted $ one $ Plain "qux")
+          )
+          (mkSPos 2 1)
       )
+      (NSymAnn (mkSpan (4, 4) (4, 7)) "foo")
     )
 
 
@@ -398,12 +672,42 @@
 
 ---------------------------------------------------------------------------------
 
-assertParseText :: Text -> NExpr -> Assertion
-assertParseText str expected =
+-- * Helpers
+
+var = mkSym
+
+invariantVal = staysInvariantUnder var
+
+staysInvariantUnder :: (NixLang -> ExpectedHask) -> NixLang -> Assertion
+staysInvariantUnder f v =
+  (<=>) (f v) v
+
+type NixLang = Text
+type ExpectedHask = NExpr
+
+(<=>) :: ExpectedHask -> NixLang -> Assertion
+(<=>) = assertParseText
+
+throwParseError :: forall ann . Text -> Text -> Doc ann -> Assertion
+throwParseError entity expr err =
+  assertFailure $
+    renderString $
+      layoutSmart Prettyprinter.defaultLayoutOptions $
+        nest 2 $
+          vsep
+            [ mempty
+            , "Unexpected fail parsing " <> reflow entity <> ":"
+            , nest 2 $ vsep
+              [ "Expression:"
+              , reflow expr
+              , "Error: " <> nest 2 err
+              ]
+            ]
+
+assertParseText :: ExpectedHask -> NixLang -> Assertion
+assertParseText expected str =
   either
-    (\ err ->
-      assertFailure $ toString $ "Unexpected fail parsing `" <> str <> "':\n" <> show err
-    )
+    (throwParseError "expression" str)
     (assertEqual
       ("When parsing " <> toString str)
       (stripPositionInfo expected)
@@ -411,39 +715,35 @@
     )
     (parseNixText str)
 
-assertParseTextLoc :: Text -> NExprLoc -> Assertion
+assertParseTextLoc :: NixLang -> NExprLoc -> Assertion
 assertParseTextLoc str expected =
   either
-    (\ err ->
-      assertFailure $ toString $ "Unexpected fail parsing `" <> str <> "':\n" <> show err
-    )
+    (throwParseError "expression" str)
     (assertEqual
       ("When parsing " <> toString str)
       expected
     )
     (parseNixTextLoc str)
 
-assertParseFile :: FilePath -> NExpr -> Assertion
+assertParseFile :: Path -> NExpr -> Assertion
 assertParseFile file expected =
   do
-  res <- parseNixFile $ "data/" <> file
-  either
-    (\ err ->
-      assertFailure $ "Unexpected fail parsing data file `" <> file <> "':\n" <> show err
-    )
-    (assertEqual
-      ("Parsing data file " <> file)
-      (stripPositionInfo expected)
-      . stripPositionInfo
-    )
-    res
+    res <- parseNixFile $ "data/" <> file
+    either
+      (throwParseError "data file" $ toText file)
+      (assertEqual
+        ("Parsing data file " <> coerce file)
+        (stripPositionInfo expected)
+        . stripPositionInfo
+      )
+      res
 
-assertParseFail :: Text -> Assertion
+assertParseFail :: NixLang -> Assertion
 assertParseFail str =
   either
-    (const pass)
+    (const stub)
     (\ r ->
-      assertFailure $ toString $ "Unexpected success parsing `" <> str <> ":\nParsed value: " <> show r
+      assertFailure $ toString $ "\nUnexpected success parsing string ''" <> str <> "'':\n''Parsed value: ''" <> show r <> "''."
     )
     (parseNixText str)
 
@@ -456,8 +756,53 @@
     Right expr = parseNixTextLoc src
     result =
       renderStrict
-      . layoutPretty (LayoutOptions $ AvailablePerLine 80 0.4)
+      . layoutPretty defaultLayoutOptions
       . prettyNix
       . stripAnnotation $
         expr
-  in assertEqual "" expect result
+  in
+  assertEqual mempty expect result
+
+
+-----
+
+-- | This class constructs functions that accept variacic number of argumets.
+-- Every argument is an assertion.
+-- So now the new assertions can be added just by adding it to a block of according assertions.
+class VariadicAssertions t where
+  checkListPairs' :: ((ExpectedHask, NixLang) -> Assertion) -> [(ExpectedHask, NixLang)] -> t
+
+instance VariadicAssertions (IO a) where
+  checkListPairs' f acc =
+    do
+      traverse_ f acc
+      pure $ error "never would be reached, cuz `I'm lazy`."
+
+instance (VariadicAssertions a) => VariadicAssertions ((ExpectedHask, NixLang) -> a) where
+  checkListPairs' f acc x = checkListPairs' f (acc <> one x)
+
+checks :: (VariadicAssertions a) => a
+checks = checkListPairs' (uncurry assertParseText) []
+
+
+class VariadicArgs t where
+  checkList' :: (NixLang -> Assertion) -> [NixLang] -> t
+
+instance VariadicArgs (IO a) where
+  checkList' f acc =
+    do
+      traverse_ f acc
+      pure $ error "never would be reached, cuz `I'm lazy`."
+
+instance (VariadicArgs a) => VariadicArgs (NixLang -> a) where
+  checkList' f acc x = checkList' f (acc <> one x)
+
+knownAs :: (VariadicArgs a) => (NixLang -> Assertion) -> a
+knownAs f = checkList' f []
+
+mistakes :: (VariadicArgs a) => a
+mistakes = knownAs assertParseFail
+
+invariantVals :: (VariadicArgs a) => a
+invariantVals = knownAs invariantVal
+
diff --git a/tests/PrettyParseTests.hs b/tests/PrettyParseTests.hs
--- a/tests/PrettyParseTests.hs
+++ b/tests/PrettyParseTests.hs
@@ -1,6 +1,6 @@
-{-# LANGUAGE DataKinds #-}
-{-# LANGUAGE MonoLocalBinds #-}
-{-# LANGUAGE NoMonomorphismRestriction #-}
+{-# language DataKinds #-}
+{-# language MonoLocalBinds #-}
+{-# language NoMonomorphismRestriction #-}
 
 
 
@@ -18,7 +18,6 @@
 import           Nix.Expr
 import           Nix.Parser
 import           Nix.Pretty
-import           Nix.Utils
 import           Prettyprinter
 import           Test.Tasty
 import           Test.Tasty.Hedgehog
@@ -29,23 +28,26 @@
 asciiString = Gen.list (Range.linear 1 15) Gen.lower
 
 asciiText :: Gen Text
-asciiText = toText <$> asciiString
+asciiText = fromString <$> asciiString
 
+asciiVarName :: Gen VarName
+asciiVarName = coerce <$> asciiText
+
 -- Might want to replace this instance with a constant value
 genPos :: Gen Pos
 genPos = mkPos <$> Gen.int (Range.linear 1 256)
 
 genSourcePos :: Gen SourcePos
 genSourcePos =
-  liftA3
-    SourcePos
-    asciiString
-    genPos
+  join (liftA3
+      SourcePos
+      asciiString
+    )
     genPos
 
 genKeyName :: Gen (NKeyName NExpr)
 genKeyName =
-  Gen.choice [DynamicKey <$> genAntiquoted genString, StaticKey <$> asciiText]
+  Gen.choice [DynamicKey <$> genAntiquoted genString, StaticKey <$> asciiVarName]
 
 genAntiquoted :: Gen a -> Gen (Antiquoted a NExpr)
 genAntiquoted gen =
@@ -53,51 +55,53 @@
 
 genBinding :: Gen (Binding NExpr)
 genBinding = Gen.choice
-  [ liftA3
-      NamedVar
+  [ liftA3 NamedVar
       genAttrPath
       genExpr
       genSourcePos
-  , liftA3
-      Inherit
+  , liftA3 Inherit
       (Gen.maybe genExpr)
-      (Gen.list (Range.linear 0 5) genKeyName)
+      (Gen.list (Range.linear 0 5) asciiVarName)
       genSourcePos
   ]
 
 genString :: Gen (NString NExpr)
 genString = Gen.choice
-  [ DoubleQuoted <$> Gen.list (Range.linear 0 5) (genAntiquoted asciiText)
-  , liftA2
-      Indented
-      (Gen.int (Range.linear 0 10))
-      (Gen.list
-        (Range.linear 0 5)
-        (genAntiquoted asciiText)
-      )
+  [ DoubleQuoted <$> genLines
+  , liftA2 Indented
+      (Gen.int $ Range.linear 0 10)
+      genLines
   ]
+ where
+  genLines =
+    Gen.list
+      (Range.linear 0 5)
+      (genAntiquoted asciiText)
 
 genAttrPath :: Gen (NAttrPath NExpr)
 genAttrPath =
-  liftA2
-    (:|)
+  liftA2 (:|)
     genKeyName
     $ Gen.list (Range.linear 0 4) genKeyName
 
 genParams :: Gen (Params NExpr)
 genParams = Gen.choice
-  [ Param <$> asciiText
-  , liftA3
-      ParamSet
-      (Gen.list (Range.linear 0 10) (liftA2 (,) asciiText $ Gen.maybe genExpr))
+  [ Param <$> asciiVarName
+  , liftA3 (mkGeneralParamSet . pure)
+      (Gen.choice [stub, asciiText])
+      (Gen.list (Range.linear 0 10) $
+        liftA2 (,)
+          asciiText
+          (Gen.maybe genExpr)
+      )
       Gen.bool
-      (Gen.choice [stub, pure <$> asciiText])
   ]
 
+
 genAtom :: Gen NAtom
 genAtom = Gen.choice
-  [ NInt   <$> Gen.integral (Range.linear 0 1000)
-  , NFloat <$> Gen.float (Range.linearFrac 0.0 1000.0)
+  [ NInt   <$> Gen.integral (Range.linear     0   1000  )
+  , NFloat <$> Gen.float    (Range.linearFrac 0.0 1000.0)
   , NBool  <$> Gen.bool
   , pure NNull
   ]
@@ -106,48 +110,65 @@
 -- list Arbitrary instance which makes the generator terminate. The
 -- distribution is not scientifically chosen.
 genExpr :: Gen NExpr
-genExpr = Gen.sized $ \(Size n) -> Fix <$> if n < 2
-  then Gen.choice [genConstant, genStr, genSym, genLiteralPath, genEnvPath]
-  else Gen.frequency
-    [ (1 , genConstant)
-    , (1 , genSym)
-    , (4 , Gen.resize (Size (n `div` 3)) genIf)
-    , (10, genRecSet)
-    , (20, genSet)
-    , (5 , genList)
-    , (2 , genUnary)
-    , (2, Gen.resize (Size (n `div` 3)) genBinary)
-    , (3, Gen.resize (Size (n `div` 3)) genSelect)
-    , (20, Gen.resize (Size (n `div` 2)) genAbs)
-    , (2, Gen.resize (Size (n `div` 2)) genHasAttr)
-    , (10, Gen.resize (Size (n `div` 2)) genLet)
-    , (10, Gen.resize (Size (n `div` 2)) genWith)
-    , (1, Gen.resize (Size (n `div` 2)) genAssert)
-    ]
+genExpr =
+  Gen.sized genCurbed
  where
-  genConstant    = NConstant                <$> genAtom
-  genStr         = NStr                     <$> genString
-  genSym         = NSym                     <$> asciiText
-  genList        = NList                    <$> fairList genExpr
-  genSet         = NSet NNonRecursive       <$> fairList genBinding
-  genRecSet      = NSet NRecursive          <$> fairList genBinding
-  genLiteralPath = NLiteralPath . ("./" <>) <$> asciiString
-  genEnvPath     = NEnvPath                 <$> asciiString
-  genUnary       = liftA2 NUnary   Gen.enumBounded       genExpr
-  genBinary      = liftA3 NBinary  Gen.enumBounded       genExpr     genExpr
-  genSelect      = liftA3 NSelect  genExpr               genAttrPath (Gen.maybe genExpr)
-  genHasAttr     = liftA2 NHasAttr genExpr               genAttrPath
-  genAbs         = liftA2 NAbs     genParams             genExpr
-  genLet         = liftA2 NLet     (fairList genBinding) genExpr
-  genIf          = liftA3 NIf      genExpr               genExpr     genExpr
-  genWith        = liftA2 NWith    genExpr               genExpr
-  genAssert      = liftA2 NAssert  genExpr               genExpr
+  genCurbed (coerce -> n) =
+    Fix <$>
+      bool
+        small
+        big
+        (n >= 2)
+   where
 
+    genConstant    = NConstant                         <$> genAtom
+    genStr         = NStr                              <$> genString
+    genSym         = NSym                              <$> asciiVarName
+    genLiteralPath = NLiteralPath . ("./" <>) . coerce <$> asciiString
+    genEnvPath     = NEnvPath . coerce                 <$> asciiString
+
+    small = Gen.choice [genConstant, genStr, genSym, genLiteralPath, genEnvPath]
+
+    big =
+      let
+          sizeDivBy i = Size $ n `div` i
+          resizeDivBy i = Gen.resize (sizeDivBy i)
+      in
+      Gen.frequency
+        [ (1 , genConstant)
+        , (1 , genSym)
+        , (2 , genUnary)
+        , (5 , genList)
+        , (20, genSet)
+        , (10, genRecSet)
+        , (1 , resizeDivBy 2 genAssert)
+        , (4 , resizeDivBy 3 genIf)
+        , (2 , resizeDivBy 3 genBinary)
+        , (3 , resizeDivBy 3 genSelect)
+        , (20, resizeDivBy 2 genAbs)
+        , (2 , resizeDivBy 2 genHasAttr)
+        , (10, resizeDivBy 2 genLet)
+        , (10, resizeDivBy 2 genWith)
+        ]
+     where
+      genList        = NList                             <$> fairList genExpr
+      genSet         = NSet mempty                       <$> fairList genBinding
+      genRecSet      = NSet Recursive                    <$> fairList genBinding
+      genUnary       = liftA2 NUnary   Gen.enumBounded       genExpr
+      genBinary      = join (liftA3 NBinary  Gen.enumBounded) genExpr
+      genSelect      = liftA3 NSelect  (Gen.maybe genExpr)   genExpr     genAttrPath
+      genHasAttr     = liftA2 NHasAttr genExpr               genAttrPath
+      genAbs         = liftA2 NAbs     genParams             genExpr
+      genLet         = liftA2 NLet     (fairList genBinding) genExpr
+      genIf          = join (liftA3 NIf      genExpr) genExpr
+      genWith        = join (liftA2 NWith) genExpr
+      genAssert      = join (liftA2 NAssert) genExpr
+
 -- | Useful when there are recursive positions at each element of the list as
 --   it divides the size by the length of the generated list.
 fairList :: Gen a -> Gen [a]
 fairList g = Gen.sized $ \s -> do
-  k <- Gen.int (Range.linear 0 (unSize s))
+  k <- Gen.int $ Range.linear 0 $ unSize s
   -- Use max here to avoid dividing by zero when there is the empty list
   Gen.resize (Size (unSize s `div` max 1 k)) $ Gen.list (Range.singleton k) g
 
@@ -155,49 +176,53 @@
 equivUpToNormalization x y = normalize x == normalize y
 
 normalize :: NExpr -> NExpr
-normalize = foldFix $ Fix . \case
+normalize = foldFix $ \case
   NConstant (NInt n) | n < 0 ->
-    NUnary NNeg $ Fix $ NConstant $ NInt $ negate n
+    mkNeg $ mkInt $ negate n
   NConstant (NFloat n) | n < 0 ->
-    NUnary NNeg $ Fix $ NConstant $ NFloat $ negate n
+    mkNeg $ mkFloat $ negate n
 
-  NSet recur binds -> NSet recur $ normBinding <$> binds
-  NLet binds  r -> NLet (normBinding <$> binds) r
+  NSet recur binds ->
+    mkSet recur $ normBinding <$> binds
+  NLet binds  r ->
+    mkLets (normBinding <$> binds) r
 
-  NAbs params r -> NAbs (normParams params) r
+  NAbs params r ->
+    mkFunction (normParams params) r
 
-  r             -> r
+  r             -> Fix r
 
  where
   normBinding (NamedVar path r     pos) = NamedVar (normKey <$> path) r pos
-  normBinding (Inherit  mr   names pos) = Inherit mr (normKey <$> names) pos
+  normBinding (Inherit  mr   names pos) = Inherit mr names pos
 
   normKey (DynamicKey quoted) = DynamicKey (normAntiquotedString quoted)
   normKey (StaticKey  name  ) = StaticKey name
 
   normAntiquotedString
-    :: Antiquoted (NString NExpr) NExpr -> Antiquoted (NString NExpr) NExpr
+    :: Antiquoted (NString NExpr) NExpr
+    -> Antiquoted (NString NExpr) NExpr
   normAntiquotedString (Plain (DoubleQuoted [EscapedNewline])) = EscapedNewline
   normAntiquotedString (Plain (DoubleQuoted strs)) =
-    let strs' = normAntiquotedText <$> strs
-    in
-    if strs == strs'
-      then Plain $ DoubleQuoted strs
-      else normAntiquotedString $ Plain $ DoubleQuoted strs'
+    bool normAntiquotedString id (strs == strs')
+      (Plain $ DoubleQuoted strs')
+    where
+     strs' = normAntiquotedText <$> strs
   normAntiquotedString r = r
 
-  normAntiquotedText :: Antiquoted Text NExpr -> Antiquoted Text NExpr
+  normAntiquotedText
+    :: Antiquoted Text NExpr
+    -> Antiquoted Text NExpr
   normAntiquotedText (Plain "\n"  ) = EscapedNewline
   normAntiquotedText (Plain "''\n") = EscapedNewline
   normAntiquotedText r              = r
 
-  normParams (ParamSet binds var (Just "")) = ParamSet binds var mempty
+  normParams (ParamSet (Just "") variadic pset) = ParamSet Nothing variadic pset
   normParams r                              = r
 
 -- | Test that parse . pretty == id up to attribute position information.
 prop_prettyparse :: Monad m => NExpr -> PropertyT m ()
-prop_prettyparse p = do
-  let prog = show $ prettyNix p
+prop_prettyparse p =
   either
     (\ s -> do
       footnote $ show $ vsep
@@ -236,16 +261,18 @@
         success
         (equivUpToNormalization p v)
     )
-    (parse $ toText prog)
+    (parse $ fromString prog)
  where
+  prog = show $ prettyNix p
+
   parse     = parseNixText
 
   normalise s = String.unlines $ reverse . dropWhile isSpace . reverse <$> String.lines s
 
   ldiff :: String -> String -> [Diff [String]]
-  ldiff s1 s2 = getDiff ((: mempty) <$> String.lines s1) ((: mempty) <$> String.lines s2)
+  ldiff s1 s2 = getDiff (one <$> String.lines s1) (one <$> String.lines s2)
 
 tests :: TestLimit -> TestTree
-tests n = testProperty "Pretty/Parse Property" $ withTests n $ property $ do
-  x <- forAll genExpr
-  prop_prettyparse x
+tests n =
+  testProperty "Pretty/Parse Property" $
+    withTests n $ property $ prop_prettyparse =<< forAll genExpr
diff --git a/tests/PrettyTests.hs b/tests/PrettyTests.hs
--- a/tests/PrettyTests.hs
+++ b/tests/PrettyTests.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE TemplateHaskell #-}
+{-# language TemplateHaskell #-}
 module PrettyTests (tests) where
 
 import           Test.Tasty
@@ -9,29 +9,48 @@
 import           Nix.Pretty
 
 case_indented_antiquotation :: Assertion
-case_indented_antiquotation = do
-  assertPretty (mkIndentedStr 0 "echo $foo")   "''echo $foo''"
-  assertPretty (mkIndentedStr 0 "echo ${foo}") "''echo ''${foo}''"
+case_indented_antiquotation =
+  do
+    assertPretty
+      (mkIndentedStr 0 "echo $foo")
+      "''echo $foo''"
+    assertPretty
+      (mkIndentedStr 0 "echo ${foo}")
+      "''echo ''${foo}''"
 
 case_string_antiquotation :: Assertion
-case_string_antiquotation = do
-  assertPretty (mkStr "echo $foo")   "\"echo \\$foo\""
-  assertPretty (mkStr "echo ${foo}") "\"echo \\${foo}\""
+case_string_antiquotation =
+  do
+    assertPretty
+      (mkStr "echo $foo")
+      "\"echo \\$foo\""
+    assertPretty
+      (mkStr "echo ${foo}")
+      "\"echo \\${foo}\""
 
 case_function_params :: Assertion
 case_function_params =
-  assertPretty (mkFunction (mkParamset mempty True) (mkInt 3)) "{ ... }:\n  3"
+  assertPretty
+    (mkFunction (mkVariadicParamSet mempty) (mkInt 3))
+    "{ ... }:\n  3"
 
 case_paths :: Assertion
-case_paths = do
-  assertPretty (mkPath False "~/test.nix") "~/test.nix"
-  assertPretty (mkPath False "/test.nix")  "/test.nix"
-  assertPretty (mkPath False "./test.nix") "./test.nix"
+case_paths =
+  do
+    assertPretty
+      (mkPath False "~/test.nix")
+      "~/test.nix"
+    assertPretty
+      (mkPath False "/test.nix")
+      "/test.nix"
+    assertPretty
+      (mkPath False "./test.nix")
+      "./test.nix"
 
 tests :: TestTree
 tests = $testGroupGenerator
 
 ---------------------------------------------------------------------------------
-assertPretty :: NExpr -> String -> Assertion
+assertPretty :: NExpr -> Text -> Assertion
 assertPretty e s =
   assertEqual ("When pretty-printing " <> show e) s . show $ prettyNix e
diff --git a/tests/ReduceExprTests.hs b/tests/ReduceExprTests.hs
--- a/tests/ReduceExprTests.hs
+++ b/tests/ReduceExprTests.hs
@@ -1,14 +1,13 @@
-{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
+{-# options_ghc -fno-warn-name-shadowing #-}
 module ReduceExprTests (tests) where
-import           Data.Fix
 import           Test.Tasty
 import           Test.Tasty.HUnit
 
-import           Nix.Atoms
 import           Nix.Expr.Types
 import           Nix.Expr.Types.Annotated
 import           Nix.Parser
 import           Nix.Reduce                     ( reduceExpr )
+import           Nix.Expr.Shorthands
 
 
 tests :: TestTree
@@ -46,13 +45,13 @@
 selectBasic = parseNixTextLoc "{b=2;a=42;}.a"
 
 selectBasicExpect :: NExpr
-selectBasicExpect = Fix . NConstant $ NInt 42
+selectBasicExpect = mkInt 42
 
 selectNested :: Result NExprLoc
 selectNested = parseNixTextLoc "{a={b=2;a=42;};b={a=2;};}.a.a"
 
 selectNestedExpect :: NExpr
-selectNestedExpect = Fix . NConstant $ NInt 42
+selectNestedExpect = mkInt 42
 
 selectIncorrectAttrPath :: Result NExprLoc
 selectIncorrectAttrPath = parseNixTextLoc "{a=42;}.b"
diff --git a/tests/TestCommon.hs b/tests/TestCommon.hs
--- a/tests/TestCommon.hs
+++ b/tests/TestCommon.hs
@@ -1,32 +1,32 @@
-{-# LANGUAGE PartialTypeSignatures #-}
-{-# LANGUAGE ScopedTypeVariables #-}
+{-# language PartialTypeSignatures #-}
 
 module TestCommon where
 
 import           GHC.Err                        ( errorWithoutStackTrace )
 import           Control.Monad.Catch
 import           Data.Time
+import           Data.Text.IO as Text
 import           Nix
 import           Nix.Standard
 import           Nix.Fresh.Basic
 import           System.Environment
 import           System.IO
-import           System.Posix.Files
-import           System.Posix.Temp
+import           System.PosixCompat.Files
+import           System.PosixCompat.Temp
 import           System.Process
 import           Test.Tasty.HUnit
 
-hnixEvalFile :: Options -> FilePath -> IO (StdValue (StandardT (StdIdT IO)))
+hnixEvalFile :: Options -> Path -> IO (StdValue (StandardT (StdIdT IO)))
 hnixEvalFile opts file =
   do
     parseResult <- parseNixFileLoc file
     either
-      (\ err -> fail $ "Parsing failed for file `" <> file <> "`.\n" <> show err)
+      (\ err -> fail $ "Parsing failed for file `" <> coerce file <> "`.\n" <> show err)
       (\ expr ->
         do
           setEnv "TEST_VAR" "foo"
           runWithBasicEffects opts $
-            catch (evaluateExpression (pure file) nixEvalExprLoc normalForm expr) $
+            catch (evaluateExpression (pure $ coerce file) nixEvalExprLoc normalForm expr) $
             \case
               NixException frames ->
                 errorWithoutStackTrace . show
@@ -46,30 +46,31 @@
     )
     (parseNixText src)
 
-nixEvalString :: String -> IO String
-nixEvalString expr = do
-  (fp, h) <- mkstemp "nix-test-eval"
-  hPutStr h expr
-  hClose h
-  res <- nixEvalFile fp
-  removeLink fp
-  pure res
+nixEvalString :: Text -> IO Text
+nixEvalString expr =
+  do
+    (coerce -> fp, h) <- mkstemp "nix-test-eval"
+    Text.hPutStr h expr
+    hClose h
+    res <- nixEvalFile fp
+    removeLink $ coerce fp
+    pure res
 
-nixEvalFile :: FilePath -> IO String
-nixEvalFile fp = readProcess "nix-instantiate" ["--eval", "--strict", fp] ""
+nixEvalFile :: Path -> IO Text
+nixEvalFile fp = fromString <$> readProcess "nix-instantiate" ["--eval", "--strict", coerce fp] mempty
 
-assertEvalFileMatchesNix :: FilePath -> Assertion
-assertEvalFileMatchesNix fp = do
-  time    <- liftIO getCurrentTime
-  hnixVal <- (<> "\n") . printNix <$> hnixEvalFile (defaultOptions time) fp
-  nixVal  <- nixEvalFile fp
-  assertEqual fp nixVal hnixVal
+assertEvalFileMatchesNix :: Path -> Assertion
+assertEvalFileMatchesNix fp =
+  do
+    time    <- liftIO getCurrentTime
+    hnixVal <- (<> "\n") . printNix <$> hnixEvalFile (defaultOptions time) fp
+    nixVal  <- nixEvalFile fp
+    assertEqual (coerce fp) nixVal hnixVal
 
 assertEvalMatchesNix :: Text -> Assertion
-assertEvalMatchesNix expr = do
-  time    <- liftIO getCurrentTime
-  hnixVal <- (<> "\n") . printNix <$> hnixEvalText (defaultOptions time) expr
-  nixVal  <- nixEvalString expr'
-  assertEqual expr' nixVal hnixVal
- where
-  expr' = toString expr
+assertEvalMatchesNix expr =
+  do
+    time    <- liftIO getCurrentTime
+    hnixVal <- (<> "\n") . printNix <$> hnixEvalText (defaultOptions time) expr
+    nixVal  <- nixEvalString expr
+    assertEqual (toString expr) nixVal hnixVal
