How I switched from Stack to Cabal

Zelenya - May 6 - - Dev Community

šŸ“¹Ā Hate reading articles? Check out the complementary video, which covers the same content.


Have you ever struggled to choose between stack and cabal for your Haskell projects?

I havenā€™t, because all the years ago I started with stack and never dared to switch to cabal.

šŸ’”Ā Technically, I did use it in the enterprise setting, but letā€™s say itā€™s irrelevant.

Stack has been my personal go-to build tool, basically for 3 reasons:

  • managing GHC versions;
  • managing dependency versions;
  • managing modules.

From what Iā€™ve heard in the last couple of years, those 3 do not justify not using cabal. So, I wanted to give it a try.

šŸ¤”Ā Note that at the moment, I donā€™t worry about any optimizations and disk usage ā€” I just want to make my life simpler and try out things.

Managing GHC versions

One nice thing about stack is that it by default manages ghc versions.

But also ghcup does that and I already use ghcup to manage stack itself, the haskell language server (hls), and even the compatible ghc version. Recently Iā€™ve been hooking up stack to make it use ghcup-installed ghc versions. So, it feels simpler to just use ghcup.

ghcup

Yeah, itā€™s also nice that out of the box stack uses the correct ghc version when switching between projects ā€” something that cabal doesnā€™t do. And itā€™s fine. Well, at least in my case ā€” because I rarely actively use more than 1 or 2 GHC versions and donā€™t have to worry about it that often.

Regarding reproducible builds, you can still use theĀ with-compilerĀ option in theĀ cabal.projectĀ file to pin down the ghc version.



with-compiler: ghc-9.4.5


Enter fullscreen mode Exit fullscreen mode

šŸ’”Ā Note: You can also pin the base version.

Managing dependency versions

Another thing that stack manages is dependency versions. Instead of relying on dependency resolutions or picking versions by hand, you can use stackage snapshots.

These days, you can also use stackage snapshots with cabal ā€” by using import in cabal.project:



packages: .
import: https://www.stackage.org/lts-21.7/cabal.config


Enter fullscreen mode Exit fullscreen mode

šŸ’”Ā The cabal.project file comes in place of theĀ stack.yamlĀ file (for project configuration and options).

You can stop there, but you can also try a different workflow. Ask yourself what you want. Do you want reproducible builds? Do you want to make sure that libraries work together? Something else?

You can use cabal freeze to pin down the dependencies, which ensures (more) reproducible builds.

šŸ’”Ā It results in a cabal.project.freeze file **that we need to commit (or share).

Iā€™ve used freeze- and lock-like workflows in a couple of previous jobs, and most of the time, it is pretty convenient, except for the days when I was the lucky one and had to deal with occasional problematic dependency bump.

If you donā€™t care about reproducibility, you donā€™t have to freeze anything. In either case, modern cabal is capable of constructing proper build plans. And if you worry about getting the ā€œrightā€ dependency versions, remember that stackage still exists and assists you even if you donā€™t use it directly ā€” maintainers still want to make it into the snapshots and make sure that their packages work with others.

šŸ’”Ā As a side note: the in-between step or solution can be directly freezing a stackage snapshot:



curl https://www.stackage.org/lts-21.7/cabal.config > cabal.project.freeze


Managing modules

Okay, the actual reason Iā€™ve been avoiding cabal is this: I hate enumerating the modules in cabal files by hand. And I didnā€™t mind using an extra tool just for that.

Then the other day I had an epiphany: hpack takes care of that boilerplate ā€” not stack!

So, I can cut out the middle man ā€” one middle man, but keep the other oneā€¦

Running hpack generates the .cabal file based on the package.yaml. You can run it manually, but itā€™s easy to forget, which makes it a good candidate for automation.

There are other alternatives for dealing with this and other boilerplate, but itā€™s good enough for me. If youā€™re less lazy and want to cut out hpack as well: you can generate the cabal file, patch up the version bounds, and then delete the hpack file.

Takeaways

To reiterate, if you want to switch from stack to cabal, you can generate the .cabal file from hpackā€™s package.yaml (and then either keep it or delete it) and swap stack.yaml for cabal.project.

šŸ’”Ā Depending on your prev. workflow, you might need to remove the .cabal file from gitignore.

So, will I choose stack pr cabal for my next Haskell projects? I donā€™t know.


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player