diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,17 +1,60 @@
 
 # Changelog
 
-## [(diff)](https://github.com/haskell-nix/hnix/compare/0.11.0...master#files_bucket) Progress
+## [(diff)](https://github.com/haskell-nix/hnix/compare/0.12.0...master#files_bucket) Progress
 
+
+## [(diff)](https://github.com/haskell-nix/hnix/compare/0.11.1...0.12.0#files_bucket) 0.12.0 (2020-12-31)
+
+* *Disclaimer*: Current `derivationStrict` primOp implementation and so every evaluation of a derivation into a store path currently relies on the `hnix-store-remote`, which for those operations relies on the running `nix-daemon`, and so operations use/produce effects into the `/nix/store`. Be cautious - it is effectful.
+
+* Introduction:
+  * New module `Nix.Effects.Derivation`.
+  * Operations on derivations:
+    * old got principled implementations.
+    * also new operations got introduced.
+  * Implementation of the `derivationStrict` primOp.
+
+* Breaking:
+  * [(link)](https://github.com/haskell-nix/hnix/pull/554/3bba5549273c892c60aad5dd6d5058a8db40efbf) `Nix.Effects`: **class** `MonadStore` got principled implementation.
+    * `addPath'` got principled into `addToStore`.
+    * `toFile_` got principled into `addTextToStore'`.
+    * For help & easy migration you may use `addPath` & `toFile_` `addTextToStore` standalone functions in the module.
+  * [(link)](https://github.com/haskell-nix/hnix/pull/554/3bba5549273c892c60aad5dd6d5058a8db40efbf) `Nix.Effects.Basic`: `defaultDerivationStrict` got reimplemented & moved into `Nix.Effects.Derivation`.
+  * [(link)](https://github.com/haskell-nix/hnix/pull/554/3bba5549273c892c60aad5dd6d5058a8db40efbf) `Nix.Standard`: instance for `MonadStore (Fix1T t m)` got principled accoding to class `MonadStore` changes.
+  * [(link)](https://github.com/haskell-nix/hnix/pull/554/3bba5549273c892c60aad5dd6d5058a8db40efbf) `Nix.Fresh.Basic`: instance for `MonadStore (StdIdT m)` got principled.
+
+* Additional:
+  * [(link)](https://github.com/haskell-nix/hnix/pull/554/3bba5549273c892c60aad5dd6d5058a8db40efbf) **New module `Nix.Effects.Derivation`**: [HNix(0.12.0):Nix.Effects.Derivation documentation](https://hackage.haskell.org/package/hnix-0.12.0/docs/Nix-Effects-Derivation.html).
+  * [(link)](https://github.com/haskell-nix/hnix/pull/554/commits/9bcfbbe88ff0bd8d803296193ee1d8603dc5289e) `Nix.Convert`: Principled `NVPath -> NixString` coercion.
+    * In a form of principled `instance FromValue NixString m (NValue' t f m (NValue t f m))`.
+  * [(link)](https://github.com/haskell-nix/hnix/pull/554/commits/a8e6d28fdb98a1c34f425c8395338fdabe96becc) `Nix.String`: Allow custom computations inside string contexts.
+    * By providing `runWithStringContext{T,}'` methods into the API.
+  * [(link)](https://github.com/haskell-nix/hnix/pull/554/commits/e45f7632c51a9657f6e8d54c39fd4d21c466d85f) Includded support for new `base16-bytestring`, which advertices 2x-4x speed increase of its operations.
+  * [(link)](https://github.com/haskell-nix/hnix/pull/554/3bba5549273c892c60aad5dd6d5058a8db40efbf) `Nix.Effects`: `addPath` & `toFile_` standalone functions got principled implementation through the internal use of the new `MonadStore` type class implementation.
+  * [(link)](https://github.com/haskell-nix/hnix/pull/554/3bba5549273c892c60aad5dd6d5058a8db40efbf) `Nix.Effects`: added `addTextToStore`, `parseStoreResult` implementations.
+  * [(link)](https://github.com/haskell-nix/hnix/pull/554/3bba5549273c892c60aad5dd6d5058a8db40efbf) `Nix.Effects`: added type synonyms `{RecursiveFlag, RepairFlag, StorePathName, FilePathFilter, StorePathSet}`.
+  * [(link)](https://github.com/haskell-nix/hnix/pull/760) `Nix.Exec`: Fixed the rendering of internal `Frames`.
+    * Which is an internal mechanism of a project to passing around messages with their context, still to be used internally).
+  * [(link)](https://github.com/haskell-nix/hnix/pull/554/3bba5549273c892c60aad5dd6d5058a8db40efbf) `HNix / Nix`: The library now also uses `hnix-store-remote`.
+  * [(link)](https://github.com/haskell-nix/hnix/pull/554/06b0fca9fd607eb2e995f003424e797a41ffa5b7) `cabal.project`: project uses `cryptohash-sha512` override, the `hnix-store-core` requires it from `hnix` and uses that override also. [Detailed info](https://github.com/haskell-hvr/cryptohash-sha512/pull/5#issuecomment-752796913). We promise to attend to this issue, probably by migrating to `cryptonite` in the nearest releases.
+
+Future note: The HNix is a big project. During the initial development and currently the API for simplicity exposes allmost all functions, types, etc. Big open API means a big effort to create/maintain a quite noisy changelog and you parsing through it, and also creates a frequent requirements to mark releases as major and bother you due to some type changes in some parts that may not be used or applicable to be public API.
+
+This year the most gracious API clean-up would happen, we would check and keep open what Hackage projects are using from the API, and the other parts would be open on the request by a way of rapid minor releases. That clean-up is also a work toward splitting the project into several packages some time in the future (split would be into something like Expressions, Evaluation, Executable, Extra), which migration also would be done most thoughful and graceful as possible, with as much easiness and automation provided for migration downstream as possible. If all goes as planned - all downstream would need to do is to get and run our script that would migrate our old map of module imports to new ones, and maybe manually add those newly formed packages into `.cabal` description.
+
+If it is possible, please, try to switch & use the higher-level API functions where it is applicable. Thank you.
+
+
 ### [(diff)](https://github.com/haskell-nix/hnix/compare/0.11.0...0.11.1#files_bucket) 0.11.1 (2020-12-09)
 
 * Additional:
-  * [(link)](https://github.com/haskell-nix/hnix/commit/d32a6fbaf3df1c8879d1b19a18f21c031a73e56c) `Nix/Builtins`: `isString` fixed - It used to return `True` for values coercible to string like derivations and paths. It only accepts string values now.
-  * [(link)](https://github.com/haskell-nix/hnix/commit/53b4db2525a8f074d8c262fa7b66ce97e5820890) `Nix/Builtins`: `substring` fixed - Negative lengths used to capture an empty string. Now they capture the whole rmeainder of the string.
-  * [(link)](https://github.com/haskell-nix/hnix/commit/dc31c5e64f8c7aaaea14cac0134bd47544533e67) `Nix/Effects`: `pathExists` fixed - Now also works with directories.
-  * [(link)](https://github.com/haskell-nix/hnix/commit/e2ad934492eeac9881527610e4a1c1cf31ea1115) `Nix/Parser`: `->` is now properly right-associative (was non-associative).
-  * [(link)](https://github.com/haskell-nix/hnix/commit/50baea5e1e482be3c4fcc13c9a45b1083243f681) `Nix/Parser`: Nix `assert` parser (`nixAssert` function) now accepts top-level Nix format also (which means also accepts all kinds of statements), before that it accepted only regular Nix expressions.
-  * [(link)](https://github.com/haskell-nix/hnix/commit/59698de7185dfae508e5ccea4377a82023c4a0d5) `Nix/Render`: `renderLocation` now also shows/handles location of errors in raw strings.
+  * [(link)](https://github.com/haskell-nix/hnix/commit/d32a6fbaf3df1c8879d1b19a18f21c031a73e56c) `Nix.Builtins`: `isString` fixed - It used to return `True` for values coercible to string like derivations and paths. It only accepts string values now.
+  * [(link)](https://github.com/haskell-nix/hnix/commit/53b4db2525a8f074d8c262fa7b66ce97e5820890) `Nix.Builtins`: `substring` fixed - Negative lengths used to capture an empty string. Now they capture the whole rmeainder of the string.
+  * [(link)](https://github.com/haskell-nix/hnix/commit/dc31c5e64f8c7aaaea14cac0134bd47544533e67) `Nix.Effects`: `pathExists` fixed - Now also works with directories.
+  * [(link)](https://github.com/haskell-nix/hnix/commit/e2ad934492eeac9881527610e4a1c1cf31ea1115) `Nix.Parser`: `->` is now properly right-associative (was non-associative).
+  * [(link)](https://github.com/haskell-nix/hnix/commit/50baea5e1e482be3c4fcc13c9a45b1083243f681) `Nix.Parser`: Nix `assert` parser (`nixAssert` function) now accepts top-level Nix format also (which means also accepts all kinds of statements), before that it accepted only regular Nix expressions.
+  * [(link)](https://github.com/haskell-nix/hnix/commit/59698de7185dfae508e5ccea4377a82023c4a0d5) `Nix.Render`: `renderLocation` now also shows/handles location of errors in raw strings.
 
 
 ## [(diff)](https://github.com/haskell-nix/hnix/compare/0.10.1...0.11.0#files_bucket) 0.11.0 (2020-11-02)
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -17,7 +17,6 @@
 <!-- START doctoc generated TOC please keep comment here to allow auto update -->
 <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
 
-
 - [Prerequisites](#prerequisites)
 - [Getting Started](#getting-started)
   - [Cachix prebuild binary caches](#cachix-prebuild-binary-caches)
@@ -44,11 +43,13 @@
 ## Prerequisites
 Tooling is WIP, `nix-shell` and `nix-store` are still used for their purpose, so, to access them Nix is required to be installed.
 
+*Disclaimer*: Current `derivationStrict` primOp implementation and so every evaluation of a derivation into a store path currently relies on the `hnix-store-remote`, which for those operations relies on the running `nix-daemon`, and so operations use/produce effects into the `/nix/store`. Be cautious - it is effectful.
+
 ## Getting Started
 
-```
+```shell
 # Note: --recursive
-git clone --recursive https://github.com/haskell-nix/hnix.git
+git clone --recursive 'https://github.com/haskell-nix/hnix.git'
 cd hnix
 ```
 
@@ -67,35 +68,35 @@
 Cabal [Quickstart](https://cabal.readthedocs.io/en/3.4/nix-local-build.html).
 
 1. (Optional), to enter the projects reproducible Nix environment:
-```
+```shell
 nix-shell
 ```
   
 2. Building:
-```
+```shell
 cabal v2-configure
 cabal v2-build
 ```
 
 3. Loading the project into `ghci` REPL:
-```
+```shell
 cabal v2-repl
 ```
 
 4. Testing:
 
 * Default suite:
-```
+```shell
 cabal v2-test
 ```
 
 * All available tests:
-```
+```shell
 env ALL_TESTS=yes cabal v2-test
 ```
 
 * Selected (list of tests is in `tests/Main.hs`):
-```
+```shell
 env NIXPKGS_TESTS=yes PRETTY_TESTS=1 cabal v2-test
 ```
 
@@ -105,7 +106,7 @@
 
 To run benchmarks:
 
-```
+```shell
 cabal v2-bench
 ```
 
@@ -113,7 +114,7 @@
 
 To build `hnix` with profiling enabled:
 
-```
+```shell
 cabal v2-configure --enable-tests --enable-profiling --flags=profiling
 cabal v2-run hnix -- <args> +RTS -p
 ```
@@ -122,7 +123,7 @@
 
 To build `hnix` for debugging, with full tracing output and stack traces:
 
-```
+```shell
 cabal v2-configure --enable-tests --enable-profiling --flags=profiling --flags=tracing
 cabal v2-run hnix -- -v5 --trace <args> +RTS -xc
 ```
@@ -131,7 +132,7 @@
 
 
 #### Run HNix
-```
+```shell
 cabal v2-run hnix -- --help
 ```
 (`--` is for separation between `cabal` & `hnix` args)
@@ -142,7 +143,7 @@
 There is a number of build options to use with `nix-build`, documentation of them is in: `./default.nix`, keys essentially pass-through the [Nixpkgs Haskell Lib API](https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/haskell-modules/lib.nix).
 
 Options can be used as:
-```
+```shell
 nix-build \
   --arg <option1> <argument1> \
   --arg <option2> <argument2> \
@@ -151,7 +152,7 @@
 
 #### Run benchmarks
 
-```
+```shell
 nix-build \
   --arg disableOptimization false \
   --arg enableDeadCodeElimination true \
@@ -161,7 +162,7 @@
 
 #### With profiling
 
-```
+```shell
 nix-build \
   --arg disableOptimization false \
   --arg enableDeadCodeElimination true \
@@ -171,7 +172,7 @@
 
 #### With full debug info
 
-```
+```shell
 nix-build \
   --arg disableOptimization false \
   --arg enableDeadCodeElimination true \
@@ -185,13 +186,13 @@
 
 #### Run the result
 
-```
+```shell
 ./result/bin/hnix
 ```
 
 ## Development status loop with amazing [`ghcid`](https://github.com/ndmitchell/ghcid)
 
-```
+```shell
 ghcid --command="cabal v2-repl --repl-options=-fno-code --repl-options=-fno-break-on-exception --repl-options=-fno-break-on-error --repl-options=-v1 --repl-options=-ferror-spans --repl-options=-j"
 ```
 (optional) To use projects reproducible environment, wrap `ghcid ...` command into a `nix-shell --command ' '`.
@@ -202,12 +203,12 @@
 ## Using the HNix REPL
 
 Enter in:
-```
+```shell
 hnix --repl
 ```
 
 Evaluate an expression:
-```
+```shell
 hnix --eval -E '(import <nixpkgs> {}).pkgs.hello' --repl
 ```
 This also binds the evaluated expression result to the `input` variable, so that variable can be inspected.
@@ -218,7 +219,7 @@
 
 Nix is a lazy language with the ability of recursion, so by default REPL and eval prints are lazy:
 
-```
+```shell
 hnix \
   --eval \
   --expr '{ x = true; }'
@@ -228,7 +229,7 @@
 
 To disable laziness add the `--strict` to commands or `:set strict` in the REPL.
 
-```
+```shell
 hnix \
   --eval \
   # Strictly \
@@ -246,7 +247,7 @@
 
 3. When the pull request is ready to be submitted, to save time - please, test it with:
 
-```
+```shell
 git submodule update --init --recursive
 nix-shell --run "LANGUAGE_TESTS=yes cabal v2-test"
 ```
@@ -257,6 +258,6 @@
 
 Currently, the main high-level goal is to be able to evaluate all of Nixpkgs:
 
-```
+```shell
 hnix --eval -E "import <nixpkgs> {}" --find
 ```
diff --git a/data/nix/corepkgs/fetchurl.nix b/data/nix/corepkgs/fetchurl.nix
new file mode 100644
--- /dev/null
+++ b/data/nix/corepkgs/fetchurl.nix
@@ -0,0 +1,41 @@
+{ system ? "" # obsolete
+, url
+, hash ? "" # an SRI ash
+
+# Legacy hash specification
+, md5 ? "", sha1 ? "", sha256 ? "", sha512 ? ""
+, outputHash ?
+    if hash != "" then hash else if sha512 != "" then sha512 else if sha1 != "" then sha1 else if md5 != "" then md5 else sha256
+, outputHashAlgo ?
+    if hash != "" then "" else if sha512 != "" then "sha512" else if sha1 != "" then "sha1" else if md5 != "" then "md5" else "sha256"
+
+, executable ? false
+, unpack ? false
+, name ? baseNameOf (toString url)
+}:
+
+derivation {
+  builder = "builtin:fetchurl";
+
+  # New-style output content requirements.
+  inherit outputHashAlgo outputHash;
+  outputHashMode = if unpack || executable then "recursive" else "flat";
+
+  inherit name url executable unpack;
+
+  system = "builtin";
+
+  # No need to double the amount of network traffic
+  preferLocalBuild = true;
+
+  impureEnvVars = [
+    # We borrow these environment variables from the caller to allow
+    # easy proxy configuration.  This is impure, but a fixed-output
+    # derivation like fetchurl is allowed to do so since its result is
+    # by definition pure.
+    "http_proxy" "https_proxy" "ftp_proxy" "all_proxy" "no_proxy"
+  ];
+
+  # To make "nix-prefetch-url" work.
+  urls = [ url ];
+}
diff --git a/data/nix/corepkgs/imported-drv-to-derivation.nix b/data/nix/corepkgs/imported-drv-to-derivation.nix
new file mode 100644
--- /dev/null
+++ b/data/nix/corepkgs/imported-drv-to-derivation.nix
@@ -0,0 +1,21 @@
+attrs @ { drvPath, outputs, name, ... }:
+
+let
+
+  commonAttrs = (builtins.listToAttrs outputsList) //
+    { all = map (x: x.value) outputsList;
+      inherit drvPath name;
+      type = "derivation";
+    };
+
+  outputToAttrListElement = outputName:
+    { name = outputName;
+      value = commonAttrs // {
+        outPath = builtins.getAttr outputName attrs;
+        inherit outputName;
+      };
+    };
+    
+  outputsList = map outputToAttrListElement outputs;
+    
+in (builtins.head outputsList).value
diff --git a/data/nix/corepkgs/unpack-channel.nix b/data/nix/corepkgs/unpack-channel.nix
new file mode 100644
--- /dev/null
+++ b/data/nix/corepkgs/unpack-channel.nix
@@ -0,0 +1,39 @@
+with import <nix/config.nix>;
+
+let
+
+  builder = builtins.toFile "unpack-channel.sh"
+    ''
+      mkdir $out
+      cd $out
+      xzpat="\.xz\$"
+      gzpat="\.gz\$"
+      if [[ "$src" =~ $xzpat ]]; then
+        ${xz} -d < $src | ${tar} xf - ${tarFlags}
+      elif [[ "$src" =~ $gzpat ]]; then
+        ${gzip} -d < $src | ${tar} xf - ${tarFlags}
+      else
+        ${bzip2} -d < $src | ${tar} xf - ${tarFlags}
+      fi
+      if [ * != $channelName ]; then
+        mv * $out/$channelName
+      fi
+    '';
+
+in
+
+{ name, channelName, src }:
+
+derivation {
+  system = builtins.currentSystem;
+  builder = shell;
+  args = [ "-e" builder ];
+  inherit name channelName src;
+
+  PATH = "${nixBinDir}:${coreutils}";
+
+  # No point in doing this remotely.
+  preferLocalBuild = true;
+
+  inherit chrootDeps;
+}
diff --git a/hnix.cabal b/hnix.cabal
--- a/hnix.cabal
+++ b/hnix.cabal
@@ -1,5 +1,5 @@
 name:           hnix
-version:        0.11.1
+version:        0.12.0
 synopsis:       Haskell implementation of the Nix language
 description:    Haskell implementation of the Nix language.
 category:       System, Data, Nix
@@ -11,9 +11,19 @@
 license-file:   LICENSE
 build-type:     Simple
 cabal-version:  >= 1.10
+data-dir:       data/
+data-files:
+    nix/corepkgs/buildenv.nix
+    nix/corepkgs/unpack-channel.nix
+    nix/corepkgs/derivation.nix
+    nix/corepkgs/fetchurl.nix
+    nix/corepkgs/imported-drv-to-derivation.nix
 extra-source-files:
     data/nix/corepkgs/buildenv.nix
+    data/nix/corepkgs/unpack-channel.nix
     data/nix/corepkgs/derivation.nix
+    data/nix/corepkgs/fetchurl.nix
+    data/nix/corepkgs/imported-drv-to-derivation.nix
     data/nix/tests/lang/binary-data
     data/nix/tests/lang/data
     data/nix/tests/lang/dir1/a.nix
@@ -341,6 +351,7 @@
       Nix.Convert
       Nix.Effects
       Nix.Effects.Basic
+      Nix.Effects.Derivation
       Nix.Eval
       Nix.Exec
       Nix.Expr
@@ -401,8 +412,9 @@
     , gitrev >= 1.1.0 && < 1.4
     , hashable >= 1.2.5 && < 1.4
     , hashing >= 0.1.0 && < 0.2
-    , hnix-store-core >= 0.1.0 && < 0.3
-    , http-client >= 0.5.14 && < 0.6 || >= 0.6.4 && < 0.8
+    , hnix-store-core >= 0.4.0 && < 0.5
+    , hnix-store-remote >= 0.4.0 && < 0.5
+    , http-client >= 0.5.14 && < 0.6 || >= 0.6.4 && < 0.7
     , http-client-tls >= 0.3.5 && < 0.4
     , http-types >= 0.12.2 && < 0.13
     , lens-family >= 1.2.2 && < 2.2
@@ -442,7 +454,7 @@
     exposed-modules:
         Nix.Options.Parser
     build-depends:
-        base16-bytestring >= 0.1.1 && < 0.2
+        base16-bytestring >= 0.1.1 && < 1.1
       , pretty-show >= 1.9.5 && < 1.11
       , serialise >= 0.2.1 && < 0.3
   -- if !flag(profiling)
diff --git a/main/Repl.hs b/main/Repl.hs
--- a/main/Repl.hs
+++ b/main/Repl.hs
@@ -124,9 +124,9 @@
     | s `Data.List.isPrefixOf` x = m args
     | otherwise = optMatcher s xs args
 
--------------------------------------------------------------------------------
--- Types
--------------------------------------------------------------------------------
+---------------------------------------------------------------------------------
+-- * Types
+---------------------------------------------------------------------------------
 
 data IState t f m = IState
   { replIt  :: Maybe NExprLoc          -- ^ Last expression entered
@@ -171,9 +171,9 @@
 
 type Repl e t f m = HaskelineT (StateT (IState t f m) m)
 
--------------------------------------------------------------------------------
--- Execution
--------------------------------------------------------------------------------
+---------------------------------------------------------------------------------
+-- * Execution
+---------------------------------------------------------------------------------
 
 exec
   :: forall e t f m
@@ -257,9 +257,9 @@
       | cfgValues cfg -> liftIO . print . prettyNValueProv =<< removeEffects val
       | otherwise     -> liftIO . print . prettyNValue =<< removeEffects val
 
--------------------------------------------------------------------------------
--- Commands
--------------------------------------------------------------------------------
+---------------------------------------------------------------------------------
+-- * Commands
+---------------------------------------------------------------------------------
 
 -- :browse command
 browse :: (MonadNix e t f m, MonadIO m)
@@ -315,9 +315,9 @@
       [opt] -> modify (\s -> s { replCfg = helpSetOptionFunction opt (replCfg s) })
       _     -> liftIO $ putStrLn "No such option"
 
--------------------------------------------------------------------------------
--- Interactive Shell
--------------------------------------------------------------------------------
+---------------------------------------------------------------------------------
+-- * Interactive Shell
+---------------------------------------------------------------------------------
 
 -- Prefix tab completer
 defaultMatcher :: MonadIO m => [(String, CompletionFunc m)]
diff --git a/src/Nix/Atoms.hs b/src/Nix/Atoms.hs
--- a/src/Nix/Atoms.hs
+++ b/src/Nix/Atoms.hs
@@ -45,9 +45,9 @@
 atomText (NURI   t) = t
 atomText (NInt   i) = pack (show i)
 atomText (NFloat f) = pack (showNixFloat f)
-  where
-    showNixFloat x
-      | x `mod'` 1 /= 0 = show x
-      | otherwise       = show (truncate x :: Int)
+ where
+  showNixFloat x
+    | x `mod'` 1 /= 0 = show x
+    | otherwise       = show (truncate x :: Int)
 atomText (NBool  b) = if b then "true" else "false"
 atomText NNull      = "null"
diff --git a/src/Nix/Builtins.hs b/src/Nix/Builtins.hs
--- a/src/Nix/Builtins.hs
+++ b/src/Nix/Builtins.hs
@@ -22,6 +22,7 @@
 {-# OPTIONS_GHC -Wno-missing-signatures #-}
 {-# 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 (withNixContext, builtins) where
 
 import           Control.Comonad
@@ -1197,6 +1198,7 @@
 -- prim_hashString from nix/src/libexpr/primops.cc
 -- fail if context in the algo arg
 -- propagate context from the s arg
+-- | The result coming out of hashString is base16 encoded
 hashString
   :: forall e t f m. MonadNix e t f m => NixString -> NixString -> Prim m NixString
 hashString nsAlgo ns = Prim $ do
@@ -1232,10 +1234,16 @@
     $ principledMakeNixStringWithoutContext
     $ Text.cons '/'
     $ Base32.encode
-    $ fst             -- The result coming out of hashString is base16 encoded
-    $ Base16.decode
-    $ encodeUtf8
-    $ principledStringIgnoreContext h
+    $ case Base16.decode (text h) of -- The result coming out of hashString is base16 encoded
+#if MIN_VERSION_base16_bytestring(1,0,0)
+      Right d -> d
+      Left e -> error $ "Couldn't Base16 decode the text: '" <> show (text h) <> "'.\nThe Left error content: '" <> e <> "'."
+#else
+      (d, "") -> d
+      (_, e) -> error $ "Couldn't Base16 decode the text: '" <> show (text h) <> "'.\nUndecodable remainder: '" <> show e <> "'."
+#endif
+   where
+    text h = encodeUtf8 $ principledStringIgnoreContext h
 
 absolutePathFromValue :: MonadNix e t f m => NValue t f m -> m FilePath
 absolutePathFromValue = \case
diff --git a/src/Nix/Cache.hs b/src/Nix/Cache.hs
--- a/src/Nix/Cache.hs
+++ b/src/Nix/Cache.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE CPP #-}
 
+-- | Reading and writing Nix cache files
 module Nix.Cache where
 
 import qualified Data.ByteString.Lazy          as BS
@@ -38,7 +39,7 @@
 writeCache :: FilePath -> NExprLoc -> IO ()
 writeCache path expr =
 #ifdef USE_COMPACT
-    C.writeCompact path =<< C.compact expr
+  C.writeCompact path =<< C.compact expr
 #else
 #ifdef MIN_VERSION_serialise
   BS.writeFile path (S.serialise expr)
diff --git a/src/Nix/Convert.hs b/src/Nix/Convert.hs
--- a/src/Nix/Convert.hs
+++ b/src/Nix/Convert.hs
@@ -61,9 +61,9 @@
 
 -}
 
-{-----------------------------------------------------------------------
-   FromValue
- -----------------------------------------------------------------------}
+---------------------------------------------------------------------------------
+-- * FromValue
+---------------------------------------------------------------------------------
 
 class FromValue a m v where
     fromValue    :: v -> m a
@@ -151,7 +151,7 @@
     NVStr' ns -> pure $ Just ns
     NVPath' p ->
       Just
-        .   hackyMakeNixStringWithoutContext
+        .   (\s -> principledMakeNixStringWithSingletonContext s (StringContext s DirectPath))
         .   Text.pack
         .   unStorePath
         <$> addPath p
@@ -260,9 +260,9 @@
   fromValueMay = fromValueMay . getDeeper
   fromValue    = fromValue . getDeeper
 
-{-----------------------------------------------------------------------
-   ToValue
- -----------------------------------------------------------------------}
+---------------------------------------------------------------------------------
+-- * ToValue
+---------------------------------------------------------------------------------
 
 class ToValue a m v where
     toValue :: a -> m v
diff --git a/src/Nix/Effects.hs b/src/Nix/Effects.hs
--- a/src/Nix/Effects.hs
+++ b/src/Nix/Effects.hs
@@ -7,6 +7,8 @@
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE TypeApplications #-}
 
 module Nix.Effects where
 
@@ -17,24 +19,31 @@
 import qualified Prelude
 
 import           Control.Monad.Trans
+import qualified Data.HashSet                  as HS
 import           Data.Text                      ( Text )
 import qualified Data.Text                     as T
-import           Network.HTTP.Client     hiding ( path )
+import qualified Data.Text.Encoding            as T
+import           Network.HTTP.Client     hiding ( path, Proxy )
 import           Network.HTTP.Client.TLS
 import           Network.HTTP.Types
 import           Nix.Expr
-import           Nix.Frames
+import           Nix.Frames              hiding ( Proxy )
 import           Nix.Parser
 import           Nix.Render
 import           Nix.Utils
 import           Nix.Value
 import qualified Paths_hnix
-import qualified System.Directory              as S
 import           System.Environment
 import           System.Exit
+import           System.FilePath                ( takeFileName )
 import qualified System.Info
 import           System.Process
 
+import qualified System.Nix.Hash               as Store
+import qualified System.Nix.Store.Remote       as Store
+import qualified System.Nix.Store.Remote.Types as Store
+import qualified System.Nix.StorePath          as Store
+
 -- | A path into the nix store
 newtype StorePath = StorePath { unStorePath :: FilePath }
 
@@ -226,36 +235,55 @@
 instance MonadPutStr IO where
   putStr = Prelude.putStr
 
+
+type RecursiveFlag = Bool
+type RepairFlag = Bool
+type StorePathName = Text
+type FilePathFilter m = FilePath -> m Bool
+type StorePathSet = HS.HashSet StorePath
+
 class Monad m => MonadStore m where
-    -- | Import a path into the nix store, and return the resulting path
-    addPath' :: FilePath -> m (Either ErrorCall StorePath)
 
-    -- | Add a file with the given name and contents to the nix store
-    toFile_' :: FilePath -> String -> m (Either ErrorCall StorePath)
+    -- | 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 a b c d = lift $ addToStore a b c d
 
+    -- | Like addToStore, but the contents written to the output path is a
+    -- regular file containing the given string.
+    addTextToStore' :: StorePathName -> Text -> Store.StorePathSet -> RepairFlag -> m (Either ErrorCall StorePath)
+    default addTextToStore' :: (MonadTrans t, MonadStore m', m ~ t m') => StorePathName -> Text -> Store.StorePathSet -> RepairFlag -> m (Either ErrorCall StorePath)
+    addTextToStore' a b c d = lift $ addTextToStore' a b c d
+
+parseStoreResult :: Monad m => String -> (Either String a, [Store.Logger]) -> m (Either ErrorCall a)
+parseStoreResult name res = case res of
+  (Left msg, logs) -> return $ Left $ ErrorCall $ "Failed to execute '" ++ name ++ "': " ++ msg ++ "\n" ++ show logs
+  (Right result, _) -> return $ Right result
+
 instance MonadStore IO where
-  addPath' path = do
-    (exitCode, out, _) <- readProcessWithExitCode "nix-store" ["--add", path] ""
-    case exitCode of
-      ExitSuccess -> do
-        let dropTrailingLinefeed p = take (length p - 1) p
-        pure $ Right $ StorePath $ dropTrailingLinefeed out
-      _ ->
-        pure
-          $  Left
-          $  ErrorCall
-          $  "addPath: failed: nix-store --add "
-          ++ show path
 
-  --TODO: Use a temp directory so we don't overwrite anything important
-  toFile_' filepath content = do
-    writeFile filepath content
-    storepath <- addPath' filepath
-    S.removeFile filepath
-    pure storepath
+  addToStore name path recursive repair = case Store.makeStorePathName name of
+    Left err -> return $ Left $ ErrorCall $ "String '" ++ show name ++ "' is not a valid path name: " ++ err
+    Right pathName -> do
+      -- TODO: redesign the filter parameter
+      res <- Store.runStore $ Store.addToStore @'Store.SHA256 pathName path recursive (const False) repair
+      parseStoreResult "addToStore" res >>= \case
+        Left err -> return $ Left err
+        Right storePath -> return $ Right $ StorePath $ T.unpack $ T.decodeUtf8 $ Store.storePathToRawFilePath storePath
 
+  addTextToStore' name text references repair = do
+    res <- Store.runStore $ Store.addTextToStore name text references repair
+    parseStoreResult "addTextToStore" res >>= \case
+      Left err -> return $ Left err
+      Right path -> return $ Right $ StorePath $ T.unpack $ T.decodeUtf8 $ Store.storePathToRawFilePath path
+
+addTextToStore :: (Framed e m, MonadStore m) => StorePathName -> Text -> Store.StorePathSet -> RepairFlag -> m StorePath
+addTextToStore a b c d = either throwError return =<< addTextToStore' a b c d
+
 addPath :: (Framed e m, MonadStore m) => FilePath -> m StorePath
-addPath p = either throwError pure =<< addPath' p
+addPath p = either throwError return =<< addToStore (T.pack $ takeFileName p) p True False
 
 toFile_ :: (Framed e m, MonadStore m) => FilePath -> String -> m StorePath
-toFile_ p contents = either throwError pure =<< toFile_' p contents
+toFile_ p contents = addTextToStore (T.pack p) (T.pack contents) HS.empty 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
@@ -8,10 +8,6 @@
 {-# LANGUAGE TypeApplications #-}
 {-# LANGUAGE ViewPatterns #-}
 
-{-# OPTIONS_GHC -Wno-missing-signatures #-}
-{-# OPTIONS_GHC -Wno-orphans #-}
-{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
-
 module Nix.Effects.Basic where
 
 import           Control.Monad
@@ -20,30 +16,24 @@
 import qualified Data.HashMap.Lazy             as M
 import           Data.List
 import           Data.List.Split
-import           Data.Maybe                     ( maybeToList )
 import           Data.Text                      ( Text )
 import qualified Data.Text                     as Text
-import           Nix.Atoms
+import           Data.Text.Prettyprint.Doc
 import           Nix.Convert
 import           Nix.Effects
 import           Nix.Exec                       ( MonadNix
-                                                , callFunc
                                                 , evalExprLoc
                                                 , nixInstantiateExpr
                                                 )
 import           Nix.Expr
 import           Nix.Frames
-import           Nix.Normal
 import           Nix.Parser
-import           Nix.Pretty
 import           Nix.Render
 import           Nix.Scope
 import           Nix.String
-import           Nix.String.Coerce
 import           Nix.Utils
 import           Nix.Value
 import           Nix.Value.Monad
-import           Prettyprinter
 import           System.FilePath
 
 #ifdef MIN_VERSION_ghc_datasize
@@ -126,8 +116,8 @@
   -> [NValue t f m]
   -> FilePath
   -> m FilePath
-findPathBy finder l name = do
-  mpath <- foldM go Nothing l
+findPathBy finder ls name = do
+  mpath <- foldM go Nothing ls
   case mpath of
     Nothing ->
       throwError
@@ -235,13 +225,13 @@
     pure $ if exists then Just apath else Nothing
 
 defaultImportPath
-  :: (MonadNix e t f m, MonadState (HashMap FilePath NExprLoc) m)
+  :: (MonadNix e t f m, MonadState (HashMap FilePath NExprLoc, b) m)
   => FilePath
   -> m (NValue t f m)
 defaultImportPath path = do
   traceM $ "Importing file " ++ path
   withFrame Info (ErrorCall $ "While importing file " ++ show path) $ do
-    imports <- get
+    imports <- gets fst
     evalExprLoc =<< case M.lookup path imports of
       Just expr -> pure expr
       Nothing   -> do
@@ -252,7 +242,7 @@
               $ ErrorCall
               . show $ fillSep ["Parse during import failed:", err]
           Success expr -> do
-            modify (M.insert path expr)
+            modify (\(a, b) -> (M.insert path expr a, b))
             pure expr
 
 defaultPathToDefaultNix :: MonadNix e t f m => FilePath -> m FilePath
@@ -263,39 +253,6 @@
 pathToDefaultNixFile p = do
   isDir <- doesDirectoryExist p
   pure $ if isDir then p </> "default.nix" else p
-
-defaultDerivationStrict
-  :: forall e t f m . MonadNix e t f m => NValue t f m -> m (NValue t f m)
-defaultDerivationStrict = fromValue @(AttrSet (NValue t f m)) >=> \s -> do
-  nn <- maybe (pure False) (demand ?? fromValue) (M.lookup "__ignoreNulls" s)
-  s' <- M.fromList <$> mapMaybeM (handleEntry nn) (M.toList s)
-  v' <- normalForm =<< toValue @(AttrSet (NValue t f m)) @_ @(NValue t f m) s'
-  nixInstantiateExpr $ "derivationStrict " ++ show (prettyNValue v')
- where
-  mapMaybeM :: (a -> m (Maybe b)) -> [a] -> m [b]
-  mapMaybeM op = foldr f (pure [])
-    where f x xs = op x >>= (<$> xs) . (++) . maybeToList
-
-  handleEntry :: Bool -> (Text, NValue t f m) -> m (Maybe (Text, NValue t f m))
-  handleEntry ignoreNulls (k, v) = fmap (k, ) <$> case k of
-      -- The `args' attribute is special: it supplies the command-line
-      -- arguments to the builder.
-      -- TODO This use of coerceToString is probably not right and may
-      -- not have the right arguments.
-    "args"          -> demand v $ fmap Just . coerceNixList
-    "__ignoreNulls" -> pure Nothing
-    _               -> demand v $ \case
-      NVConstant NNull | ignoreNulls -> pure Nothing
-      v'                             -> Just <$> coerceNix v'
-   where
-    coerceNix :: NValue t f m -> m (NValue t f m)
-    coerceNix = toValue <=< coerceToString callFunc CopyToStore CoerceAny
-
-    coerceNixList :: NValue t f m -> m (NValue t f m)
-    coerceNixList v = do
-      xs <- fromValue @[NValue t f m] v
-      ys <- traverse (`demand` coerceNix) xs
-      toValue @[NValue t f m] ys
 
 defaultTraceEffect :: MonadPutStr m => String -> m ()
 defaultTraceEffect = Nix.Effects.putStrLn
diff --git a/src/Nix/Effects/Derivation.hs b/src/Nix/Effects/Derivation.hs
new file mode 100644
--- /dev/null
+++ b/src/Nix/Effects/Derivation.hs
@@ -0,0 +1,395 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE TypeApplications #-}
+ 
+
+module Nix.Effects.Derivation ( defaultDerivationStrict ) where
+
+import           Prelude                 hiding ( readFile )
+
+import           Control.Arrow                  ( first, second )
+import           Control.Monad                  ( (>=>), forM, when )
+import           Control.Monad.Writer           ( join, lift )
+import           Control.Monad.State            ( MonadState, gets, modify )
+
+import           Data.Char                      ( isAscii, isAlphaNum )
+import qualified Data.HashMap.Lazy             as M
+import qualified Data.HashMap.Strict           as MS
+import qualified Data.HashSet                  as S
+import           Data.List
+import qualified Data.Map.Strict               as Map
+import           Data.Map.Strict                ( Map )
+import qualified Data.Set                      as Set
+import           Data.Set                       ( Set )
+import           Data.Text                      ( Text )
+import qualified Data.Text                     as Text
+import qualified Data.Text.Encoding            as Text
+
+import           Nix.Atoms
+import           Nix.Convert
+import           Nix.Effects
+import           Nix.Exec                       ( MonadNix , callFunc)
+import           Nix.Frames
+import           Nix.Json                       ( nvalueToJSONNixString )
+import           Nix.Render
+import           Nix.String
+import           Nix.String.Coerce
+import           Nix.Utils               hiding ( readFile )
+import           Nix.Value
+import           Nix.Value.Monad
+
+import qualified System.Nix.ReadonlyStore      as Store
+import qualified System.Nix.Hash               as Store
+import qualified System.Nix.StorePath          as Store
+
+import           Text.Megaparsec
+import           Text.Megaparsec.Char
+
+
+data Derivation = Derivation
+  { name :: Text
+  , outputs :: Map Text Text
+  , inputs :: (Set Text, Map Text [Text])
+  , platform :: Text
+  , builder :: Text -- should be typed as a store path
+  , args :: [ Text ]
+  , env :: Map Text Text
+  , mFixed :: Maybe Store.SomeNamedDigest
+  , hashMode :: HashMode
+  , useJson :: Bool
+  }
+  deriving Show
+
+defaultDerivation :: Derivation
+defaultDerivation = Derivation
+  { name        = undefined
+  , outputs     = Map.empty
+  , inputs      = (Set.empty, Map.empty)
+  , platform    = undefined
+  , builder     = undefined
+  , args        = []
+  , env         = Map.empty
+  , mFixed      = Nothing
+  , hashMode    = Flat
+  , useJson     = False
+  }
+
+data HashMode = Flat | Recursive
+  deriving (Show, Eq)
+
+makeStorePathName :: (Framed e m) => Text -> m Store.StorePathName
+makeStorePathName name = case Store.makeStorePathName name of
+  Left err -> throwError $ ErrorCall $ "Invalid name '" ++ show name ++ "' for use in a store path: " ++ err
+  Right spname -> return spname
+
+parsePath :: (Framed e m) => Text -> m Store.StorePath
+parsePath p = case Store.parsePath "/nix/store" (Text.encodeUtf8 p) of
+  Left err -> throwError $ ErrorCall $ "Cannot parse store path " ++ show p ++ ":\n" ++ show err
+  Right path -> return path
+
+writeDerivation :: (Framed e m, MonadStore m) => Derivation -> m Store.StorePath
+writeDerivation (drv@Derivation {inputs, name}) = do
+  let (inputSrcs, inputDrvs) = inputs
+  references <- Set.fromList <$> (mapM parsePath $ Set.toList $ inputSrcs `Set.union` (Set.fromList $ Map.keys inputDrvs))
+  path <- addTextToStore (Text.append name ".drv") (unparseDrv drv) (S.fromList $ Set.toList references) False
+  parsePath $ Text.pack $ unStorePath 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, MS.HashMap Text Text) m) => Derivation -> m (Store.Digest 'Store.SHA256)
+hashDerivationModulo (Derivation {
+    mFixed = Just (Store.SomeDigest (digest :: Store.Digest hashType)),
+    outputs,
+    hashMode
+  }) = case Map.toList outputs of
+    [("out", path)] -> return $ Store.hash @'Store.SHA256 $ Text.encodeUtf8
+      $  "fixed:out"
+      <> (if hashMode == Recursive then ":r" else "")
+      <> ":" <> (Store.algoName @hashType)
+      <> ":" <> (Store.encodeInBase Store.Base16 digest)
+      <> ":" <> path
+    outputsList -> throwError $ ErrorCall $ "This is weird. A fixed output drv should only have one output named 'out'. Got " ++ show outputsList
+hashDerivationModulo drv@(Derivation {inputs = (inputSrcs, inputDrvs)}) = do
+  cache <- gets snd
+  inputsModulo <- Map.fromList <$> forM (Map.toList inputDrvs) (\(path, outs) ->
+    case MS.lookup path cache of
+      Just hash -> return (hash, outs)
+      Nothing -> do
+        drv' <- readDerivation $ Text.unpack path
+        hash <- Store.encodeInBase Store.Base16 <$> hashDerivationModulo drv'
+        return (hash, outs)
+    )
+  return $ Store.hash @'Store.SHA256 $ Text.encodeUtf8 $ unparseDrv (drv {inputs = (inputSrcs, inputsModulo)})
+
+unparseDrv :: Derivation -> Text
+unparseDrv (Derivation {..}) = Text.append "Derive" $ parens
+    [ -- outputs: [("out", "/nix/store/.....-out", "", ""), ...]
+      list $ flip map (Map.toList outputs) (\(outputName, outputPath) ->
+        let prefix = if hashMode == Recursive then "r:" else "" in
+        case mFixed of
+          Nothing -> parens [s outputName, s outputPath, s "", s ""]
+          Just (Store.SomeDigest (digest :: Store.Digest hashType)) ->
+            parens [s outputName, s outputPath, s $ prefix <> Store.algoName @hashType, s $ Store.encodeInBase Store.Base16 digest]
+        )
+    , -- inputDrvs
+      list $ flip map (Map.toList $ snd inputs) (\(path, outs) ->
+        parens [s path, list $ map s $ sort outs])
+    , -- inputSrcs
+      list (map s $ Set.toList $ fst inputs)
+    , s platform
+    , s builder
+    , -- run script args
+      list $ map s args
+    , -- env (key value pairs)
+      list $ flip map (Map.toList env) (\(k, v) ->
+        parens [s k, s v])
+    ]
+  where
+    parens :: [Text] -> Text
+    parens ts = Text.concat ["(", Text.intercalate "," ts, ")"]
+    list   :: [Text] -> Text
+    list   ls = Text.concat ["[", Text.intercalate "," ls, "]"]
+    s = (Text.cons '\"') . (flip Text.snoc '\"') . Text.concatMap escape
+    escape :: Char -> Text
+    escape '\\' = "\\\\"
+    escape '\"' = "\\\""
+    escape '\n' = "\\n"
+    escape '\r' = "\\r"
+    escape '\t' = "\\t"
+    escape c = Text.singleton c
+
+readDerivation :: (Framed e m, MonadFile m) => FilePath -> m Derivation
+readDerivation path = do
+  content <- Text.decodeUtf8 <$> readFile path
+  case parse derivationParser path content of
+    Left err -> throwError $ ErrorCall $ "Failed to parse " ++ show path ++ ":\n" ++ show err
+    Right drv -> return drv
+
+derivationParser :: Parsec () Text Derivation
+derivationParser = do
+  _ <- "Derive("
+  fullOutputs <- list $
+    fmap (\[n, p, ht, h] -> (n, p, ht, h)) $ parens s
+  _ <- ","
+  inputDrvs   <- fmap Map.fromList $ list $
+    fmap (,) ("(" *> s <* ",") <*> (list s <* ")")
+  _ <- ","
+  inputSrcs   <- fmap Set.fromList $ list s
+  _ <- ","
+  platform    <- s
+  _ <- ","
+  builder     <- s
+  _ <- ","
+  args        <- list s
+  _ <- ","
+  env         <- fmap Map.fromList $ list $ fmap (\[a, b] -> (a, b)) $ parens s
+  _ <- ")"
+  eof
+
+  let outputs = Map.fromList $ map (\(a, b, _, _) -> (a, b)) fullOutputs
+  let (mFixed, hashMode) = parseFixed fullOutputs
+  let name = "" -- FIXME (extract from file path ?)
+  let useJson = ["__json"] == Map.keys env
+
+  return $ Derivation {inputs = (inputSrcs, inputDrvs), ..}
+ where
+  s :: Parsec () Text Text
+  s = fmap Text.pack $ string "\"" *> manyTill (escaped <|> regular) (string "\"")
+  escaped = char '\\' *>
+    (   '\n' <$ string "n"
+    <|> '\r' <$ string "r"
+    <|> '\t' <$ string "t"
+    <|> anySingle
+    )
+  regular = noneOf ['\\', '"']
+
+  parens :: Parsec () Text a -> Parsec () Text [a]
+  parens p = (string "(") *> sepBy p (string ",") <* (string ")")
+  list   p = (string "[") *> sepBy p (string ",") <* (string "]")
+
+  parseFixed :: [(Text, Text, Text, Text)] -> (Maybe Store.SomeNamedDigest, HashMode)
+  parseFixed fullOutputs = case fullOutputs of
+    [("out", _path, rht, hash)] | rht /= "" && hash /= "" ->
+      let (hashType, hashMode) = case Text.splitOn ":" rht of
+            ["r", ht] -> (ht, Recursive)
+            [ht] ->      (ht, Flat)
+            _ -> error $ "Unsupported hash type for output of fixed-output derivation in .drv file: " ++ show fullOutputs
+      in case Store.mkNamedDigest hashType hash of
+        Right digest -> (Just digest, hashMode)
+        Left err -> error $ "Unsupported hash " ++ show (hashType <> ":" <> hash) ++ "in .drv file: " ++ err
+    _ -> (Nothing, Flat)
+
+
+defaultDerivationStrict :: forall e t f m b. (MonadNix e t f m, MonadState (b, MS.HashMap Text Text) m) => NValue t f m -> m (NValue t f m)
+defaultDerivationStrict = fromValue @(AttrSet (NValue t f m)) >=> \s -> do
+    (drv, ctx) <- runWithStringContextT' $ buildDerivationWithContext s
+    drvName <- makeStorePathName $ name drv
+    let inputs = toStorePaths ctx
+
+    -- Compute the output paths, and add them to the environment if needed.
+    -- Also add the inputs, just computed from the strings contexts.
+    drv' <- case mFixed drv of
+      Just (Store.SomeDigest digest) -> do
+        let out = pathToText $ Store.makeFixedOutputPath "/nix/store" (hashMode drv == Recursive) digest drvName
+        let env' = if useJson drv then env drv else Map.insert "out" out (env drv)
+        return $ drv { inputs, env = env', outputs = Map.singleton "out" out }
+
+      Nothing -> do
+        hash <- hashDerivationModulo $ drv
+          { inputs
+        --, outputs = Map.map (const "") (outputs drv)  -- not needed, this is already the case
+          , env = if useJson drv then env drv
+                  else foldl' (\m k -> Map.insert k "" m) (env drv) (Map.keys $ outputs drv)
+          }
+        outputs' <- sequence $ Map.mapWithKey (\o _ -> makeOutputPath o hash drvName) (outputs drv)
+        return $ drv
+          { inputs
+          , outputs = outputs'
+          , env = if useJson drv then env drv else Map.union outputs' (env drv)
+          }
+
+    drvPath <- pathToText <$> writeDerivation drv'
+
+    -- Memoize here, as it may be our last chance in case of readonly stores.
+    drvHash <- Store.encodeInBase Store.Base16 <$> hashDerivationModulo drv'
+    modify (\(a, b) -> (a, MS.insert drvPath drvHash b))
+
+    let outputsWithContext = Map.mapWithKey (\out path -> principledMakeNixStringWithSingletonContext path (StringContext drvPath (DerivationOutput out))) (outputs drv')
+        drvPathWithContext = principledMakeNixStringWithSingletonContext drvPath (StringContext drvPath AllOutputs)
+        attrSet = M.map nvStr $ M.fromList $ ("drvPath", drvPathWithContext): Map.toList outputsWithContext
+    -- TODO: Add location information for all the entries.
+    --              here --v
+    return $ nvSet attrSet M.empty
+
+  where
+
+    pathToText = Text.decodeUtf8 . Store.storePathToRawFilePath
+
+    makeOutputPath o h n = do
+      name <- makeStorePathName (Store.unStorePathName n <> if o == "out" then "" else "-" <> o)
+      return $ pathToText $ Store.makeStorePath "/nix/store" ("output:" <> Text.encodeUtf8 o) h name
+
+    toStorePaths ctx = foldl (flip addToInputs) (Set.empty, Map.empty) ctx
+    addToInputs (StringContext path kind) = case kind of
+      DirectPath -> first (Set.insert path)
+      DerivationOutput o -> second (Map.insertWith (++) path [o])
+      AllOutputs ->
+        -- TODO: recursive lookup. See prim_derivationStrict
+        -- XXX: When is this really used ?
+        error "Not implemented: derivations depending on a .drv file are not yet supported."
+
+
+-- | 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 drvAttrs = do
+    -- Parse name first, so we can add an informative frame
+    drvName     <- getAttr   "name"                      $ extractNixString >=> assertDrvStoreName
+    withFrame' Info (ErrorCall $ "While evaluating derivation " ++ show drvName) $ do
+
+      useJson     <- getAttrOr "__structuredAttrs" False   $ return
+      ignoreNulls <- getAttrOr "__ignoreNulls"     False   $ return
+
+      args        <- getAttrOr "args"              []      $ mapM (fromValue' >=> extractNixString)
+      builder     <- getAttr   "builder"                   $ extractNixString
+      platform    <- getAttr   "system"                    $ extractNoCtx >=> assertNonNull
+      mHash       <- getAttrOr "outputHash"        Nothing $ extractNoCtx >=> (return . Just)
+      hashMode    <- getAttrOr "outputHashMode"    Flat    $ extractNoCtx >=> parseHashMode
+      outputs     <- getAttrOr "outputs"           ["out"] $ mapM (fromValue' >=> extractNoCtx)
+
+      mFixedOutput <- case mHash of
+        Nothing -> return Nothing
+        Just hash -> do
+          when (outputs /= ["out"]) $ lift $ throwError $ ErrorCall $ "Multiple outputs are not supported for fixed-output derivations"
+          hashType <- getAttr "outputHashAlgo" $ extractNoCtx
+          digest <- lift $ either (throwError . ErrorCall) return $ Store.mkNamedDigest hashType hash
+          return $ Just digest
+
+      -- filter out null values if needed.
+      attrs <- if not ignoreNulls
+        then return drvAttrs
+        else M.mapMaybe id <$> forM drvAttrs (demand' ?? (\case
+            NVConstant NNull -> return Nothing
+            value -> return $ Just value
+          ))
+
+      env <- if useJson
+        then do
+          jsonString :: NixString <- lift $ nvalueToJSONNixString $ flip nvSet M.empty $
+            deleteKeys [ "args", "__ignoreNulls", "__structuredAttrs" ] attrs
+          rawString :: Text <- extractNixString jsonString
+          return $ Map.singleton "__json" rawString
+        else
+          mapM (lift . coerceToString callFunc CopyToStore CoerceAny >=> extractNixString) $
+            Map.fromList $ M.toList $ deleteKeys [ "args", "__ignoreNulls" ] attrs
+
+      return $ defaultDerivation { platform, builder, args, env,  hashMode, useJson
+        , name = drvName
+        , outputs = Map.fromList $ map (\o -> (o, "")) outputs
+        , mFixed = mFixedOutput
+        }
+  where
+    -- common functions, lifted to WithStringContextT
+
+    demand' :: NValue t f m -> (NValue t f m -> WithStringContextT m a) -> WithStringContextT m a
+    demand' v f = join $ lift $ demand v (return . f)
+
+    fromValue' :: (FromValue a m (NValue' t f m (NValue t f m)), MonadNix e t f m) => NValue t f m -> WithStringContextT m a
+    fromValue' = lift . fromValue
+
+    withFrame' :: (Framed e m, Exception s) => NixLevel -> s -> WithStringContextT m a -> WithStringContextT m a
+    withFrame' level f = join . lift . withFrame level f . return
+
+    -- shortcuts to get the (forced) value of an AttrSet 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
+    getAttrOr' n d f = case M.lookup n drvAttrs of
+      Nothing -> lift d
+      Just v  -> withFrame' Info (ErrorCall $ "While evaluating attribute '" ++ show n ++ "'") $
+                   fromValue' v >>= f
+
+    getAttrOr n d f = getAttrOr' n (return d) f
+
+    getAttr n = getAttrOr' n (throwError $ ErrorCall $ "Required attribute '" ++ show n ++ "' not found.")
+
+    -- Test validity for fields
+
+    assertDrvStoreName :: MonadNix e t f m => Text -> WithStringContextT m Text
+    assertDrvStoreName name = lift $ do
+      let invalid c = not $ isAscii c && (isAlphaNum c || c `elem` ("+-._?=" :: String)) -- isAlphaNum allows non-ascii chars.
+      let failWith reason = throwError $ ErrorCall $ "Store name " ++ show name ++ " " ++ reason
+      when ("." `Text.isPrefixOf` name)    $ failWith "cannot start with a period"
+      when (Text.length name > 211)        $ failWith "must be no longer than 211 characters"
+      when (Text.any invalid name)         $ failWith "contains some invalid character"
+      when (".drv" `Text.isSuffixOf` name) $ failWith "is not allowed to end in '.drv'"
+      return name
+
+    extractNoCtx :: MonadNix e t f m => NixString -> WithStringContextT m Text
+    extractNoCtx ns = case principledGetStringNoContext ns of
+      Nothing -> lift $ throwError $ ErrorCall $ "The string " ++ show ns ++ " is not allowed to have a context."
+      Just v -> return v
+
+    assertNonNull :: MonadNix e t f m => Text -> WithStringContextT m Text
+    assertNonNull t = do
+      when (Text.null t) $ lift $ throwError $ ErrorCall "Value must not be empty"
+      return t
+
+    parseHashMode :: MonadNix e t f m => Text -> WithStringContextT m HashMode
+    parseHashMode = \case
+      "flat" ->      return Flat
+      "recursive" -> return Recursive
+      other -> lift $ throwError $ ErrorCall $ "Hash mode " ++ show other ++ " is not valid. It must be either 'flat' or 'recursive'"
+
+    -- Other helpers
+
+    deleteKeys :: [Text] -> AttrSet a -> AttrSet a
+    deleteKeys keys attrSet = foldl' (flip M.delete) attrSet keys
+
diff --git a/src/Nix/Exec.hs b/src/Nix/Exec.hs
--- a/src/Nix/Exec.hs
+++ b/src/Nix/Exec.hs
@@ -301,7 +301,7 @@
       f arg
     NVBuiltin name f -> do
       span <- currentPos
-      withFrame Info (Calling @m @t name span) (f arg)
+      withFrame Info (Calling @m @(NValue t f m) name span) (f arg)
     s@(NVSet m _) | Just f <- M.lookup "__functor" m -> do
       demand f $ (`callFunc` s) >=> (`callFunc` arg)
     x -> throwError $ ErrorCall $ "Attempt to call non-function: " ++ show x
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
@@ -8,7 +8,6 @@
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE FunctionalDependencies #-}
-{-# LANGUAGE GADTs #-}
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RankNTypes #-}
@@ -16,6 +15,7 @@
 {-# LANGUAGE TemplateHaskell #-}
 {-# LANGUAGE TypeApplications #-}
 {-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE InstanceSigs #-}
 
 {-# OPTIONS_GHC -Wno-orphans #-}
 {-# OPTIONS_GHC -Wno-missing-signatures #-}
@@ -27,8 +27,8 @@
 module Nix.Expr.Types where
 
 #ifdef MIN_VERSION_serialise
+import qualified Codec.Serialise                ( Serialise(decode, encode) )  -- For instance implementation function disamburgation
 import           Codec.Serialise                ( Serialise )
-import qualified Codec.Serialise               as Ser
 #endif
 import           Control.Applicative
 import           Control.DeepSeq
@@ -348,12 +348,12 @@
 instance Serialise r => Serialise (NKeyName r)
 
 instance Serialise Pos where
-  encode x = Ser.encode (unPos x)
-  decode = mkPos <$> Ser.decode
+  encode x = Codec.Serialise.encode (unPos x)
+  decode = mkPos <$> Codec.Serialise.decode
 
 instance Serialise SourcePos where
-  encode (SourcePos f l c) = Ser.encode f <> Ser.encode l <> Ser.encode c
-  decode = SourcePos <$> Ser.decode <*> Ser.decode <*> Ser.decode
+  encode (SourcePos f l c) = Codec.Serialise.encode f <> Codec.Serialise.encode l <> Codec.Serialise.encode c
+  decode = SourcePos <$> Codec.Serialise.decode <*> Codec.Serialise.decode <*> Codec.Serialise.decode
 #endif
 
 instance Hashable Pos where
diff --git a/src/Nix/Frames.hs b/src/Nix/Frames.hs
--- a/src/Nix/Frames.hs
+++ b/src/Nix/Frames.hs
@@ -4,6 +4,7 @@
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 
+-- | 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
   ( NixLevel(..)
   , Frames
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
@@ -22,9 +22,7 @@
 
 instance (MonadFail m, MonadFile m) => MonadFile (StdIdT m)
 instance MonadIntrospect m => MonadIntrospect (StdIdT m)
-instance MonadStore m => MonadStore (StdIdT m) where
-  addPath' = lift . addPath'
-  toFile_' = (lift .) . toFile_'
+instance MonadStore m => MonadStore (StdIdT m)
 instance MonadPutStr m => MonadPutStr (StdIdT m)
 instance MonadHttp m => MonadHttp (StdIdT m)
 instance MonadEnv m => MonadEnv (StdIdT m)
diff --git a/src/Nix/Normal.hs b/src/Nix/Normal.hs
--- a/src/Nix/Normal.hs
+++ b/src/Nix/Normal.hs
@@ -9,6 +9,9 @@
 {-# LANGUAGE TypeApplications #-}
 {-# LANGUAGE TypeFamilies #-}
 
+-- | 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           Control.Monad
@@ -117,7 +120,7 @@
 removeEffects =
   iterNValueM
     id
-    (flip queryM (pure opaque))
+    (`queryM` pure opaque)
     (fmap Free . sequenceNValue' id)
 
 opaque :: Applicative f => NValue t f m
diff --git a/src/Nix/Options.hs b/src/Nix/Options.hs
--- a/src/Nix/Options.hs
+++ b/src/Nix/Options.hs
@@ -1,3 +1,4 @@
+-- | Definitions & defaults for the CLI options
 module Nix.Options where
 
 import           Data.Text                      ( Text )
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,5 +1,6 @@
 {-# LANGUAGE TemplateHaskell #-}
 
+-- | Code that configures presentation parser for the CLI options
 module Nix.Options.Parser where
 
 import           Control.Arrow                  ( second )
diff --git a/src/Nix/Parser.hs b/src/Nix/Parser.hs
--- a/src/Nix/Parser.hs
+++ b/src/Nix/Parser.hs
@@ -9,6 +9,7 @@
 {-# OPTIONS_GHC -fno-warn-name-shadowing #-}
 {-# OPTIONS_GHC -Wno-missing-signatures #-}
 
+-- | Main module for parsing Nix expressions.
 module Nix.Parser
   ( parseNixFile
   , parseNixFileLoc
@@ -90,7 +91,7 @@
 (<+>) :: MonadPlus m => m a -> m a -> m a
 (<+>) = mplus
 
---------------------------------------------------------------------------------
+---------------------------------------------------------------------------------
 
 nixExpr :: Parser NExprLoc
 nixExpr = makeExprParser nixTerm $ map (map snd) (nixOperators nixSelector)
diff --git a/src/Nix/Reduce.hs b/src/Nix/Reduce.hs
--- a/src/Nix/Reduce.hs
+++ b/src/Nix/Reduce.hs
@@ -42,6 +42,7 @@
 import           Data.Fix                       ( Fix(..), foldFix, foldFixM )
 import           Data.HashMap.Lazy              ( HashMap )
 import qualified Data.HashMap.Lazy             as M
+import qualified Data.HashMap.Strict           as MS
 import           Data.IORef
 import           Data.List.NonEmpty             ( NonEmpty(..) )
 import qualified Data.List.NonEmpty            as NE
@@ -66,11 +67,11 @@
 
 newtype Reducer m a = Reducer
     { runReducer :: ReaderT (Maybe FilePath, Scopes (Reducer m) NExprLoc)
-                           (StateT (HashMap FilePath NExprLoc) m) a }
+                           (StateT (HashMap FilePath NExprLoc, MS.HashMap Text Text) m) a }
     deriving (Functor, Applicative, Alternative, Monad, MonadPlus,
               MonadFix, MonadIO, MonadFail,
               MonadReader (Maybe FilePath, Scopes (Reducer m) NExprLoc),
-              MonadState (HashMap FilePath NExprLoc))
+              MonadState (HashMap FilePath NExprLoc, MS.HashMap Text Text))
 
 staticImport
   :: forall m
@@ -78,7 +79,7 @@
      , Scoped NExprLoc m
      , MonadFail m
      , MonadReader (Maybe FilePath, Scopes m NExprLoc) m
-     , MonadState (HashMap FilePath NExprLoc) m
+     , MonadState (HashMap FilePath NExprLoc, HashMap Text Text) m
      )
   => SrcSpan
   -> FilePath
@@ -89,7 +90,7 @@
   path' <- liftIO $ pathToDefaultNixFile =<< canonicalizePath
     (maybe path (\p -> takeDirectory p </> path) mfile)
 
-  imports <- get
+  imports <- gets fst
   case M.lookup path' imports of
     Just expr -> pure expr
     Nothing   -> go path'
@@ -108,10 +109,10 @@
                           (Fix (NLiteralPath_ pann path))
                           pos
           x' = Fix (NLet_ span [cur] x)
-        modify (M.insert path x')
+        modify (\(a, b) -> (M.insert path x' a, b))
         local (const (Just path, emptyScopes @m @NExprLoc)) $ do
           x'' <- foldFix reduce x'
-          modify (M.insert path x'')
+          modify (\(a, b) -> (M.insert path x'' a, b))
           return x''
 
 -- gatherNames :: NExprLoc -> HashSet VarName
@@ -122,7 +123,7 @@
 reduceExpr
   :: (MonadIO m, MonadFail m) => Maybe FilePath -> NExprLoc -> m NExprLoc
 reduceExpr mpath expr =
-  (`evalStateT` M.empty)
+  (`evalStateT` (M.empty, MS.empty))
     . (`runReaderT` (mpath, emptyScopes))
     . runReducer
     $ foldFix reduce expr
@@ -133,7 +134,7 @@
      , Scoped NExprLoc m
      , MonadFail m
      , MonadReader (Maybe FilePath, Scopes m NExprLoc) m
-     , MonadState (HashMap FilePath NExprLoc) m
+     , MonadState (HashMap FilePath NExprLoc, MS.HashMap Text Text) m
      )
   => NExprLocF (m NExprLoc)
   -> m NExprLoc
diff --git a/src/Nix/Render.hs b/src/Nix/Render.hs
--- a/src/Nix/Render.hs
+++ b/src/Nix/Render.hs
@@ -128,5 +128,5 @@
             | otherwise         = "    " ++ n
       ls' = zipWith (<+>)
                     (map (pretty . pad) nums')
-                    (map ((<+>) "| ") ls)
+                    (map ("| " <+>) ls)
     pure $ vsep $ ls' ++ [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
@@ -10,6 +10,8 @@
 {-# LANGUAGE TypeApplications #-}
 {-# LANGUAGE TypeFamilies #-}
 
+
+-- | Code for rendering/representation of the messages packaged with their context (Frames).
 module Nix.Render.Frame where
 
 import           Control.Monad.Reader
@@ -152,8 +154,7 @@
           | otherwise = prettyNix (Fix (Fix (NSym "<?>") <$ x))
   pure $ if verbose opts >= Chatty
     then
-      vsep
-        $ [pretty (longLabel ++ ":\n>>>>>>>>"), indent 2 rendered, "<<<<<<<<"]
+      vsep [pretty (longLabel ++ ":\n>>>>>>>>"), indent 2 rendered, "<<<<<<<<"]
     else pretty shortLabel <> fillSep [": ", rendered]
 
 renderValueFrame
diff --git a/src/Nix/Standard.hs b/src/Nix/Standard.hs
--- a/src/Nix/Standard.hs
+++ b/src/Nix/Standard.hs
@@ -30,6 +30,8 @@
 import           Control.Monad.Ref
 import           Control.Monad.State
 import           Data.HashMap.Lazy              ( HashMap )
+import qualified Data.HashMap.Strict
+import           Data.Text                      ( Text )
 import           Data.Typeable
 import           GHC.Generics
 import           Nix.Cited
@@ -37,6 +39,7 @@
 import           Nix.Context
 import           Nix.Effects
 import           Nix.Effects.Basic
+import           Nix.Effects.Derivation
 import           Nix.Expr.Types.Annotated
 import           Nix.Fresh
 import           Nix.Fresh.Basic
@@ -82,10 +85,10 @@
 instance (MonadFix1T t m, MonadFail (Fix1T t m), MonadFile m) => MonadFile (Fix1T t m)
 
 instance (MonadFix1T t m, MonadStore m) => MonadStore (Fix1T t m) where
-  addPath' = lift . addPath'
-  toFile_' n = lift . toFile_' n
+  addToStore a b c d = lift $ addToStore a b c d
+  addTextToStore' a b c d = lift $ addTextToStore' a b c d
 
-{------------------------------------------------------------------------}
+---------------------------------------------------------------------------------
 
 newtype StdCited m a = StdCited
   { _stdCited :: Cited (StdThunk m) (StdCited m) m a }
@@ -138,7 +141,7 @@
          , Typeable m
          , Scoped (StdValue m) m
          , MonadReader (Context m (StdValue m)) m
-         , MonadState (HashMap FilePath NExprLoc) m
+         , MonadState (HashMap FilePath NExprLoc, Data.HashMap.Strict.HashMap Text Text) m
          , MonadDataErrorContext (StdThunk m) (StdCited m) m
          , MonadThunk (StdThunk m) m (StdValue m)
          , MonadValue (StdValue m) m
@@ -191,7 +194,7 @@
 
 newtype StandardTF r m a
   = StandardTF (ReaderT (Context r (StdValue r))
-                        (StateT (HashMap FilePath NExprLoc) m) a)
+                        (StateT (HashMap FilePath NExprLoc, HashMap Text Text) m) a)
   deriving
     ( Functor
     , Applicative
@@ -205,7 +208,7 @@
     , MonadThrow
     , MonadMask
     , MonadReader (Context r (StdValue r))
-    , MonadState (HashMap FilePath NExprLoc)
+    , MonadState (HashMap FilePath NExprLoc, HashMap Text Text)
     )
 
 instance MonadTrans (StandardTF r) where
@@ -219,7 +222,7 @@
 instance (MonadExec r, MonadExec m) => MonadExec (StandardTF r m)
 instance (MonadIntrospect r, MonadIntrospect m) => MonadIntrospect (StandardTF r m)
 
-{------------------------------------------------------------------------}
+---------------------------------------------------------------------------------
 
 type StandardT m = Fix1T StandardTF m
 
@@ -232,7 +235,7 @@
 mkStandardT
   :: ReaderT
        (Context (StandardT m) (StdValue (StandardT m)))
-       (StateT (HashMap FilePath NExprLoc) m)
+       (StateT (HashMap FilePath NExprLoc, Data.HashMap.Strict.HashMap Text Text) m)
        a
   -> StandardT m a
 mkStandardT = Fix1T . StandardTF
@@ -241,7 +244,7 @@
   :: StandardT m a
   -> ReaderT
        (Context (StandardT m) (StdValue (StandardT m)))
-       (StateT (HashMap FilePath NExprLoc) m)
+       (StateT (HashMap FilePath NExprLoc, Data.HashMap.Strict.HashMap Text Text) m)
        a
 runStandardT (Fix1T (StandardTF m)) = m
 
diff --git a/src/Nix/String.hs b/src/Nix/String.hs
--- a/src/Nix/String.hs
+++ b/src/Nix/String.hs
@@ -32,7 +32,9 @@
   , addStringContext
   , addSingletonStringContext
   , runWithStringContextT
+  , runWithStringContextT'
   , runWithStringContext
+  , runWithStringContext'
   )
 where
 
@@ -130,6 +132,8 @@
 principledStringMappend (NixString s1 t1) (NixString s2 t2) =
   NixString (s1 <> s2) (t1 <> t2)
 
+--  2021-01-02: NOTE: This function is ERRADICATED from the source code.
+-- ERRADICATE it from the API.
 -- | Combine two NixStrings using mappend
 hackyStringMappend :: NixString -> NixString -> NixString
 hackyStringMappend (NixString s1 t1) (NixString s2 t2) =
@@ -144,9 +148,11 @@
   contents = Text.intercalate (nsContents sep) (map nsContents nss)
   ctx      = S.unions (nsContext sep : map nsContext nss)
 
+--  2021-01-02: NOTE: This function is ERRADICATED from the source code.
+-- ERRADICATE it from the API.
 -- | Combine NixStrings using mconcat
 hackyStringMConcat :: [NixString] -> NixString
-hackyStringMConcat = foldr hackyStringMappend (NixString mempty mempty)
+hackyStringMConcat = foldr principledStringMappend (NixString mempty mempty)
 
 -- | Empty string with empty context.
 principledStringMempty :: NixString
@@ -205,7 +211,7 @@
 
 -- | Create a NixString from a Text and context
 principledMakeNixString :: Text -> S.HashSet StringContext -> NixString
-principledMakeNixString s c = NixString s c
+principledMakeNixString = NixString
 
 -- | A monad for accumulating string context while producing a result string.
 newtype WithStringContextT m a = WithStringContextT (WriterT (S.HashSet StringContext) m a)
@@ -231,6 +237,16 @@
 runWithStringContextT (WithStringContextT m) =
   uncurry NixString <$> runWriterT m
 
+-- | Run an action that manipulates nix strings, and collect the contexts encountered.
+-- Warning: this may be unsafe, depending on how you handle the resulting context list.
+runWithStringContextT' :: Monad m => WithStringContextT m a -> m (a, S.HashSet StringContext)
+runWithStringContextT' (WithStringContextT m) = runWriterT m
+
 -- | Run an action producing a string with a context and put those into a 'NixString'.
 runWithStringContext :: WithStringContextT Identity Text -> NixString
 runWithStringContext = runIdentity . runWithStringContextT
+
+-- | Run an action that manipulates nix strings, and collect the contexts encountered.
+-- Warning: this may be unsafe, depending on how you handle the resulting context list.
+runWithStringContext' :: WithStringContextT Identity a -> (a, S.HashSet StringContext)
+runWithStringContext' = runIdentity . runWithStringContextT'
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
@@ -21,9 +21,9 @@
 import           Data.Foldable           hiding ( toList )
 import qualified Data.Map                      as Map
 
--------------------------------------------------------------------------------
--- Typing Environment
--------------------------------------------------------------------------------
+---------------------------------------------------------------------------------
+-- * Typing Environment
+---------------------------------------------------------------------------------
 
 newtype Env = TypeEnv { types :: Map.Map Name [Scheme] }
   deriving (Eq, Show)
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
@@ -69,9 +69,9 @@
 import           Nix.Value.Monad
 import           Nix.Var
 
--------------------------------------------------------------------------------
--- Classes
--------------------------------------------------------------------------------
+---------------------------------------------------------------------------------
+-- * Classes
+---------------------------------------------------------------------------------
 
 -- | Inference monad
 newtype InferT s m a = InferT
@@ -210,9 +210,9 @@
   mempty  = TypeInferenceAborted
   mappend = (<>)
 
--------------------------------------------------------------------------------
--- Inference
--------------------------------------------------------------------------------
+---------------------------------------------------------------------------------
+-- * Inference
+---------------------------------------------------------------------------------
 
 -- | Run the inference monad
 runInfer' :: MonadInfer m => InferT s m a -> m (Either InferError a)
@@ -591,9 +591,9 @@
     Just x  -> TVar x
     Nothing -> error "type variable not in signature"
 
--------------------------------------------------------------------------------
--- Constraint Solver
--------------------------------------------------------------------------------
+---------------------------------------------------------------------------------
+-- * Constraint Solver
+---------------------------------------------------------------------------------
 
 newtype Solver m a = Solver (LogicT (StateT [TypeError] m) a)
     deriving (Functor, Applicative, Alternative, Monad, MonadPlus,
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
@@ -2,7 +2,9 @@
 
 import qualified Data.HashMap.Lazy             as M
 import           Data.Text                      ( Text )
-import           Nix.Utils
+import           Nix.Utils                      ( AttrSet )
+
+-- | Hindrey-Milner type interface
 
 newtype TVar = TV String
   deriving (Show, Eq, Ord)
diff --git a/src/Nix/Value.hs b/src/Nix/Value.hs
--- a/src/Nix/Value.hs
+++ b/src/Nix/Value.hs
@@ -1,25 +1,16 @@
-{-# LANGUAGE AllowAmbiguousTypes #-}
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE DataKinds #-}
 {-# LANGUAGE DeriveAnyClass #-}
-{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE ConstraintKinds #-}
 {-# LANGUAGE DeriveGeneric #-}
 {-# LANGUAGE DeriveTraversable #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE FunctionalDependencies #-}
-{-# LANGUAGE GADTs #-}
 {-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE PatternSynonyms #-}
 {-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE StandaloneDeriving #-}
 {-# LANGUAGE TemplateHaskell #-}
-{-# LANGUAGE TypeApplications #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE TypeOperators #-}
-{-# LANGUAGE UndecidableInstances #-}
 {-# LANGUAGE ViewPatterns #-}
 
 {-# OPTIONS_GHC -Wno-missing-signatures #-}
@@ -242,12 +233,11 @@
   -> r
 iterNValue' k f = f . fmap (\a -> k a (iterNValue' k f))
 
--- | An 'NValueNF' is a fully evaluated value in normal form. An 'NValue f t m' is
+-- | A 'NValue t f m' is
 --   a value in head normal form, where only the "top layer" has been
---   evaluated. An action of type 'm (NValue f t m)' is a pending evaluation that
+--   evaluated. An action of type 'm (NValue t f m)' is a pending evaluation that
 --   has yet to be performed. An 't' is either a pending evaluation, or
---   a value in head normal form. A 'NThunkSet' is a set of mappings from keys
---   to thunks.
+--   a value in head normal form.
 --
 --   The 'Free' structure is used here to represent the possibility that
 --   cycles may appear during normalization.
@@ -307,64 +297,63 @@
 pattern NVConstant x <- Free (NVConstant' x)
 
 nvConstant' :: Applicative f => NAtom -> NValue' t f m r
-nvConstant' x = NValue (pure (NVConstantF x))
+nvConstant' = NValue . pure . NVConstantF
 nvConstant :: Applicative f => NAtom -> NValue t f m
-nvConstant x = Free (NValue (pure (NVConstantF x)))
+nvConstant = Free . nvConstant'
 
 pattern NVStr' ns <- NValue (extract -> NVStrF ns)
 pattern NVStr ns <- Free (NVStr' ns)
 
 nvStr' :: Applicative f => NixString -> NValue' t f m r
-nvStr' ns = NValue (pure (NVStrF ns))
+nvStr' = NValue . pure . NVStrF
 nvStr :: Applicative f => NixString -> NValue t f m
-nvStr ns = Free (NValue (pure (NVStrF ns)))
+nvStr = Free . nvStr'
 
 pattern NVPath' x <- NValue (extract -> NVPathF x)
 pattern NVPath x <- Free (NVPath' x)
 
 nvPath' :: Applicative f => FilePath -> NValue' t f m r
-nvPath' x = NValue (pure (NVPathF x))
+nvPath' = NValue . pure . NVPathF
 nvPath :: Applicative f => FilePath -> NValue t f m
-nvPath x = Free (NValue (pure (NVPathF x)))
+nvPath = Free . nvPath'
 
 pattern NVList' l <- NValue (extract -> NVListF l)
 pattern NVList l <- Free (NVList' l)
 
 nvList' :: Applicative f => [r] -> NValue' t f m r
-nvList' l = NValue (pure (NVListF l))
+nvList' = NValue . pure . NVListF
 nvList :: Applicative f => [NValue t f m] -> NValue t f m
-nvList l = Free (NValue (pure (NVListF l)))
+nvList = Free . nvList'
 
 pattern NVSet' s x <- NValue (extract -> NVSetF s x)
 pattern NVSet s x <- Free (NVSet' s x)
 
 nvSet' :: Applicative f
        => HashMap Text r -> HashMap Text SourcePos -> NValue' t f m r
-nvSet' s x = NValue (pure (NVSetF s x))
+nvSet' s x = NValue $ pure $ NVSetF s x
 nvSet :: Applicative f
       => HashMap Text (NValue t f m) -> HashMap Text SourcePos -> NValue t f m
-nvSet s x = Free (NValue (pure (NVSetF s x)))
+nvSet s x = Free $ nvSet' s x
 
 pattern NVClosure' x f <- NValue (extract -> NVClosureF x f)
 pattern NVClosure x f <- Free (NVClosure' x f)
 
 nvClosure' :: (Applicative f, Functor m)
            => Params () -> (NValue t f m -> m r) -> NValue' t f m r
-nvClosure' x f = NValue (pure (NVClosureF x f))
+nvClosure' x f = NValue $ pure $ NVClosureF x f
 nvClosure :: (Applicative f, Functor m)
           => Params () -> (NValue t f m -> m (NValue t f m)) -> NValue t f m
-nvClosure x f = Free (NValue (pure (NVClosureF x f)))
+nvClosure x f = Free $ nvClosure' x f
 
 pattern NVBuiltin' name f <- NValue (extract -> NVBuiltinF name f)
 pattern NVBuiltin name f <- Free (NVBuiltin' name f)
 
 nvBuiltin' :: (Applicative f, Functor m)
            => String -> (NValue t f m -> m r) -> NValue' t f m r
-nvBuiltin' name f = NValue (pure (NVBuiltinF name f))
+nvBuiltin' name f = NValue $ pure $ NVBuiltinF name f
 nvBuiltin :: (Applicative f, Functor m)
           => String -> (NValue t f m -> m (NValue t f m)) -> NValue t f m
-nvBuiltin name f =
-  Free (NValue (pure (NVBuiltinF name f)))
+nvBuiltin name f = Free $ nvBuiltin' name f
 
 builtin
   :: forall m f t
diff --git a/tests/EvalTests.hs b/tests/EvalTests.hs
--- a/tests/EvalTests.hs
+++ b/tests/EvalTests.hs
@@ -429,7 +429,7 @@
 case_concat_thunk_rigth =
   constantEqualText "[1 2 3]" "[1] ++ builtins.tail [1 2 3]"
 
------------------------
+---------------------------------------------------------------------------------
 
 tests :: TestTree
 tests = $testGroupGenerator
diff --git a/tests/Main.hs b/tests/Main.hs
--- a/tests/Main.hs
+++ b/tests/Main.hs
@@ -95,7 +95,7 @@
   prettyTestsEnv      <- lookupEnv "PRETTY_TESTS"
 
   pwd <- getCurrentDirectory
-  setEnv "NIX_REMOTE" ("local?root=" ++ pwd ++ "/")
+  setEnv "NIX_REMOTE" (pwd ++ "/real-store")
   setEnv "NIX_DATA_DIR" (pwd ++ "/data")
 
   defaultMain $ testGroup "hnix" $
diff --git a/tests/ParserTests.hs b/tests/ParserTests.hs
--- a/tests/ParserTests.hs
+++ b/tests/ParserTests.hs
@@ -361,7 +361,7 @@
 tests :: TestTree
 tests = $testGroupGenerator
 
---------------------------------------------------------------------------------
+---------------------------------------------------------------------------------
 
 assertParseText :: Text -> NExpr -> Assertion
 assertParseText str expected = case parseNixText str of
diff --git a/tests/PrettyTests.hs b/tests/PrettyTests.hs
--- a/tests/PrettyTests.hs
+++ b/tests/PrettyTests.hs
@@ -32,7 +32,7 @@
 tests :: TestTree
 tests = $testGroupGenerator
 
---------------------------------------------------------------------------------
+---------------------------------------------------------------------------------
 assertPretty :: NExpr -> String -> Assertion
 assertPretty e s =
   assertEqual ("When pretty-printing " ++ show e) s . show $ prettyNix e
