What is the difference between Cabal and Stack?
Is stack a replacement for Cabal?
Yes and No.
In which cases should I use Stack instead of Cabal? What can Stack do that Cabal can't?
Stack uses the curated stackage packages by default. That being so, any dependencies are known to build together, avoiding version conflict problems (which, back when they were commonplace in the Haskell experience, used to be known as "cabal hell"). Recent versions of Cabal also have measures in place to prevent conflict. Still, setting up a reproducible build configuration in which you know exactly what will be pulled from the repositories is more straightforward with Stack. Note that there is also provision for using non stackage packages, so you are good to go even if a package isn't present in the stackage snapshot.
Personally, I like Stack and would recommend every Haskell developers to use it. Their development is fast. And it has a much better UX. And there are things which Stack does which Cabal yet doesn't provide:
- Stack even downloads GHC for you and keeps it in an isolated location.
- Docker support (which is very convenient for deploying your Haskell applications)
- Reproducible Haskell script: You can pinpoint version of a package and can get guarantee that it will always execute without any problem. (Cabal also has a script feature, but fully ensuring reproducibility with it is not quite as straightforward.)
- Ability to do
stack build --fast --file-watch
. This will automatically rebuild if you change the local files present. Using it along with--pedantic
option is a deal-breaker for me. - Stack supports creating projects using templates. It also supports your own custom templates.
- Stack has built-in hpack support in it. It provides an alternative (IMO, a better) way of writing cabal files using yaml file which is more widely used in the industry.
- Intero has a smooth experience when working with Stack.
There is a nice blog post explaining the difference: Why is Stack not Cabal? While Cabal has, in the intervening years since that post, evolved so as to overcome some of the issues discussed there, the discussion of the design goals and philosophy behind Stack remains relevant.
In what follows, I will refer to the two tools being compared as cabal-install and stack. In particular, I will use cabal-install to avoid confusion with the Cabal library, which is common infrastructure used by both tools.
Broadly speaking, we can say cabal-install and stack are frontends to Cabal. Both tools make it possible to build Haskell projects whose sets of dependencies might conflict with each other within the confines of a single system. The key difference between them lies in how they address this goal:
By default, cabal-install will, when asked to build a project, look at the dependencies specified in its
.cabal
file and use a dependency solver to figure out a set of packages and package versions that satisfy it. This set is drawn from Hackage as a whole -- all packages and all versions, past and present. Once a feasible build plan is found, the chosen version of the dependencies will be installed and indexed in a database somewhere in~/.cabal
. Version conflicts between dependencies are avoided by indexing the installed packages according to their versions (as well as other relevant configuration options), so that different projects can retrieve the dependency versions they need without stepping on each other's toes. This arrangement is what the cabal-install documentation means by "Nix-style local builds".When asked to build a project, stack will, rather than going to Hackage, look at the
resolver
field ofstack.yaml
. In the default workflow, that field specifies a Stackage snapshot, which is a subset of Hackage packages with fixed versions that are known to be mutually compatible. stack will then attempt to satisfy the dependencies specified in the.cabal
file (or possibly theproject.yaml
file -- different format, same role) using only what is provided by the snapshot. Packages installed from each snapshot are registered in separate databases, which do not interfere with each other.
We might say that the stack approach trades some setup flexibility for straightforwardness when it comes to specifying a build configuration. In particular, if you know that your project uses, say, the LTS 15.3 snapshot, you can go to its Stackage page and know, at a glance, the versions of any dependency stack might pull from Stackage. That said, both tools offer features that go beyond the basic workflows so that, by and large, each can do all that the other does (albeit possibly in a less convenient manner). For instance, there are ways to freeze exact versions of a known good build configuration and to solve dependencies with an old state of Hackage with cabal-install, and it is possible to require non-Stackage dependencies or override snapshot package versions while using stack.
Lastly, another difference between cabal-install and stack which is big enough to be worth mentioning in this overview is that stack aims at providing a complete build environment, with features such as automatic GHC installation management and Docker integration. In contrast, cabal-install is meant to be orthogonal to other parts of the ecosystem, and so it doesn't attempt to provide this sort of feature (in particular, GHC versions have to be installed and managed separately, for instance through the ghcup tool).