Why We Built a Haskell Package Manager in Rust

1 Like

You knew AI-assisted development would come to Haskell tools, and here it is.
(Cc’ing from Reddit. Maybe we can have a bit more substantive discussion over here, like some testing and evaluation of the tools ?)

If you want to start a Haskell project today, here is what you do. First you install ghcup, which manages GHC (the compiler), Cabal (the build tool), Stack (a different build tool), and HLS (the language server). Then you decide whether to use Cabal or Stack, which is a decision that has split the Haskell community for over a decade and which nobody has fully resolved. Then you configure your project, using either a .cabal file (a custom format that predates TOML, YAML, and JSON as configuration languages) or a stack.yaml plus a .cabal file (because Stack still needs Cabal files underneath). Then you wait for GHC to compile your dependencies, which takes long enough that you start questioning your life choices.

I am not exaggerating for effect. This is the actual experience. I have introduced Haskell to teams and watched the enthusiasm drain from people’s faces during the toolchain setup. Not because the language was hard. Because the first thirty minutes were spent fighting ghcup, cabal update, resolver mismatches, and cryptic build errors that had nothing to do with the code they wanted to write.

Here is what a typical first encounter looks like. You want to write a small HTTP server in Haskell. You install ghcup. You install GHC 9.8.2. You run cabal init. You get a .cabal file with a dozen fields, most of which you do not understand yet. You add warp as a dependency. You run cabal build. GHC starts compiling warp and its transitive dependencies: http-types, bytestring, text, network, streaming-commons, vault, wai, and about forty others. This takes four to six minutes on a modern machine. The first time. Every time you switch GHC versions or clean your cache, you pay that cost again.

Now compare this with Rust. You run cargo new my-server. You add axum to Cargo.toml. You run cargo build. It compiles. The first build is not instant either, but cargo does not ask you which of two incompatible build tools you prefer, does not require a separate tool to manage the compiler, and does not present you with a configuration format from 2005.

I’m not sure this is a fair comparison. Why does ghcup get dinged for HLS but rust-analyzer gets skipped in the rust setup? Rust doesn’t have transitive dependencies?

I think I spent just a few minutes getting tooling set up using ghcup the first time I tried. What exactly is enervating their enthusiasm during the tooling setup? I’m missing the advantage over cabal or guix/nix

1 Like

I read the article, but I do not understand what they are trying to achieve.

It seems like a thin wrapper around various tools, so that I only ever have to interface with one cli app.

There’s some confusing optimization claims, e.g. they circumvent cabal when there are only boot dependencies (probably just running ghc --make). How does that even work with cabal.project files?

Now there’s also yet another custom config format, which seems to rather add to the fragmentation than remove it.

6 Likes

Both the supposed alternatives for Cabal and GHC (hx, bhc) were LLM-generated by a single user over the course of several months by pushing straight to main with failing Github Actions tests. bhc’s API documentation is outright slop yanked from GHC and it claims it has addressed a whole bunch of longstanding Haskell concerns with no issues opened, no pull requests and presumably zero active users.

This has about as much to do with Haskell as pasture mud has to do with human food. Reposting these kinds of articles should be a bannable offense.


Oh, also there’s a “try it” curl at the end of the article that could well be a malicious link.

8 Likes

too many competing tools? xkcd: Standards

3 Likes

I appreciate the sentiment, wanting One Tool to do the job instead of juggling tools.

I didn’t know about the supposed cabal start-up lag, and that is the kind of thing that it would be good to try and reduce down - but instead of abandoning Haskell for it, surely we should work to remove such weaknesses from GHC-compiled programs, and benefit all GHC packages because of it.

Maybe we can extend linear types for in place modifications, or further mature the non-moving GC, or maybe we can replace parts of C internals with oxidised components, but I don’t think a vibe-coded rust replacement for the ecosystem makes sense.

TL;DR is I think there are some interesting ideas, but this project goes about them in the wrong way.

2 Likes

I read the shell code. It’s not malicious, just a bit sloppy:

    # Create temp directory
    TMPDIR=$(mktemp -d)
    trap "rm -rf $TMPDIR" EXIT

$TMPDIR is unquoted. In this case that cannot lead to arbitrary file deletion, but it’s still bad form.

