Using hoogle in a haskell development environment on nix

Here's what my Nix Haskell dev environment looks like

in ~/.nixpkgs/config.nix:

The environment helper function

First off, define a haskellEnvFun function for building Haskell environments:

packageOverrides = super: rec {

haskellEnvFun = { withHoogle ? false, compiler ? null, name }:
  let hp = if compiler != null
             then super.haskell.packages.${compiler}
             else haskellPackages;

      ghcWith = if withHoogle
                  then hp.ghcWithHoogle
                  else hp.ghcWithPackages;

  in super.buildEnv {
    name = name;
    paths = [(ghcWith myHaskellPackages)];
  };

Defining some environments

Call this function to define two environments: one for running the Hoogle builder on changes, and one without:

haskellEnvHoogle = haskellEnvFun {
  name = "haskellEnvHoogle";
  withHoogle = true;
};

haskellEnv = haskellEnvFun {
  name = "haskellEnv";
  withHoogle = false;
};

Packages

Define all the packages you want to use in your local Haskell dev environment:

myHaskellPackages = hp: with hp; [
  Boolean
  HTTP
  HUnit
  MissingH
  QuickCheck
  SafeSemaphore
  Spock
  aeson
  async
  attoparsec
  bifunctors
  blaze-builder
  blaze-builder-conduit
  blaze-builder-enumerator
  blaze-html
  blaze-markup
  blaze-textual
  cased
  cassava
  cereal
  comonad
  comonad-transformers
  directory_1_2_4_0
  dlist
  dlist-instances
  doctest
  exceptions
  fingertree
  foldl
  free
  hamlet
  hashable
  hspec
  hspec-expectations
  html
  http-client
  http-date
  http-types
  io-memoize
  keys
  language-c
  language-javascript
  language-bash
  lens
  lens-action
  lens-aeson
  lens-datetime
  lens-family
  lens-family-core
  lifted-async
  lifted-base
  linear
  list-extras
  list-t
  logict
  mime-mail
  mime-types
  mmorph
  monad-control
  monad-coroutine
  monad-loops
  monad-par
  monad-par-extras
  monad-stm
  monadloc
  mongoDB
  monoid-extras
  network
  newtype
  numbers
  optparse-applicative
  parsec
  parsers
  pcg-random
  persistent
  persistent-mongoDB
  persistent-template
  pipes
  pipes-async
  pipes-attoparsec
  pipes-binary
  pipes-bytestring
  pipes-concurrency
  pipes-csv
  pipes-extras
  pipes-group
  pipes-http
  pipes-mongodb
  pipes-network
  pipes-parse
  pipes-safe
  pipes-shell
  pipes-text
  posix-paths
  postgresql-simple
  pretty-show
  profunctors
  random
  reducers
  reflection
  regex-applicative
  regex-base
  regex-compat
  regex-posix
  regular
  relational-record
  resourcet
  retry
  rex
  safe
  sbv
  scotty
  semigroupoids
  semigroups
  shake
  shakespeare
  shelly
  simple-reflect
  speculation
  split
  spoon
  stm
  stm-chans
  stm-stats
  streaming
  streaming-bytestring
  streaming-wai
  strict
  stringsearch
  strptime
  syb
  system-fileio
  system-filepath
  tagged
  taggy
  taggy-lens
  tar
  tardis
  tasty
  tasty-hspec
  tasty-hunit
  tasty-quickcheck
  tasty-smallcheck
  temporary
  test-framework
  test-framework-hunit
  text
  text-format
  time
  tinytemplate
  transformers
  transformers-base
  turtle
  uniplate
  unix-compat
  unordered-containers
  uuid
  vector
  void
  wai
  wai-conduit
  warp
  wreq
  xhtml
  yaml
  zippers
  zlib
];

Shell helpers

In your ~/.profile define a couple bash functions to load those environments for convenience:

env-type () {
  envtype="$1"
  shift
  nix-shell -Q -p $envtype "$@"
}

haskell-env () {
  env-type "haskellEnv" "$@"
}

haskell-env-hoogle () {
  env-type "haskellEnvHoogle" "$@"
}

Hoogle

Call haskell-env-hoogle in your shell. This will build all of your packages + docs and load you into an environment with hoogle in scope. At this point I usually type:

hoogle server --local -p 8080 &> /tmp/hoogle.log & disown

to launch a hoogle server the the background. Eventually I want to have a systemd service for this so that I can just nixos-rebuild to regen docs and launch the server automatically.

Emacs

For emacs I've set the haskell-hoogle-url to http://localhost:8080/?hoogle=%s, so that I can get local hoogle docs for keywords under my cursor. I use spacemacs so I just type , h h for this functionality.

You can see my full nixpkgs config here: https://github.com/jb55/nix-files/blob/659798f2ca81fb7ad0cb5a29de576024ee16eef8/nixpkgs/config.nix#L20

Hope that helps.


haskellPackages.hoogleLocal appears to be out of date; it doesn't exist anymore.

William Casarin's answer appears to be assuming you will have a single "haskell development environment" you use, rather than using nix-shell to have different dev environments for different projects.

What I've just figured out how to do instead is to write my shell.nix to override ghc.withPackages and ghcWithPackages to be ghc.withHoogle, so that when nix-shell creates an environment with a GHC that knows about all the necessary packages it also creates a hoogle database that knows about the same packages.

Here's my shell.nix1:

{ nixpkgs ? import <nixpkgs> {}, compiler ? "default", withHoogle ? true }:

let

  inherit (nixpkgs) pkgs;

  f = import ./default.nix;

  packageSet = (
    if compiler == "default"
      then  pkgs.haskellPackages
      else  pkgs.haskell.packages.${compiler}
  );

  haskellPackages = (
    if withHoogle
      then  packageSet.override {
              overrides = (self: super:
                {
                  ghc = super.ghc // { withPackages = super.ghc.withHoogle; };
                  ghcWithPackages = self.ghc.withPackages;
                }
              );
            }
      else  packageSet
  );

  drv = haskellPackages.callPackage f {};

in

  if pkgs.lib.inNixShell then drv.env else drv

I'm newish to nix, but I believe this should be pretty much "project independent"; I can use cabal2nix . > default.nix to generate a nix package from my cabal file when I change it, without having to touch shell.nix.

I haven't actually used this in real development yet, only a dummy project I was using to try to figure out how to get hoogle working in nix-shell.


1The skeleton of this was what cabal2nix --shell spits out, with the project-specific guts removed and replaced with f = import ./default.nix instead of embedding the nixified cabal package again.


Using @Ben's answer as reference, here is a diff on the required changes I needed to make to a cabal2nix --shell file:

diff --git a/shell.nix b/shell.nix
index 540ade3..e207d6e 100644
--- a/shell.nix
+++ b/shell.nix
@@ -1,4 +1,4 @@
-{ nixpkgs ? import <nixpkgs> {}, compiler ? "default", doBenchmark ? false }:
+{ nixpkgs ? import <nixpkgs> {}, compiler ? "default", doBenchmark ? false , withHoogle ? true}:

 let

@@ -21,10 +21,23 @@ let
         license = stdenv.lib.licenses.bsd3;
       };

-  haskellPackages = if compiler == "default"
+  haskellPackages' = if compiler == "default"
                        then pkgs.haskellPackages
                        else pkgs.haskell.packages.${compiler};

+  haskellPackages = (
+    if withHoogle
+    then  haskellPackages'.override {
+      overrides = (self: super:
+        {
+          ghc = super.ghc // { withPackages = super.ghc.withHoogle; };
+          ghcWithPackages = self.ghc.withPackages;
+        }
+      );
+    }
+    else haskellPackages'
+  );
+
   variant = if doBenchmark then pkgs.haskell.lib.doBenchmark else pkgs.lib.id;

   drv = variant (haskellPackages.callPackage f {});```