haskellneovimnixosxmonad

NixOS, Haskell LSP in Neovim, XMonad imports are not found


Here is a screenshot of the problem: enter image description here

OS: NixOS, unstable channel.

Neovim: 0.7.2.

Haskell LSP: haskell-language-server.

Running xmonad --recompile in the terminal works.

Please help :-)

EDIT 1:

As asked by @ArtemPelenitsyn in the comments below, here is my init.lua: https://pastebin.com/70jMHm02. The parts that I think relevant:

require'lspconfig'.hls.setup{}

I think it's something more related to NixOS and not to Neovim.

EDIT 2:

As asked by @Ben in the comments below, here is the needed info:

λ ghci
GHCi, version 9.0.2: https://www.haskell.org/ghc/  :? for help
ghci> import XMonad

<no location info>: error:
    Could not find module ‘XMonad’
    It is not a module in the current program, or in any known package.
ghci> 

Here is everything related to Haskell and XMonad in my configuration.nix:

# snip
services.xserver.windowManager.xmonad.enable = true;
services.xserver.displayManager.defaultSession = "none+xmonad";
services.xserver.windowManager.xmonad.enableConfiguredRecompile = true;
services.xserver.windowManager.xmonad.enableContribAndExtras = true;
# snip
environment.systemPackages = with pkgs; [
  ghc
  haskell-language-server
  haskellPackages.xmobar
  haskellPackages.xmonad
  haskellPackages.xmonad-contrib
];
# snip

Solution

  • In your configuration.nix, you have the following:

    environment.systemPackages = with pkgs; [
      ghc
      haskell-language-server
      haskellPackages.xmobar
      haskellPackages.xmonad
      haskellPackages.xmonad-contrib
    ];
    

    Here you actually haven't installed a GHC that can access the xmobar, xmonad, and xmonad-contrib packages. Installed you've installed a GHC that doesn't know about any packages, and separately installed those Haskell packages. Any executables in those packages will be added to the PATH environment variable (which is how you can actually run xmonad), but PATH isn't know GHC finds installed packages. You need another step to connect the installation of GHC with the packages, so that you (and haskell-language-server) can import them.

    The reason is that the way GHC is expecting to work is that installing packages is a mutating operation on the file system. On a "normal" system you install GHC and it knows about the packages that were bundled with it, then you install another package like xmonad into a folder that GHc will look in for packages1 and now the effect of running that same ghc program has changed.

    Nix doesn't like that. Packages are supposed to be immutable in Nix. You can't change a GHC-without-xmonad into a GHC-with-xmonad after the fact.

    So just installing pkgs.ghc isn't actually what you want. That package is already completely determined by the nix code that evaluates to, and the package it determines is a baseline ghc with no additional packages. Instead you need to create an entirely new package that consists of GHC installed with xmonad.

    Fortunately, this is an extremely common need so there is already a wrapper function to generate this package for you. haskellPackages.ghcWithPackages2. This function takes a single argument, which must be a function you provide. That function will itself be called on a single argument which is the collection of Haskell packages available, and should return a list of which ones you want included in the GHC installation package you're building.

    So that means what you actually want is something like this:

    environment.systemPackages = with pkgs; [
      haskell-language-server
    
      # If you want you can use `with hpkgs; [` to avoid explicitly
      # selecting into the hpkgs set on every line
      (haskellPackages.ghcWithPackages (hpkgs: [  
        hpkgs.xmobar
        hpkgs.xmonad
        hpkgs.xmonad-contrib
      ]))
    ];
    

    Under the hood what ghcWithPackages actually does is install ghc and those packages, just as you did, but then it also creates a very small "wrapper package" around ghc that sets environment variables telling it where to find the specific set of packages you installed. The thing that gets added to PATH to provide commands like ghc, ghci, etc is not the underlying GHC, but the wrapped one.3

    You don't really need to know any of this "under the hood" stuff, just that every time you need GHC to have a specific set of packages you need to create a new nix package with ghcWithPackages. Knowing that it's based on wrapper scripts can help you not stress about space being wasted though; if you have 100 Haskell projects they can all share any GHC versions and Haskell package versions that are common; it's only the tiny wrappers that you have 100 copies of.

    This is also the basic model used by most programming languages that have direct support in nixpkgs (and even some other things that aren't strictly programming languages but can be extended by installing plugins after the fact). It doesn't work precisely the same way for every language, as it depends on what code had to be written around the packaging tools each language has. But the basic conceptual model is frequently something like this.

    This is all documented in the nixpkgs manual; as opposed to the manual for Nix itself, or for NixOS. It has a section on Languages and Frameworks where you can find the documentation for how a number of programming language ecosystems are supported in nixpkgs. Although the Haskell section under that has been turned into a small paragraph telling you to go to a separate site for the nixpkgs Haskell docs.


    One final note: I'm not 100% sure whether haskell-language-server will just automatically pick up the ghc in your PATH and run with those packages, or if you need any further configuration. Since I am a Haskell developer I have a number of projects that each need different sets of available packages (or even GHC version in some cases), so I don't have any GHC (or HLS) installed at the environment.system-packages level; each of my projects has its own shell environment, ultimately generated from the projects .cabal file. This means I've never actually used haskell-language-server on "loose" Haskell files living outside of a project, and I'm not sure whether you need to do anything more to get it to work. But this is definitely what you need to get ghci> import XMonad to work (without dramatically changing how you do things).


    1 And I believe also update some registry files, but I'm not 100% across all of the details. They're not important for this level of explanation.


    2 And if you don't like the version of GHC (and everything else) contained in haskellPackages, all the other Haskell package sets also contain this ghcWithPackages function, such as haskell.packages.ghc924, haskell.packages.ghc8102, etc (the top level haskellPackages is one of these sets; whichever is determined to be a good default in the revision of nixpkgs you happen to be using).


    3 The environment variables I can see in the wrapper script all have NIX_ in the name, so I suspect the base GHC packages in nixpkgs are patched to support this behaviour.