get_install_dir() {
    if [ -n "$HX_INSTALL_DIR" ]; then
        echo "$HX_INSTALL_DIR"
    elif [ -d "$HOME/.local/bin" ]; then
        echo "$HOME/.local/bin"
    elif [ -w "/usr/local/bin" ]; then
        echo "/usr/local/bin"
    else
        echo "$HOME/.local/bin"
    fi
}

It may install into /usr/local/bin if it’s writable. Weirdly enough, this code actually prefers a global installation, unless "$HOME/.local/bin" already exists… it doesn’t even try to create it.

And then it also asks for sudo:


    if [ -w "$INSTALL_DIR" ]; then
        cp "$BINARY" "$INSTALL_DIR/"
        chmod +x "$INSTALL_DIR/hx"
    else
        info "Requesting sudo access..."
        sudo mkdir -p "$INSTALL_DIR"
        sudo cp "$BINARY" "$INSTALL_DIR/"
        sudo chmod +x "$INSTALL_DIR/hx"
    fi

Despite that, I tried it in a docker container and I found the UX lacking:

$ hx new my-app
error: unrecognized subcommand 'my-app'

Usage: hx new [OPTIONS] <COMMAND>

For more information, try '--help'.

Well… ok. This is what the documentation told me to run. I guess I can do it myself:

$ mkdir foo
$ cd foo
$ hx init
âś“ Created bin project: foo
Next steps:
  cd /root/foo
  hx build
  hx run
$ hx build
Unable to create steel home directory "/root/.local/share/steel": No such file or directory (os error 2)
    Building foo

error: toolchain not found: cabal

fix: Run `hx toolchain install`
      Install cabal
$ hx toolchain install
warning: No version specified
Example: hx toolchain install 9.8.2
Or: hx toolchain install --ghc 9.8.2
Or: hx toolchain install --bhc latest
$ hx toolchain install --ghc latest
  Installing GHC latest
 WARN Direct GHC installation failed, falling back to ghcup: configuration error: Invalid GHC version format: latest. Expected format like 9.8.2

error: toolchain not found: ghcup

fix: Run `hx toolchain install`
      Install ghcup
$ hx toolchain install --ghc 9.12.4
  Installing GHC 9.12.4
âś— Failed to download GHC 9.12.4                                                                                                                     WARN Direct GHC installation failed, falling back to ghcup: configuration error: GHC 9.12.4 download failed: HTTP 404 Not Found. This version may not be available for your platform.

error: toolchain not found: ghcup

fix: Run `hx toolchain install`
      Install ghcup

I don’t know. I ran like 4 commands and they all failed. It tells me to run hx toolchain install, but that isn’t a valid command even (lacks a version). Then the examples indicate I can use latest as version, but that seems to fail as well. Then it seems downloading GHC is broken (it has its own installation logic), then it tries to fall back to ghcup, but that isn’t installed either and hx can’t install ghcup.

Luckily, I know a thing or two about ghcup, so I manage to install it after all.

After installing the toolchain, I run:

$ hx build
Unable to create steel home directory "/root/.local/share/steel": No such file or directory (os error 2)
    Building my-app
âś— Build failed                                                                                                                                     Warning: The package list for 'hackage.haskell.org' does not exist. Run 'cabal
update' to download it.
Error: [Cabal-7107]
Could not resolve dependencies:
[__0] trying: my-app-0.1.0.0 (user goal)
[__1] next goal: base (dependency of my-app)
[__1] rejecting: base-4.22.0.0/installed-fde1 (conflict: my-app => base^>=4.17 || ^>=4.18 || ^>=4.19 || ^>=4.20)
[__1] fail (backjumping, conflict set: base, my-app)
After searching the rest of the dependency tree exhaustively, these were the goals I've had most trouble fulfilling: my-app, base



error: build failed

Oh… uhm. What is cabal? I thought I only have to use hx. Anyway.

$ hx build
Unable to create steel home directory "/root/.local/share/steel": No such file or directory (os error 2)
    Building my-app
