Convenience in Haskell: Ergonomics of Cabal

Sorry for bringing up any conflict with this post. I did read through that github issue you mentioned but I suppose I didn’t understand the conversation enough to realize an acceptable solution had already been proposed. That there was already a satisfying solution on a proposed design of a cabal exact-printer also eluded me during my readings.

As far as I had read, it seemed that cabal exact-printer was pretty complicated and still pretty far out of reach to be implemented. That’s why I had the idea of just trying to wrap around cabal/hpack to make something that was more easily achievable now. But I’ll have to go back and re-read the issues to see where it landed.

Thanks for clarifying!

Sorry for bringing up any conflict with this post

No need to apologize – there’s a lot of lore buried in the cabal issue tracker, but especially on complicated tickets with long discussions among many people it can be quite hard to parse out the current state of play, especially when different cabal developers may disagree, and there’s no central “voice of authority”.

4 Likes

Yet again, another feature blocked on Cabal exact-printing abilities…

1 Like

hpack worked fine for years. Proves that building tools on top of cabal works fine instead of trying to force the core design to fit your aesthetic can lead to success.

Follow hpack’s lead. I’ve noticed that cabal has a lot of drive-by opinions but not drive-by, mergeable diffs :laughing:

1 Like

To be honest, it sounds like you’re describing stack :smiley:

2 Likes

What killer feature exactly did cabal reimplement? Nix-style builds were proposed and discussed before stack happened afaik (but not implemented in time).

Cabal still refuses to implement the killer feature “install GHC without asking”. And I’m happy it doesn’t.

Support for stackage? Well, that’s maybe the only thing, but in its current form it’s not very usable and boils down to just “cabal downloads a cabal.config for you”. So that hasn’t been grandiously implemented either.

So I really don’t know what you mean. Maybe backpack support? Oh wait, it’s stack that doesn’t support this.

Kinda confused here.

Sorry, I totally skipped your comment about package.yaml, because that’s not a killer feature, it’s horrendous.

cabal-install and stack are not at different abstraction levels. They never were.

You’re maybe confusing the Setup.hs functionality with cabal-install?

But yes, I agree, cabal-install could also be structured differently. It’s poorly designed. There should be very clear separation between concerns. E.g.

  • coming up with a build plan
  • executing that build plan

This is implemented in some package managers as output → input pipelines. Every phase could have an output that the next tool consumes. Then you could build high abstraction cli around it more easily.

What is this principle called? People will get annoyed at me for bringing this up again, but let me try: it’s called unix principle.

That would also mean stack wouldn’t have to re-implement half of what is already in cabal-install.

And now to the question: which tool is more likely to ever achieve better such separation? It’s probably cabal-install, because stack firmly believes in batteries-include everything. It’s unlikely it will ever be modular. cabal-install is heading into that direction, very very slowly though. And maybe it’s not enough, but we can hope.

5 Likes

There are actually 3-4 different tools in the Haskell ecosystem, i.e, one is Haskell.Nix and related workflows, another is GHCup, another is Cabal, and the last is Stack.

Each of these tools have their own limitations:

  1. GHCup, for instance, does not want to build anything other than core Haskell tooling.
  2. Cabal is a very old tool, and the pace of work (help out!) is slower than many would like.
  3. Nix-based workflows require learning Nix.
  4. Stack will install a new GHC every time you change versions, resulting in very slow initial builds, and has an implicit conflict with GHCup.

End of the day, talk to people involved, preferably privately, on how people should go forward with this.

Not at all. Since ghcup follows strictly unix philosophy, it’s easily integratable with even stack. And we did just that by following yet another unix principle (called hooks… e.g. look at git hooks).

There:

Such unix.

6 Likes

…or for those not acquainted with “the Unix philosophy” :

As far as I had read, it seemed that cabal exact-printer was pretty complicated and still pretty far out of reach to be implemented.

I don’t think it’s very complicated; but there are lot of details and constraints to take into account when reworking the parser; starting from correctness (parse everything on Hackage in the same way) and performance (both time and memory). It’s no cowboy job if you allow me.

I have put some effort in understanding what we have and what is missing. If anybody has good engineering skills to offer, they are welcome to get in touch either on GitHub or with a DM.

2 Likes

RE: filewatch, I appreciate there are no current plans as such, but is it true that maintainers have no desire to add it? The thread remains open, and doesn’t contain any real arguments against, just discussions about scope and implementation difficulties. Personally, it’s something I’d love to see.

I do think you make a good point about Oleg’s comment regarding globs and how module lists are critical for other tooling. Sacrificing human ergonomics for machine ergonomics is a good sign that a phase split is needed, and it sounds like cabal sdist can play that role.

5 Likes

Yeaahhh but stack is kind of weird though! It uses package sets, it downloads ghc and a bunch of stuff :confounded:. It has two separate places where you specify packages:

