Welcome again the Case Split plugin for HLS
Hello everybody!
This summer I am a contributor to GSoC, and I’m pouring my energies and keystrokes into the HLS ecosystem, specifically into #5013, by bringing back to life a plugin that’s been dead for a while, the Case Split plugin.
What’s it for?
This plugin, long story short, will turn this snippet
data RGB = R Int | G Int String | B
case _ :: RGB of
R _ -> foo
into this
data RGB = R Int | G Int String | B
case _ :: RGB of
R _ -> foo
G _ _ -> _
B -> _
saving you from having to mechanically type all those¹ ->s and a list of patterns that “why do I have to tell them to the compiler, doesn’t it know them already?”
Furthermore, whether I manage to get it within GSoC timeframe or not, this work will be reusable in providing very similar features, such as splitting function definitions:
foo :: Bool -> Foo
foo _ = _
into this
foo :: Bool -> Foo
foo False = _
foo True = _
Where am I at?
The progress is tracked by PR #5104.
I’m tempted to claim I’m not too far off from completing the project, as I’m now more optimistic about it, because I perceive I’ve already learned a lot, and I feel more confident in exploring the code, and trying things out. For this change of perspective, I can’t thank enough, for the support I’ve received form them, my mentors Hannes, Magnus, and Andreas.
To give you an idea, the plugin already works as described by the two snippets above, but it still needs polishing.
My experience
This post is not intended to be a guide for writing an HLS plugin (I still have quite some work to do, to finish the new plugin, so…), but just a way for me to share my experience as a GSoC contributor, through a list of suggestions for whoever, like me, starts contributing to the Haskell ecosystem without too much previous OSS or Haskell experience.
Always connected
First I want to share suggestions that are not Haskell-related.
If you are terminal (no pun intended)
You do want to know about Byobu, because it’s just great! It’s basically a terminal-based window manager that keeps running even if you close the terminal, and that allows you to access to it from multiple terminals!
Plus, it highly improves your experience if you have to SSH into the actual machine you work on from another one (my workstation is in a room that faces south, which is scorching hot in the italian summer, so I tend to code from my laptop in cooler room; my laptop would melt if I compiled HLS on it).
Remote
Also, still because of summer, sometimes there’s power outages where I live, and if you’re working from remote (e.g. you’re somewhere on holiday during the day, and working on the project during the night), you can’t afford you computer going down forever, because you don’t have physical access to it.
Well, be aware that some motherboards have settings (look for them in the BIOS) for automatically turning the system back on after a power cut, and also settings for turning it on based on a schedule, e.g. every day at a given time. I can’t tell you yet it’s bullet proof, but you might want to enable these sort of settings.
Know your IDE
Just to give an example, at some point I wanted to know what, if any, were the differences between the test failures I was observing with GHC 9.6.7 versus those I would get with GHC 9.8.4. Since all lines were deemed to be different because of the different timestamps, I wanted to use the full capabilities of a text editor to see the diff. In Vim, answering that was as easy as running the two cabal test commands in two :terminal windows, going normal mode in those terminals, and finally comparing the two windows via :diffthis to verify that indeed the failures were identical.
Write down your thoughts
Several times, I got unstuck by simply crafting a question to ask somebody else for help. The process itself of organizing the thoughts in order to write them down in a question for somebody else to understand, augments your concentration and forces you to focus on the tiny details. Getting yourself into the habit of doing this is not an easy task (at least not for me; the feeling that my writing will be “wasted” is always around the corner), but the benefit is worth the effort.
Haskell
And here are some suggestions about our lovely language.
Working on this project is being very instructive. I’m learning so much, and learning goes through the difficulties one finds along the way, and the solution to those difficulties.
Do learn how to use pattern guards and MultiWayIf
You want to know what the following does:
= if | Just foo <- bar -- no warning here
, let Just baz = quux -- warning here
, bool -> qux
| otherwise -> wuz
Mastering this will allow you to write much neater code.
Typed holes
In the past I had sometimes written undefined in places where I didn’t know what to write, just to have GHC tell me what type was expected there, but I would reach for that only occasionally.
With this project, I decided to make more substantial use of this approach, known as Typed holes. Do read through that page, and most importantly look for -fmax-refinement-hole-fits/-fno-max-refinement-hole-fits and -fmax-valid-hole-fits/-fno-max-valid-hole-fits and similar options. I found great value in these settings.
More in general, there’s great value in being able to keep writing code even thought we don’t know what to write in some specific place. For instance, if you find yourself think you know that you should pass some term of type Foo to the function bar, but you don’t know how or where to get a Foo, from, go ahead, don’t be afraid to write
do
bar (_ :: Foo)
or even
let foo :: Foo = _
bar foo
You’ll figure out what to put in place of _ later.
Hoogle
Always keep Hoogle on a browser tab, and force yourself to use it if you haven’t yet, because it’s great together with typed holes: you know the signature of the function you need in one place where you’ve temporarily put a _, but there’s no fit for it, because you haven’t got valid fits in scope? Paste the signature in Hoogle, and see what you find.
import Debug.Trace
When you really want some visual feedback of what you’re program is doing (and since Haskell Debugger, read below, is still work-in-progress), you can reach for trace* functions in this module. They correspond to the various std::cout in C++, console.log in JavaScript, or any logging mechanism in other languages. Nothing you’re gonna push upstream, but very useful for making reason of why the code is behaving as it is, when the code you’re working on is not (hopefully not yet!) strongly typed enough for you to know what’s wrong from the types themselves.
Tests
Write tests early. Even if the test is just gonna fail all the time because you have’t even written the code you have to write, there is value. After an initial investment of time in writing the boilerplate of your test (which you can also just copy-paste-edit from existing tests!), checking the code you write against tests at the modest price of cabal run/test is really beneficial for your mental health. I mean, versus having to move to another IDE instance, where you have to reload the HLS to digest your latest edits to it, and manually test what it does now.
Also, be aware that you can change the timeout of tests. There’s several ways: you might just change an environment variable,
LSP_TIMEOUT=5
or passing an option to cabal test/run:
cabal test <plugin-tests> --test-options="--lsp-timeout 5"
cabal run <plugin-tests> -- --lsp-timeout 5
Side tasks
Do not underestimate the benefit you get from addressing smaller tasks that are not strictly part of you project, because they can be training/playground for you to warm up for the actual project. I, for instance, hit the bug I reported at #4935, and fixed it in PR #4937: this gave me a very confided context in which I could experiment with and understand some of the ghc-exactprint library, which I would have used in my actual project.
Haskell debugger
Yes, a graphical debugger for Haskell exists, it integrates with VSCode, and I’ve also provided instructions on how to use it in Vim, using Vimspector.
As you can see on the marketplace, it’s not a complete debugger yet,
We are working on a first class debugger for Haskell. It is still not ready for general consumption!
but guess what? Its development is truly active. The reason it can be used with Vim+Vimspector, for instance, is precisely that Rodrigo Mesquita addressed a DAP non-compliance I reported, after just two days; he and other folks are truly actively working on this debugger, so the more you make use of it and report bugs and similar, the better it will get. Fast.
--lib
Occasionally you’ll find you need to install some library, and you will probably do it via cabal install [--lib] library-you-need.
I have forgotten or haven’t fully understood the implications of the --lib to cabal install, but a good rule of thumb is: don’t pass it, and if you think you have to pass it, probably it’s time you ask to more knowledgeable people.
¹ No, you’re not gonna get zillion lines if you case some :: Int of and, more in general, the plugin will only insert as many missing patterns as the -fmax-uncovered-patterns flag commands.