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
| ^^^^^^^^^^^^^^^^^^^^^
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)
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).
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:
v2-
can be omitted in Cabal 3 because it's the default;
The order of flags is not important as long as install
goes right
after cabal
;
--lib
is important, because by default you get only executables (that's
what happens with criterion
: the package holds an executable);
--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=.
.
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.
cabal v2-install --lib
means if no environment specifiedIt 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.
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.
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.
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.