Convenience in Haskell: Ergonomics of Cabal

Yea, I agree that it’d be better to see cabal fixed than to see another competing format; standardization is really important. But sometimes it seems to me that we’re often at odds with balancing the technical needs vs the user needs. My thought is that maybe we shouldn’t try to shoehorn both needs into one tool. Maybe we need one set of tools to address the first need (e.g: cabal), and another set of tools to target the 2nd need, and let the user-friendly tool bridge that gap between the two ends of it.

But ultimately, I personally don’t care how we get to the place where the UX is better, I just want to see it happen. I want to help out, but I also think its worth just contributing to discussion before pouring a lot of efforts towards things. I’m just chewing over my own thoughts and asking questions to help clarify things for myself. Thanks for the discussion everyone!

I think there’s a good case for .cabal to be a flat file with nothing fancy. Easy to parse and analyze purely, for instance. I might -1 adding wildcards to :cabal myself for that reason.

Which is why package.yaml is the format you’re looking for :grin:

I do often wish that the cabal team would have just said “Oh! Wonderful! stack does all this cool stuff so we can keep cabal simple!”

But instead, cabal team realized they were losing marketshare hard, and got salty, and decided they need to reimplement the killer features that were drawing folks to stack. So now we have cabal.project to replicate stack.yaml (but with fewer features, worse UX, and an awkward file format), cabal-file includes to sort of do what package.yaml and YAML includes natively do (but without the ability to do cross-file referencing, an important feature for multi-package repositories).

There’s some obvious things that cabal can add to bridge the gap. Support package.yaml natively. It’s easy! Hell, support stack.yaml, too. Add a --file-watch flag. Load multiple targets in GHCi (sure, print a warning if it borks, but at least let me do the thing that works fine in stack and is a huge improvement for working on multi-package projects).

But why is cabal's goal "catching up to stack"? Why can’t we just have two tools at different levels of abstraction?

4 Likes

I shouldn’t get drawn into these discussions which relitigate a very frustrating past, but I’ll just say this is not an accurate historical account of when, how and why cabal.project files were developed.

More generally, cabal’s goal has never been “catching up to stack”. It is certainly the case that if there’s a feature a lot of people like in another tool, then that’s a feature that is sometimes worth considering adding to a tool – but that’s how all tools in a common space interact, always, and presenting it negatively is strange.

In fact, the fact that cabal has no plans or desire to add package.yaml or stack.yaml support, cross-file referencing of cabal files, or a filewatch flag for that matter (all issues which have been discussed), is strong evidence that cabal is not motivated by “catching up to stack”.

So what this comment presents is basically a strawman claim that cabal has a certain goal that it does not, then it complains that cabal isn’t doing a good enough job at this goal – when in fact the evidence should lead one to conclude that cabal isn’t doing those things because that is not the goal.

On multi-package support, btw, was under active development for some time but required changes to ghc to do properly, and is now merged: Add support for loading multiple components into one repl session by mpickering · Pull Request #8726 · haskell/cabal · GitHub

19 Likes

Are there docs about cabal’s design goals and non-goals?

It sounds like the main missing features are specialized for industrial use, which ofc isn’t the only driving faction of Haskell development. I’m sure the cabal contributors are balancing more varied concerns.

So I’d be interested in reading more about these decisions and the tradeoffs at play. I find Haskell design decisions usually have good reasons :grin:

Here’s the original goals back when Cabal was first proposed and introduced in 2005, almost 20 years ago:

The Haskell Package System (Cabal) has the following main goal: to specify a standard way in which a Haskell tool can be packaged, so that it is easy for consumers to use it, or re-package it, regardless of the Haskell implementation or installation platform.

The Cabal also supports tool authors by providing an infrastructure that automates the process of building and packaging simple tools. It is not necessary to use this code—indeed complex libraries may exceed its abilities—but it should handle many cases with no trouble.

see: The Haskell Cabal

The above describes the goals of Cabal the library, and cabal packages.

We don’t have anything quite so pithy describing cabal-install the binary, though its description in its own cabal file perhaps is a start:

The ‘cabal’ command-line program simplifies the process of managing Haskell software by automating the fetching, configuration, compilation and installation of Haskell libraries and programs.

Since that was written, I think it it would be worth adding that it is a tool for automating common related tasks in developing such things – i.e. the test and benchmark facilities, etc.

The main thing these goals clarify is why some things do not make sense for cabal files – they interfere with their ability to be used as modular units of package distribution.

Regarding features of cabal-install the considerations are often a lot more loose, and just involve trying to avoid feature and code-bloat where external tools will suffice. The proposal for an external command system will help with this to some degree, as has the work that has been done in opening up more of cabal-install as a library that other tools can make use of: An external command system for cabal: what would you do with it?

3 Likes

On the specific issue of glob expansion, there is a ticket about it, and a proposal on how to handle it that everyone seems to agree is at least fine, and some think is actively good. It was even in the issue linked in the initial post on this thread!

So this entire discussion is motivated by a false premise – that cabal is hostile to this feature, or must be. Instead, there was a technical architectural concern raised, and a good solution proposed. That solution, like other things we would like, remains blocked on an exact-printer.

So what we see is not any issue with the design goals of various systems or the need for new systems, or anything else, although reaching and maintaining a common understanding is of course important. Everything there is basically fine and good. What we see is somebody needs to write an exact-printer.

And again, there is a ticket for that (linked in the above ticket), which has, within it, a perfectly good design that just needs someone to implement it.

9 Likes

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: