Yesterday I learnt about a new Haskell tool called Stack. At the first blush, it looks like it does much the same job as Cabal. So, what is the difference between them? Is stack a replacement for Cabal? In which cases should I use Stack instead of Cabal? What can Stack do that Cabal can't?
What is the difference between Cabal and Stack?
50.6k views Asked by ZhekaKozlov AtThere are 3 answers
From what I can glean from the FAQ, it seems that Stack uses the Cabal library, but not the cabal.exe
binary (more correctly known as cabal-install). It looks like the aim of the project is automatic sandboxing and avoidance of dependency hell.
In other words, it uses the same Cabal package structure, it just provides a different front-end for managing this stuff. (I think!)
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).
Yes and No.
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 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.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.