package.yaml - where you set dependencies
Stack.yaml - where you specify extra-deps for stuff outside the package set

And it still does not provide a nice stack add <dep> or stack remove <dep> or any other convenience commands newcomers want to see when getting into a new language.

I’m talking more about a tool that allows the user to get by largely without having to edit the package config file at all, and that doesn’t do all kinds of extra stuff; something that’s more of a 1 to 1 translation with a cabal file, but that allows the tool to make all the changes to it.

I know the cabal package format provides for a lot of flexibility, but if I didn’t have to go in there to add dependencies, or modify other-modules/exposed-modules, I personally would likely never interface with a cabal file directly.

I’m talking about a tool that automates all those common interactions with the package file so the only thing I ever need to worry about is basically just setting package dependencies.

I know now that all of this functionality can probably be added once exact-printing/parsing is implemented, but I wasn’t sure how far off that was (or previously if exact printing could even remedy all of the concerns, such as globs in exposed/other modules), so I thought, “we just make a tool wraps cabal to get us there more easily, simple!” :sweat_smile:

The exact-printer issue seems to be a stopper but… Can anyone explain to me what is it and why would be needed for many of the things discussed here? From my perspective, Cabal library can already parse, modify and write the cabal-spec AST, isn’t it? why something like using globs needs the exact-printer?

1 Like

Cabal can parse and modify the cabal AST, but writing (print) it is the problem. The AST is abstract and does not contain exact source position information (and perhaps comments are removed too, I’m not sure). So cabal is not able to modify only a part of the file and keep the rest exactly the same.

2 Likes

Here’s the relevant cabal-watch ticket. `cabal watch` command · Issue #5252 · haskell/cabal · GitHub

I agree there’s no strong negatives there, but the response from the author of ghcid, which provides watch-like functionality very well basically said “there’s no purpose to putting this in cabal,” and made a pretty compelling case as to why.

Making ghcid available as an external subcommand would allow people to type cabal watch and get the functionality through that unified cli if they really wanted, but from a code standpoint it doesn’t seem to make sense to add a whole bunch of new logic to the cabal-install codebase when it works perfectly well as an external tool, and there’s not really any duplication of code or functionality as a result…

3 Likes

That assumes enough Haskellers thought the work was an improvement to begin with e.g. as noted earlier:

…Cabal is now something more that just a system for simply packing, unpacking and installing Haskell packages - one example being the build option in the cabal program, in contrast to other package managers like rpm and dpkg (which have separate rpmbuild or dpkg-deb programs respectively for that purpose), or apk (no such feature).

This would seem to be a useful reduction in complexity for Cabal, and would bring it closer to the SRP. But can enough Haskellers (including the Cabal developers) can be convinced of those long-term benefits to accept the short-term costs of transition to the new arrangement? If not, any such work would be for nothing.

rpm, dpkg and apk are not comparable to Cabal, as they are not language-specific. I would argue that better comparisons would be to tools such as cargo, pip and npm — all of which have rather similar feature-sets and aims to Cabal.

3 Likes

…that looks very much like a call for a SRP-driven “separation of concerns”:

  • cargo - probably the closest to Cabal, with only one compiler to deal with, and its own build option. The intended arrival of the GNU Rust compiler in April of next year then poses an interesting question - will cargo be expected to work with both implementations, or will there be an all-GNU replacement?

    That will require some effort in either case, but less effort will be needed if cargo “externalised” its build feature, as per the SRP.

  • pip - While still primarily for use with a single-implementation interpreted language, it recently had to be extended to work with “pre-generated” Python-specific byte-code. Presumably the extra code required didn’t all end up in pip, but also went into new programs - again, guided by the SRP to some extent.

  • npm - a quick search suggest there’s been no build option since version 6.14.18:

    npm-build | npm Docs


Vaguely recalling how often upgrading GHC breaks existing code the Haskell language changes, Cabal being language-specific doesn’t seem all that beneficial:

2 Likes

Worthwhile to recall: Cabal is an acronym for “common architecture for building applications and libraries”. The word build is right there in the name. Cabal-the-library is not a package manager, but it interoperates with one. The one it interoperates with is ghc-pkg and that is provided by GHC itself. Cabal’s interactions with the ghc package database are intermediated through ghc-pkg, and the need to keep the notions of compiled package descriptions in sync is one significant point of coupling between Cabal and GHC.

cabal-install-the-executable with v1- commands was also not a package manager, in precisely the same way, just a build tool wrapping the build architecture of Cabal-the-library. With v2- commands it got more ambitious and became a project build tool, and extended the package-management of ghc-pkg with a more sophisticated notion of a nix-like “store”.

A useful refactor to promote logical modular division could be to try to pull out the store-interaction code from cabal-install into a library, and perhaps even pull it back into ghc-pkg itself.

7 Likes