Convenience in the Haskell ecosystem

A little off-topic, but: one of the challenges of a potential stack add (and it arises in other areas of Stack too) is this:

If you have a *.yaml file that is ‘nicely formatted, with comments’ (from the perspective a human user of the file), and a machine then wants to add (or remove) some element of it, and you want the end result to still be ‘nicely formatted’ (as before) - that is a non-trivial task.

If anybody reads this and knows of an existing Haskell library that can acheive it - please post here! EDIT: By the way, ChatGPT can do it, if you ask it nicely!

4 Likes

Just wanted to say that I’ve been a big stack user since day dot, thank you @mpilgrem for picking up its maintenance :raised_hands:

5 Likes

The task is not just non-trivial, Cabal has certain operations that do similar things, like cabal gen-bounds, and the issue has been sitting unresolved for years (#7544). It’s not even all that hard to do, just that it requires a lot of commitment and everyone else has better things to do.

5 Likes

Yes, I’m aware of the history.

If I may be blunt for a moment, my own view of the reason that curated Stack templates were unsuccessful is that they just aren’t very useful for most users. If I look through the stack-templates repo, they mostly fall into two groups:

  1. Templates focussed around a specific library: foundation, hakyll-template, servant
  2. Templates associated with a specific person: chrisdone, franklinchen, kurt

Group (1) are useful if you’re using that specific library — but if you’re not, they’re irrelevant. Group (2) is even worse: how am I, as a beginner, supposed to know if I should follow Chris Done’s or Frank Linchen’s template? And I’m not even sure what value curation has for such idiosyncratic templates.

I suspect curated templates would offer the most value if they were just the opposite: there should be a small choice, each template should be of general applicability, and it should be obvious which one to use when. I’m thinking it could work better to have a much more limited choice between, say, minimal-lib, minimal-exe, large-exe and batteries-included, or something along those lines. Further choice could be provided by a mechanism like that offered by the current Stack template support.

Indeed, this requires writing an exact-printing parser, which is difficult. As it happens, I believe @Liamzy is currently working on one for Cabal, but I’m not sure of its current status.

4 Likes

This was my impression as well. I’ve tried to do data analysis in Haskell in a project that already had all the libraries included, was just testing performance of some algorithms. I could generate a plot after a while but with Python it was around 4-5 times faster to generate it (we had a python shell around the library, so I was fortunate to be able to test both).

Had a similar experience with diagrams vs just using Cairo in Python.

The Haskell libraries are safer, better designed, but also slower to get something up for single use. Sometimes now when I do something, I get a quick prototype in Python to see what libraries I need, and when this gets long I switch back to Haskell

3 Likes

This is one of the reason why I have given up on using external tools and haven’t even try HLS. I rely instead on little knows ghci commands :type-at, :uses and :loc-at.
.

I believe those commands has been merged from intero, leading to intero to be obsolete.
However, they are a bit tricky to call manualy but are easily to integrate in a text editor.

I use vim and tmux to load ghci and send command from vim to (via tmux) ghci.

Here is the code vim code to transform a visual selection to a ghci command

vnoremap <space>rt :call Haskell_type_at("type-at")<CR>gv
vnoremap <space>ru :call Haskell_type_at("uses")<CR>gv
vnoremap <space>rl :call Haskell_type_at("loc-at")<CR>gv
nmap <space>rT viw<space>rtv
nmap <space>rU viw<space>ruv
nmap <space>rL viw<space>rlv

function Haskell_type_at(mode) range
  " mode can be type-at uses loc-at
  let l:command = printf (":%s %s %d %d %d %d\n",a:mode,  expand("%:p"), line('.'), col("'<"), line("'>"), col("'>")+1)
  call TmuxSend(l:command)
endfunction
3 Likes
  1. Like maxigit, I have not experienced 80% of this hassle because I have never used HLS.

    On Emacs, both haskell-mode (but requires one single non-default setting) and dante already do 80% of what HLS does for less than 10% of the setup cost. (They both just call ghci’s :type-at etc. (To be sure, that approach requires :load to be successful; so I just add -fdefer-type-errors to help.))

  2. “Patches welcome.”

  3. The Haskell culture and the Python culture have a fundamental disagreement on two axes about libraries.

    1. compiler-stays-minimal vs batteries-included

      (You know what, a decade ago Haskellers actually tried batteries-included. Look for the history of “Haskell Platform”.)

    2. you-have-choice vs only-one-library-to-choose-from

7 Likes

I haven’t tried out haskell with emacs, but the easiest path to using HLS is to just use vscode. You don’t have to fiddle with hie files or anything of the like. You only need GhcUp and to make sure vscode knows where GhcUp is and everything else happens automatically.

I used to be an avid emacs user but I decided I wanted “easy street” for the rest of my days, so I switched over. I know vscode isn’t everyone’s cup of tea, but if you give it a try, you’ll find vscode + haskell works wonderfully well.

1 Like

I think HLS is kind of overkill for smaller projects. ghci can take you quite far.

And the on-ramp to a new Haskell project is quite smooth if you use ghci. I don’t consider adding packages to a cabal/package.yaml file to be especially onerous.

1 Like

Having to fiddle with hie.yaml files really shouldn’t be dependent on the editor. Indeed, for about two years now, writing a manual one shouldn’t be necessary for all but the most esoteric setups.

1 Like

For the people here who are saying HLS isn’t necessary: you’re not wrong, but recall this thread is about convenience. It’s entirely possible to develop a project without HLS, and I’ve done so myself many times, but having proper editor integration is a far nicer experience.

I’m proposing something quite different to the old Haskell Platform: not a set of globally available packages, but merely a Cabal file template with a curated list of useful dependencies. The latter seems very reasonable to me, since it imposes nothing on the Haskell community at large: it just provides a starting point for a project, if one so chooses.

6 Likes

I’m not entirely convinced by this argument. If you start out with a blank .cabal file with only base, then you must add additional libraries (even boot libraries that will always be available). However, if cabal-install defaulted to adding a few of the more popular boot libraries, then it would be a little easier to get started. In a lot of cases, you wouldn’t have to remove packages.

You’re 100% correct that people would complain about having to remove packages, but my guess is that overall, it would remove more friction for more users than it would create.

Of course, trying to come up with which libraries to add by default to would likely lead to a lot of bike-shedding.

I completely agree with this as well, but I imagine you’re underestimating the importance of the default template.

It is hard to have any sort of meaningful discussion here without data, but I’d guess that cabal init or stack new would be called without the --with-template argument (that is to say, just using the default template) at least 75% of the time.


The basic argument I’m trying to make is that the default template is likely pretty important, and adding a few of the popular boot libraries would likely remove friction, at least when aggregating over all users.

8 Likes

A possible compromise: cabal init is interactive, so as the first question, ask for a template to use.

As a matter of fact, it basically already does this: it’s just that the available templates are limited to ‘library’, ‘executable’, ‘library and executable’, and ‘test suite’.

6 Likes

Agreed. I don’t at all mean that hls isn’t without its faults; it should work effortlessly in atleast all of the major editors. I was just writing to make it known that there’s at least one setup where HLS does work with the level of convenience we’re hoping to see in the Haskell ecosystem.

At work we have our own build system, we don’t use cabal. But one of the things it does is automatically include a small hardcoded set, such as text, mtl, vector and some others. It’s hardcoded for everyone and not configurable. I’ve never regretted that, and in fact I tend to forget those are not technically built in. As a contrast with cabal, the build file for an executable starts one line: haskell.executable { main = ./Thing.hs; private = [./Mod.hs Mod2.hs]; }

Part of the reason is that there is an ambient set of globally pinned versions, while cabal must live in the more complicated multi-version world. But, one line! Cabal buries me in bureaucracy about licenses and versions and descriptions and haskell language version and yes I want base and blah blah blah.

Back in cabal v1 days it was actually 0 lines, install what you want and ghc --make. Yes I know it led to its own problems and I’m not sure but cabal v2 may finally have a way to emulate the old way, but I don’t mind delaying the bureaucracy until I actually need it… and it turns out usually I don’t, since most stuff never goes to hackage.

While I’m complaining about cabal, does it still not have a way to generate the deps list from the imports? Or maybe someone wrote one and it could get integrated? Especially because in haskell module names can have nothing to do with the package name, that seems ripe to automate. I have imports automated for decades now, if I had been using cabal during that time I would probably have come up with another program to generate the import → package mapping.

1 Like

Not as far as I know. That feature sounds like a good one, and a natural extension of HLS’s feature to automatically import modules that provide the identifiers that you use.

1 Like

Are folks aware of static-ls? I’ve never used it, but it sounds like it fits the bill.

The goal of static-ls is to provide a high-speed, low-memory solution for large projects for which haskell-language-server tends to take up too much memory on recompilation.

5 Likes

This is the first I’ve heard of it, and it looks great! I’d be very happy with this as a fallback when HLS doesn’t work.

The main reason I am not using HLS is I don’t know how long it will leave. I have been doing Haskell for 20 years and I have seen countless cool projects going abandonware : ghc-mod, ghc-mod-ide, intero. In fact, the only stable tools are canal and ghc/ghci (sorry for the stack team).
Whatever people are telling me, I have no guarantee that ghcup and HLS will be still there in 5 years time (it takes 9 months to make a baby and then people realize that it is there main priority).

The only long term solution seems to merge HLS feature into ghci (with maybe a system of external plugins for exotic features). Of course if we think LSP is a long term solution itself.

2 Likes

So… I know this is all sorts of horrible, and I’m not suggesting the implementation resemble it at all, but for scaffolding a few general purpose project types with sensible defaults, could we take inspiration from:

1 Like