âś— Build failed                                                                                                                                     Error: [Cabal-7107]
Could not resolve dependencies:
[__0] trying: my-app-0.1.0.0 (user goal)
[__1] next goal: base (dependency of my-app)
[__1] rejecting: base-4.22.0.0/installed-fde1 (conflict: my-app => base^>=4.17 || ^>=4.18 || ^>=4.19 || ^>=4.20)
[__1] skipping: base; 4.22.0.0, 4.21.2.0, 4.21.1.0, 4.21.0.0 (has the same characteristics that caused the previous version to fail: excluded by constraint '^>=4.17 || ^>=4.18 || ^>=4.19 || ^>=4.20' from 'my-app')
[__1] rejecting: base; 4.20.2.0, 4.20.1.0, 4.20.0.1, 4.20.0.0, 4.19.2.0, 4.19.1.0, 4.19.0.0, 4.18.3.0, 4.18.2.1, 4.18.2.0, 4.18.1.0, 4.18.0.0, 4.17.2.1, 4.17.2.0, 4.17.1.0, 4.17.0.0, 4.16.4.0, 4.16.3.0, 4.16.2.0, 4.16.1.0, 4.16.0.0, 4.15.1.0, 4.15.0.0, 4.14.3.0, 4.14.2.0, 4.14.1.0, 4.14.0.0, 4.13.0.0, 4.12.0.0, 4.11.1.0, 4.11.0.0, 4.10.1.0, 4.10.0.0, 4.9.1.0, 4.9.0.0, 4.8.2.0, 4.8.1.0, 4.8.0.0, 4.7.0.2, 4.7.0.1, 4.7.0.0, 4.6.0.1, 4.6.0.0, 4.5.1.0, 4.5.0.0, 4.4.1.0, 4.4.0.0, 4.3.1.0, 4.3.0.0, 4.2.0.2, 4.2.0.1, 4.2.0.0, 4.1.0.0, 4.0.0.0, 3.0.3.2, 3.0.3.1 (constraint from non-reinstallable package requires installed instance)
[__1] fail (backjumping, conflict set: base, my-app)
After searching the rest of the dependency tree exhaustively, these were the goals I've had most trouble fulfilling: base, my-app



error: build failed

Aha, so it created the project with some random bounds. I don’t even know what it used.

I looked up the code that generates the project: hx/crates/hx-cli/src/commands/init.rs at b9dcac765e9f500e1bce398e6a395a012cb2dae6 · arcanist-sh/hx · GitHub

fn generate_cabal(name: &str, kind: ProjectKind) -> String {
    match kind {
        ProjectKind::Bin => format!(
            r#"cabal-version:      3.0
name:               {name}
version:            0.1.0.0
synopsis:           A Haskell project
license:            MIT
author:             Author
maintainer:         author@example.com
build-type:         Simple

executable {name}
    main-is:          Main.hs
    hs-source-dirs:   src
    default-language: GHC2021
    build-depends:
        base ^>=4.17 || ^>=4.18 || ^>=4.19 || ^>=4.20
    ghc-options:      -Wall
"#
        ),
        ProjectKind::Lib => format!(
            r#"cabal-version:      3.0
name:               {name}
version:            0.1.0.0
synopsis:           A Haskell library
license:            MIT
author:             Author
maintainer:         author@example.com
build-type:         Simple

library
    exposed-modules:  Lib
    hs-source-dirs:   src
    default-language: GHC2021
    build-depends:
        base ^>=4.17 || ^>=4.18 || ^>=4.19 || ^>=4.20
    ghc-options:      -Wall
"#
        ),
    }
}

And at this point, I’m not quite sure if I want to continue my experimentation.

19 Likes

Now that I’ve read this in full, I do believe the author is well-meaning, just misguided.

However, parts of this post are genuinely insulting to the people who are actually engaging with the community in trying to solve these real but difficult tooling problems. I’d prefer that we didn’t waste those people’s time by bringing this to further attention, as there’s nothing insightful here.

Unfortunately, I can see this becoming a repeat of the NeoHaskell thread…

7 Likes

I tried to check out bhs, but it doesn’t compile :man_shrugging:

1 Like

Thanks for the reports, this seems to be indeed the industrialisation of mediocrity combined with delusions of grandeur, and I don’t see any productive conversation arising from discussing this project in its current state.

13 Likes