Wingman for Haskell
haskellwingman.dev
haskellwingman.dev
I hope holes & case-splitting get into many more languages.
Edit: it’s addressed in the Wingman’s landing page FAQ.
> Wingman is built on top of HLS, and that's the reason it doesn't require dedicated editor support.
The project’s source code also lives in HLS, as a plugin: https://github.com/haskell/haskell-language-server/tree/mast...
f :: YourTestInput -> CorrectAndPerformantAdventOfCodeSolution
f = _
---Seriously though, even after writing Haskell primarily for years, I've never done hole-driven development and I'm still not sure what I'm missing. I tried once — granted, that was a long time ago — but I didn't get it. This post[0] suggests I'm not alone in thinking this.
I'm glad Wingman exists and I think branding it nicely like this is the right thing to do, but I think I need to see more concrete examples to really see the magic. Probably something like Gary Bernhardt's Destroy All Software screencast series. It's one thing for someone to tell you that vim is fast, but it's another thing entirely watching the tool come to life in the hands of an expert. I imagine it's a similar thing for Wingman — I still have this mental block where I'm picturing the tool conjuring up an undefined for each hole, which is exactly what the documentation suggests the tool doesn't do. I would love to see someone use Wingman to quickly hammer out an Advent of Code puzzle, or even something more real-world like an Esqueleto query (but is that even possible?)
[0]: https://bitemyapp.com/blog/please-stop-using-typed-holes/
There are more fundamental objections to typed holes - but Wingman is kind of the answer to those concerns - with typed holes you're filling in types from the expressions, with Wingman you're doing pretty much the opposite - synthesizing programs from the type.
Now there might be usability issues with Wingman as well, but it represents a different direction - more suitable for type-driven development.
https://www.youtube.com/watch?v=y8OnoxKotPQ
https://www.youtube.com/watch?v=DYvhC_RdIwQ
Shout out to the Galactus team!
The way the tooling has progressed recently imo is that being proven in real-time.
Some other cool tooling advancements since then that come to mind are.. Nix integration (both nixpkgs and haskell.nix), ghc heapview, ghcjs, a glut of formatters, ghcid (is this that new?)
IDEs are generally terrible for code quality, (and for my personal happiness,) but they're also viral because if one person working on a codebase is using an IDE, then they're probably going to write code that forces everyone else to also use an IDE - e.g. because understanding the code practically requires jump-to-definition.
Wait… so the code you write makes me magically able to know the record field names of your data types without looking at their definition?
Your statement may be true if it’s talking only about the core logic of an application, but when we get to the part that interfaces with the rest of the world, things always get somewhat messy. An IDE is a huge help here.
I mean, sure, you may be able to memorize the arguments that “readCreateProcess” takes (https://www.stackage.org/haddock/lts-18.19/process-1.6.13.2/...), but sometimes it’s really handy to have an IDE help you with this.
How frequently would you use and subsequently forget how to use this function?
If I use a library function like this, I most likely write it exactly once in my codebase. The cognitive overhead of going on hackage one time per project-library is very small.
I agree an IDE makes you marginally faster in this case - just pointing out that this marginal benefit need not be significant.
Hoogle and even a simple "rg -A10 'data TheRecordName'" work perfectly fine. No reason to shave seconds with an entirely different tool.
For readCreateProcess, I just..look at the Haddocks while I program :)
Good Haskell modules tend to be quite literate and are therefore easy to read as plaintext. Clicking into the source of Hackage is often very fruitful compared to other languages (Go comes to mind.)
Often they aren't meant to be read top-to-bottom of course, but they're a few hundred lines of code that makes sense and backs a very readable export list.
And good Haskell isn't especially bound by writing and managing the code. You spend most of the time thinking and then a small amount of excellent, composable code pops out. Rinse, repeat, compose it all together.
Haskell is no different than any other language in this regard. Implementing e.g. a standard CRUD app in Haskell using “beam” involves a lot of boilerplate, for which a proper IDE is super useful.
It absolutely 100% is. This is half the reason people like to use it.
> Implementing e.g. a standard CRUD app in Haskell using “beam” involves a lot of boilerplate
I haven't used beam, but for me, the appeal of Haskell is that that you can throw out like 90% of the low-semantic-density boilerplate you see elsewhere. Composable abstractions, typeclasses, generics, lenses, etc. - all of these things directly serve to reduce unnecessary semantic sparsity, and incidentally obviate the need for an IDE.
An IDE gives you hints and you learn to rely on them; it takes 1 second to check something and you end up doing many checks like this.
In a terminal you have to check everything and remember about everything yourself. Maybe it takes 1 minute instead of 1 second. So you only do this when it matters and you make sure you remember the result because you don’t want to waste that 1 minute again.
Generation with ML and then filtering with the typechecker/compiler for valid results could be a really powerful pair.
I think the key thing is that sometimes there are multiple ways to answer something. For example consider this type:
[Maybe a] -> [a]
It takes a list of optional values and returns non-optional values. The ‘obvious’ semantics in Haskell could be achieved with: f [] = []
f (Nothing:xs) = f xs
f (Just x:xs) = x:f xs
And there are plenty of other ways to write that function. But you could also write functions like g xs = []
h xs = reverse (f xs)
j [Just a, Just b] = [b, a, b]
j _ = []
And all three would satisfy the type above. So the type may be a sufficient input to the tool as the programmer may select the suggestion, but it wouldn’t be sufficient input to the compiler as the compiler could just guess the wrong thing.In this situation, choosing “Fill in hole”, Wingman always just fails with a “ran out of gas”-error.
(Using GHC 8.10.7)
Sure, it's easy money, but also boring, lol
I then had the realization that if someone manages to genuinely automate programming, humans will wind up still being in the loop to confirm that the generated code is reasonable under the hood, and to be sure we have people who understand what it's doing and why.
If I'm right about that, code review chops will be worthwhile in the long haul.
Of course, there is just that existential risk that the machines recognizes the profit distributions we are taking are negatively impacting their margins - and ability to survive. :(
Where all this goes, nobody knows! But it won't be boring.