diff --git a/content/posts/cabal-test-dev-trick.md b/content/posts/cabal-test-dev-trick.md
new file mode 100644
index 0000000..0a4a3d1
--- /dev/null
+++ b/content/posts/cabal-test-dev-trick.md
@@ -0,0 +1,293 @@
++++
+date = "2022-10-03"
+draft = false
+path = "/blog/cabal-test-dev-trick"
+tags = ["haskell"]
+title = "The cabal 'test-dev' trick"
++++
+
+I found something mysterious while working on a large Haskell codebase: there
+was a Cabal target called `test:test-dev`, containing `test` and `src` in
+`source-dirs`. For a while, I didn't realise what it's for, and it seemed like
+the kind of thing that someone goes and creates for some old use case. Now I
+have put this into at least three other codebases because it fixes my problems.
+What changed?
+
+I learned about some limitations of cabal, GHC, and GHCi:
+
+* Cabal will only build modules in one package (and target!) at a time.
+ In practice this means that your test suite can only start building after
+ your entire library is built. This is silly!
+
+ To illustrate the problem, say you have some module deep in your module tree,
+ `Instrumentation` that depends on `hs-opentelemetry`, and you have
+ `hs-opentelemetry` in your workspace. Then if you change something in
+ `hs-opentelemetry`, Cabal will build that package in its entirety before
+ starting on building yours, even though most of your package doesn't need any
+ of `hs-opentelemetry`.
+
+ Cross-package parallelism has motivated some [intrepid hackers at
+ Tweag][haskell-bazel] to make Bazel work well for Haskell builds. This work
+ allows you to build things in parallel, ignoring package boundaries for
+ modules that don't require the entire dependency package to be ready yet.
+ However, it also loses the fine grained recompilation avoidance that's always
+ improving inside `ghc-make`. It turns out, we can hack cabal to do nearly the
+ same thing.
+
+* GHCi doesn't know how to have interpreted modules from multiple packages.
+
+ This is consequential for a couple of reasons:
+ * No `:load` support if you want to work on dependencies and grab everything
+ in scope including private items
+ * The debugger doesn't work across packages! This one really hurts in the
+ occasions when you absolutely need the debugger. While figuring out a bug
+ in my HSpec integration for OpenTelemetry, I needed to point the GHCi
+ debugger at HSpec, and this was the (only) way.
+ * `:reload` does not work across packages. This alone is enough to recommend
+ this trick with abandon.
+
+[haskell-bazel]: https://www.tweag.io/blog/2022-06-23-haskell-module/
+
+What if you could Not Have Those Problems? It turns out, they are all solved by
+Putting More Code in a Target, although sometimes this needs to be done with
+a little more persuasion of the build system.
+
+If you think you have too much code in one package, you are probably mistaken.
+Most of GHC, representing a Lot of Haskell, is one package. Avoiding the
+limitations of the build infrastructure around multiple packages improves build
+times and developer productivity.
+
+## How do I `test-dev`?
+
+Include both `src` and `test` in `hs-source-dirs` (`source-dirs` in hpack), and
+then use `cabal run test:test-dev` (yes, `run`; cabal's shiny new test
+runner doesn't let you control the entry point, which is no fun), or
+`cabal repl test:test-dev`.
+
+You will need to put all your dependencies for `src/` *and* your dependencies
+for `test/` in the dependencies for `test-dev`.
+
+## But what if a dependency is causing cross-target sadness?
+
+In the spirit of [the yolo method of setting up HLS][yolo-method] in which you
+unceremoniously temporarily stuff some third party project into a cabal
+workspace with your code, you can also just lie to cabal that the dependency is
+part of your package.
+
+[yolo-method]: ../nix-hls-for-deps#yolo-method
+
+Simply tell cabal "it's *my* package now" by putting its source directory into
+`hs-source-dirs` and remove the dependency (since it's your package now).
+Cabal will apparently not think much of it!
+
+Let's use hspec as an example. First clone hspec below your project directory
+(nested git repos are no problem as long as they remain untracked):
+
+```
+$ git clone https://github.com/hspec/hspec
+```
+
+Then do something like this in your cabal file:
+
+```
+test-suite test-dev
+ -- ...
+ hs-source-dirs:
+ src
+ test
+ hspec/hspec
+ hspec/hspec-core
+ build-depends:
+ -- your app depends PLUS (your test depends MINUS hspec/hspec-core) PLUS hspec/hspec-core's depends
+```
+
+Finally you can `cabal run test:test-dev`, or `cabal repl test:test-dev`, and
+it will build your code *and* the hspec code all as one imaginary package,
+allowing you to use the full GHCi feature set and compile faster, especially if
+you're actively working on hspec.
+
+In this way, you can blur the line between dependencies and your own code,
+working on them as one, while also keeping them separate the rest of the time.
+
+### Bonus section: It also makes HLS work better
+
+You can also put `test-dev` in place of the other targets in `hie.yaml` in all
+the paths it includes, which will improve HLS's overall performance and
+usability, although perhaps at the cost of more startup time. This is because
+HLS has the same problem as cabal where it seemingly doesn't build dependent
+targets in parallel with their dependencies.
+
+More frustratingly, if a dependency fails to compile, it will sometimes take
+down all the dependent code with it, even if the dependent code doesn't all
+actually import the dependency.
+
+You can set this up in `hie.yaml` something like so:
+
+```yaml
+cradle:
+ cabal:
+ - path: "src"
+ component: "test:test-dev"
+
+ - path: "test"
+ component: "test:test-dev"
+```
+
+---
+
+## Are split packages a good idea in some contexts?
+
+Here's a [blog post by Matt Parsons on compile times][parsons-compile-times],
+which I generally agree with based on my experience working in large Haskell
+codebases.
+
+[parsons-compile-times]: https://www.parsonsmatt.org/2019/11/27/keeping_compilation_fast.html#module-parallelism
+
+Some highlights include:
+
+Given a package `B`, depending on `A`:
+
+> By combining `A` and `B` into a single package, we sped up compile
+ times for a *complete* build of the application by 10%. A clean build of the
+ new `AB` package was 15% faster to build all told, and incremental builds were
+ improved too.
+
+He suggests "if you're not willing to GitHub it, then it should probably stay
+in the main package".
+
+I agree with this heuristic: you can cache the entire thing as a package (in
+fact, Nix will automatically do it for you) if it's on GitHub/Hackage, and it
+probably doesn't change that often if you are willing to do that, so the slight
+increase in annoyingness of developing it is probably fine.
+
+That said, you can have your cake and eat it too! It's easily possible to [stuff
+dependencies into your workspace while working on them][yolo-method], or even
+artificially integrate them into your own package while working on them via the
+`test-dev` trick.
+
+Using these methods, you can get exactly the same developer experience while working
+on libraries as if they are fully part of the codebase by telling cabal that
+they *are* fully part of the codebase only while developing on them, then have
+them be separate while releasing.
+
+---
+
+## The elephant in the room: "Wait, GHC fixed this?"
+
+My post describes a workaround for the absence of a GHC feature called
+"multiple home units", which released in GHC 9.4, and is supposed to solve
+these problems. However, [Cabal does not yet support it][cabal-mhu], which is
+rather a roadblock. Also, [GHCi has limited functionality under multiple home
+units][ghci-mhu].
+
+You can read more about the ongoing work on multiple home units [at the
+Well-Typed blog post on the subject][wt-multiple-home-units].
+
+[wt-multiple-home-units]: https://well-typed.com/blog/2022/01/multiple-home-units/
+[cabal-mhu]: https://github.com/haskell/cabal/issues/8238
+[ghci-mhu]: https://gitlab.haskell.org/ghc/ghc/-/issues/20889
+
+That post points out that Stack does the `test-dev` hack described in this
+article if you do something like `stack repl exe:myexample lib:myexample`, but
+I don't use Stack.
+
+The `test-dev` trick will continue working into the future, but I look forward
+to the day that it is no longer necessary.
+
+### Limitations
+
+This hack has some unfortunate limitations, many of which are discussed in the
+posts about multiple home units above:
+
+- It doesn't reuse object code from the build of `lib:sample` or `sample:test:test`.
+- You need to set `default-extensions` to the union of all of the
+ `default-extensions` that each of the directories are normally built with. I
+ didn't actually notice this since I typically use a copious number.
+- Other GHC flags need to be compatible.
+- It breaks [-XPackageImports].
+- Module name conflicts probably don't do anything good.
+- I doubt that [cabal `mixins`][cabal-mixins] work with this.
+
+[cabal-mixins]: https://cabal.readthedocs.io/en/3.4/cabal-package.html#pkg-field-mixins
+
+[-XPackageImports]: https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/package_qualified_imports.html?highlight=packageimports#extension-PackageImports
+
+## Bonus section: `ghcid` on tests
+
+You know what fixing GHCi on tests in the presence of app modifications does?
+That's right, you can run your test suite with ghcid on every file save. This
+is seriously awesome since it uses interpreted mode for those sweet GHCi reload
+times.
+
+This section was inspired by [Matt Parsons' blog post on ghcid][parsons-ghcid],
+which incidentally uses this trick because Stack does it under the hood!
+
+[parsons-ghcid]: https://www.parsonsmatt.org/2018/05/19/ghcid_for_the_win.html
+
+
+
+Back to our regularly scheduled bonus content:
+
+First, create a file `test/Main.hs` something like so (this circumvents the
+`cabal test` runner which doesn't let you have a custom entry point):
+
+```haskell
+module Main where
+import Spec (spec)
+import Hspec
+
+main :: IO ()
+main = testMain
+
+-- avoids overlapping names with Spec.main
+testMain :: IO ()
+testMain = hspec spec
+```
+
+Then you can ghcid your tests with:
+
+```
+$ ghcid --command 'cabal repl test:test-dev --ghc-options="-osuf dyn_o -hisuf dyn_hi"' --test testMain
+```
+
+When you save any file, ghcid will pilot the GHCi session to reload and rerun
+the tests.
+
+
+
+#### Acknowledgements
+
+Thanks to Hazel Weakly, Matt Parsons, and Chris Zehner for their valuable
+feedback and discussion on drafts of this post.
diff --git a/content/posts/nix-hls-for-deps.md b/content/posts/nix-hls-for-deps.md
index 9cf1b2e..d52b52b 100644
--- a/content/posts/nix-hls-for-deps.md
+++ b/content/posts/nix-hls-for-deps.md
@@ -12,7 +12,7 @@ functionality cross-project. Another problem I sometimes have is that I want to
make a contribution to some library but setting up an IDE for it is a pain in
the neck.
-# YOLO method (putting it all in a workspace with your app and not telling Nix)
+# YOLO method (putting it all in a workspace with your app and not telling Nix) {#yolo-method}
Often it is easiest to partially bypass Nix while doing development on
dependencies. This means that Nix provides the compiler but the dependency
diff --git a/sass/main.scss b/sass/main.scss
index 2c3c996..725e08b 100644
--- a/sass/main.scss
+++ b/sass/main.scss
@@ -38,12 +38,16 @@ $lower2: darken($text, 30);
}
}
-pre {
+@mixin box {
padding: 1em;
margin: .5em 0;
border-radius: .3em;
- overflow: auto;
background-color: $lowerbg1;
+}
+
+pre {
+ @include box;
+ overflow: auto;
code {
font-family: Inconsolata, Monaco, Consolas, 'Courier New', Courier, monospace;
@@ -287,6 +291,11 @@ kbd {
box-shadow: 0px 2px 3px $lowerbg2;
}
+/* asides. god help me */
+aside {
+ @include box;
+}
+
/* headings */
h1 {