Skip to content

Instantly share code, notes, and snippets.

@merijn
Last active June 14, 2024 17:22
Show Gist options
  • Save merijn/8152d561fb8b011f9313c48d876ceb07 to your computer and use it in GitHub Desktop.
Save merijn/8152d561fb8b011f9313c48d876ceb07 to your computer and use it in GitHub Desktop.

The Cabal/Stack Disambiguation Guide

One of the most frequently asked Haskell beginner questions in recent years is:

"Stack or cabal?"

I will helpfully not answer this question. Instead I will hope to eliminate the confusion that many of the askers seem to have about the various different things named "cabal" and how they relate to each other and stack.

So, how many things named "cabal" do we have? We have:

  1. CABAL (the spec)

    CABAL is the Common Architecture for Building Applications & Libraries. It's a specification for defining how Haskell applications and libraries should be built, defining dependencies, etc.

  2. .cabal (the file format)

    The file format used to write down the aforementioned definitions for a specific package.

  3. Cabal (the library)

    The library implementing the above specification and file format.

  4. cabal (the executable)

    The cabal executable, more accurately named cabal-install, is a commandline tool that uses Cabal (the library) to resolve dependencies from Hackage and build packages.

How does Stack relate to all this CABAL stuff?

Stack is a replacement for cabal-install, i.e. the cabal executable. The stack commandline tool, like cabal-install is a tool that uses Cabal (the library) to resolve dependencies and build packages. The main difference between cabal-install and stack is how they resolve dependencies.

So, what do cabal-install and stack do differently?

cabal-install looks at the declared version ranges of a package in the .cabal file and using the available versions on Hackage it computes a build plan satisfying the version constraints, then compiles using this build plan.

stack on the other hand uses "resolvers". A resolver is a snapshot of various package versions on Stackage and dependencies are resolved by "just use the exact version specified by the resolver".

It is possible to make stack resolve things dynamically [1] as cabal-install and vice versa, you can create snapshots (freeze files) using cabal-install to accomplish what stack does.

So which tool should I use?

Honestly, at this point I don't think there is much difference, use whichever tool best fits your workflow. The only real strong opinion I have is that you should avoid Stack's (optional) use of hpack at all costs.

hpack is a tool that generates .cabal files from package.yaml. In the past there were some (in my personal opinion, weak) reasons for using package.yaml, but those are nowadays possible in .cabal too.

package.yaml does not support all CABAL features and requires all your potential users to install extra tooling. The .cabal format is understood by both cabal-install and stack without extra tools, so everyone can just use/contribute with their preferred tools.

[1]As of Stack 2.x this functionality has been dropped from Stack and so this is no longer possible.
@freeman42x
Copy link

@hasufell from what I understand stack allows you to use per project GHC without yout having to PATH to GHC or use something like direnv. Can cabal do that? If not what is the equivalent workflow with?

And does current Cabal solve the problem of all package/version combinations compiling nice with each other? I know stack guarantees that using stackage. I'd rather avoid using stack because it used (still is?) very buggy in the past, but previous feature keeps me using it.

@freeman42x
Copy link

After a quick google search I can't find any comparison of cabal install and stack features.

@hasufell
Copy link

from what I understand stack allows you to use per project GHC without yout having to PATH to GHC or use something like direnv. Can cabal do that?

Easily: cabal build -w ghc-8.10.7. The version of ghc can also be recorded in cabal.project file.

And does current Cabal solve the problem of all package/version combinations compiling nice with each other?

You can use stackage with cabal, there are two methods:

  1. use the upstream config: https://www.stackage.org/lts-18.26/cabal.config
  2. use stack2cabal: https://hackage.haskell.org/package/stack2cabal

@freeman42x
Copy link

Setting GHC version in cabal.project is a nice option. 💯

Using that pretty big cabal.config seems kind of overkill. Any other alternatives besides taking out from it all libs you are not using?

Does stack2cabal give you all the versions for all stackage libs like cabal.config above or just the ones used in the project?

@hasufell
Copy link

Using that pretty big cabal.config seems kind of overkill

I don't understand what that means. Stack does the same.

Does stack2cabal give you all the versions for all stackage libs like cabal.config above or just the ones used in the project?

All of them of course, otherwise your transitive dependencies might differ. It's not just about your direct dependencies.

@freeman42x
Copy link

@hasufell "Using that pretty big cabal.config seems kind of overkill" I'm talking about placing that into your project repository. It's a lot of code and stack does not force you to do that.

Thank you, I was wondering whether that would affect transitive dependencies also.

@philderbeast
Copy link

@razvan-flavius-panda you can import the cabal.config directly from stackage into the cabal project but because constraints are additive with cabal it can be necessary to download it locally so that conflicting constraints can be removed.

@brandonchinn178
Copy link

I arrived to this page via the ghcup docs. The section about hpack seems a bit too opinionated for being a guide linked to from GHCup docs, which are now the official recommended instructions for installing Haskell.

There are at least two things that plain .cabal files cannot do:

  1. Import settings from external files.
    • This is important in a monorepo, when we want to set default-extensions, ghc-options, and metadata fields (like version and author) the same across multiple Haskell projects
    • With hpack, we can use the include! macro to load settings and defaults from a common file. This also allows us to share common stanzas across all our packages
  2. Discover modules automatically
    • haskell/cabal#5343
    • Love it or hate it, this is a valid workflow that some people (including myself) want

Whether you think these are good things or not, these are concrete things that package.yaml supports that .cabal does not, contrary to what this statement is implying:

In the past there were some (in my personal opinion, weak) reasons for using package.yaml, but those are nowadays possible in .cabal too.

I would recommend that GHCup either makes their own less-opinionated document, or that this document is edited to be less opinionated about hpack.

cc @hasufell because you added a link to this gist in GHCup (link)

@hasufell
Copy link

I have no interest in maintaining a page like this. Feel free to fork the gist and make a more accurate comparison.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment