haskellcabalhaskell-criterion

Haskell Criterion: Could not load module, hidden package


When I do exactly what this Criterion tutorial says to do to get started, I get an error. What am I doing wrong? Is the tutorial wrong? If so, is there a place that I can learn the right way to use Criterion?

Specifically, as the tutorial says, I ran the following in command line:

cabal update
cabal install -j --disable-tests criterion

This ran without error. Then I copied exactly the example program in the tutorial:

import Criterion.Main

-- The function we're benchmarking.
fib m | m < 0     = error "negative!"
      | otherwise = go m
  where
    go 0 = 0
    go 1 = 1
    go n = go (n-1) + go (n-2)

-- Our benchmark harness.
main = defaultMain [
  bgroup "fib" [ bench "1"  $ whnf fib 1
               , bench "5"  $ whnf fib 5
               , bench "9"  $ whnf fib 9
               , bench "11" $ whnf fib 11
               ]
  ]

I put that into a file called benchTest.hs, and then I used the command line to compile the program exactly as it says in the tutorial, but with benchTest in place of Fibber, which is what they called it. Specifically, the I ran the following in the command line:

ghc -O --make benchTest

This resulted in this error:

benchTest.hs:1:1: error:
    Could not load module `Criterion.Main'
    It is a member of the hidden package `criterion-1.5.13.0'.
    You can run `:set -package criterion' to expose it.
    (Note: this unloads all the modules in the current scope.)
    It is a member of the hidden package `criterion-1.5.13.0'.
    You can run `:set -package criterion' to expose it.
    (Note: this unloads all the modules in the current scope.)
    Use -v (or `:set -v` in ghci) to see a list of the files searched for.
  |
1 | import Criterion.Main
  | ^^^^^^^^^^^^^^^^^^^^^

Solution

  • Short history of Cabal evolution

    Through its history, Cabal has gone through a big transformation of how it works, which is commonly denoted as v1- commands vs. v2- commands; e.g. since Cabal 2 you can say cabal v1-install or cabal v2-install. What happens when you say just cabal install depends on the Cabal version: Cabal 2 will use v1-install by default while Cabal 3 will use v2-install. The change in defaults reflects the preferred mode of operation. So much so, v1 has become basically unmaintained. I don't expect it to be removed soon because there is a group of hard proponents of the old way. But personally I think that, first, the new way (fun fact you can use cabal new-install as a synonym) is technically superior, and second, that newcomers should just use it because it's better documented and you can have more luck in getting help with it (in many cases, it's easier to help because of the above-mentioned superiority)

    Why v1 was subsumed by v2 (in a nutshell)

    The main trouble you can run in with v1 is incompatible dependencies across several projects. Imagine, you work on project A that depends on package X of version 42, and, at the same time you're starting with project B that also depends on X but of version 43. Guess what: you can't v1-build the two projects on the same machine without wiping out cabal's cache in between. This was the way it worked in dark ages (from the middle of 2000s to yearly 2010s).

    After that, cabal sandboxes arrived. They allowed you to build our imaginary projects A and B with less hussle but the interface was not great and, more importantly, every sandbox was independent and, therefore, held a big chunk of repetitive binaries; e.g. A and B could depend also on Y of the same version 13, so there's theoretically no need to build and store Y twice, but that's exactly what cabal sandboxes would do.

    Cabal v2 arrived in the late 2010s and brought exactly that: isolation between projects via an (also recent) GHC feature called environment files and sharing of build artifacts via Cabal store (so that you don't store many copies of the same thing).

    Environments and v2-install

    You can create a separate environment for every project (A, B, etc.) by doing

    cabal v2-install --lib X-42 --package-env=.
    

    in the directory of the respective project. Couple notes on the syntax:

    1. v2- can be omitted in Cabal 3 because it's the default;

    2. The order of flags is not important as long as install goes right after cabal;

    3. --lib is important, because by default you get only executables (that's what happens with criterion: the package holds an executable);

    4. --package-env=. means: create a GHC environment file in the current directory (hence .). If the command succeeds you will notice a new ("hidden" on Linux) file in the current directory, named something like .ghc.environment.x86_64-linux-9.0.2. This is the file that tells all subsequent calls to GHC in this directory where to search for the libraries compiled by Cabal and stored in… Cabal store (on Linux it's the ~/.cabal/store directory by default). In principle, you can use other than . values for environments, and if the value doesn't correspond to a path, it will be a named environment. More details in Cabal reference manual… In practice, I find 99.99% cases perfectly served by --package-env=..

    5. X-42 means the package X of version 42 should be added to the newly created environment. You can omit the version (you will get "some compatible version"), and you can list more than one package.

    What cabal v2-install --lib means if no environment specified

    It means the default environment. There is a single shared environment called default. It has the same very problem that v1 had (see above). So, in practice it could work but it will be very fragile, especially if you get into the "project A and project B" situation described above. Even if you only work with one project now, I suggest using --package-env because it's future proof.

    Why the initial error

    As you say, you were using Cabal 2 and therefore v1-install initially and saw the dreaded "hidden package" error — what's the reason for this? Honestly, I have no idea. And I doubt it's easy to figure without rolling back to that older Cabal version and experimenting more. As I say above, v1 is not really maintained anymore, and even if it looks like a bug in Cabal (which is perfectly possible especially with earlier releases in the Cabal 2 line), no one will probably bother about it.

    Isn't it sad that old tutorials don't work anymore

    It is. Unfortunately, the software technology has to develop to make the world a better place to live (see the reasons for v2 above again). Sometimes this development has to break backward compatibility. Ideally, we'd go and update all educational materials and manuals to reflect the change but that's hardly possible. Sigh. New users of Haskell has to be careful and creative with respect to the v1 to v2 shift and try to get a basic understanding of v2 early on and try to apply it to the good but old tutorials that are still out there.

    Are environments the best approach?

    Some of the designers and proponents of v2 argue that environment files are a too subtle of a feature. As a ("proper") alternative, they suggest creating a full-fledged cabal package for every project you start. This amounts to calling cabal init, which will create a <project-name>.cabal file in the current directory, and maintaining the .cabal file, including the list of package dependencies there; you will also use cabal v2-build to build the project (instead of directly calling GHC).

    While more robust, unsurprisingly, this idea doesn't sleep well with many people who use Haskell to try a lot of small independent things: it feels lame to create a whole "package" every time. Well, it's just one extra file in practice, and it's not even extra if you compare it to the environments-based approach I described above, which also maintains one extra file, but in that case you don't ever need to edit it by hand (unlike the .cabal file). All in all, in the scenarios of "trying one small thing" I find the environments-based approach working better for me. But it does have its limitations compared to the package-based approach, notably, it's hard to figure out how to get profiling versions of dependencies in the environment. But that's a story for another day…

    You can find more discussion about how cabal v2-install --lib can be improved in Cabal issue.

    If you want to follow the officially blessed way of doing things (i.e. via a package), please, take a minute to read the Getting Started section of the Cabal manual — it's very clear and shows exactly an example of simple application with a dependency on an external package.