I’m starting a company, I’m using Haskell and I’m happy with it. Things are going good, the features I develop work, I write tests, everything feels in place, I’ve got no complaints about my current workflow, or the language, or the ecosystem (perhaps about tooling, it was non-trivial to set up my CI/CD pipeline, but that’s a subject for another topic).
The thing is, I’ve never worked with another Haskell developer before, so I’m just doing things my way. Although that’s fine, sometimes I wonder if I’m doing things the “correct” way. (I know it’s very hard to define correctness in software engineering).
Other languages I’ve used have some agreed upon practices that almost everybody using them follow. I wonder if Haskell has the same.
I think one of the beauties of this language is the freedom it gives one to do anything they want in any way they please, but with it comes the feeling that “there may be a better way to do this” all the time. Usually I’m happy if the artifact does what I want it to do under production loads, but I digress.
Is there a way to write Haskell that I can expect to see if I start reading codebases out there? Or is it common for each project or company to develop a style?
The question of “best practices” is something that comes up very often, and I am afraid this isn’t really a thing in Haskell at this time.
For example, every codebase I’ve touched has had a different logging solution. Some people like to use mtl, but others prefer the use of transformers directly. There are many powerful database libraries available.
There are things that come up more often than others – for example, I’ve never seen command-line parsers other than optparse-applicative, although other seemingly high-quality alternatives exist. Likewise, amazonka is the fully-featured AWS client.
It’s a bit of a rambling answer to tell you that there is no such thing as a single vision of idiomatic Haskell, as far as I know. This can be both terrifying and freeing! Go ahead and do what feels right. One of the main benefits of Haskell is that it’ll be easy to refactor if you change your mind.
Yeah, I guess that’s part of the reason Haskell is so fun, you have to actually make choices and find out what’s better for your specific needs. And it also allows a lot of personal expression in the code style and practices. It’s like painting, in a way, and we have lots of freedom to decide how we fill our canvas.
There is not an idiomatic Haskell, and I think initiatives to codify a “simple Haskell” or “common Haskell” have stalled out. The best advice I’ve seen is to cultivate an engineering dialect almost as part of your business strategy. For example, Obsidian Systems’ strategy appears to be “build the biggest, most powerful levers you can and apply them to the problem”, which is why they’re deep into things like Nix and Reflex. The tools you reach for will be determined in part by how you plan to hire, and how long you’re willing to spend onboarding as you expand.
I find the type-level “big guns” (like package singletons) usually start to creep in when I try to use types to nail down the last 10% of a problem. Often, 80% correcteness is significantly simpler to write and maintain, and still leagues ahead of many other languages.
EDIT: Oh, there’s also a fair amount of folklore around single-letter variable names, particularly in type signatures. I tried to write up a thorough dictionary of them.
Other languages I’ve used have some agreed upon practices that almost everybody using them follow. I wonder if Haskell has the same.
There are a lot of best practices for writing Haskell, but many of them aren’t specific to Haskell (like “give sensible names to variables” and/or are so widespread in Haskell that you don’t really notice them (like “try to keep your functions pure”)
While Haskell’s best practices may be much broader than those of other languages, they nonetheless do exist.
Keep in mind that these are obviously not rules set in stone, you should adapt to your situation.
When working with other (Haskell) programmers on the same project, having a set of guidelines makes everyone’s life easier. The point is not to please everyone, but to have the code be predictable for everyone.
Use a style guide (e.g. we used Kowainik’s guide with our own tweaks)
Decide on an automatic formatter (doesn’t matter which, just pick one. We used fourmolu)
Maybe even enforce the format in your CI/CD (but do make sure everyone uses the same version in that case)
Not much else I can add; great thing about strong typing is that you can refactor “relatively” easily, so if you change your mind or the requirements change, refactoring isn’t such a set-back.
All in all, I wish you the best of luck in your endeavors, and when in doubt just ask fellow Haskellers more specific questions. Like, “which package should I use [in my current situation] to achieve [insert specific goal]?”
My first thought, as others have said, is there isn’t as much of a drama with Haskell as other languages. There’s no ‘design patterns’ compendium, for example.
OTOH, there’s certainly newbie code I would (and have) described as unidiomatic. See Stackoverflow [Haskell] {not |un}idiomatic.
Telltales include too much nested if then else rather than case | guards; few curried definitions; over-use of Lists; …
A useful discussion example might be this Java-to-Haskell case study. My feeling is both of the Haskell approaches seem too Java-y(?)
That the code is no shorter isn’t really the point. Using advanced List functions like intersperse is rather cheating (and intercalate smuggles in a concat, with embarrassingly bad performance).
Along the same lines as that Layer Cake piece, there isn’t clear separation of the ‘business logic’ (a triangular array) vs the human-readable prettifying vs the IO.
Back in junior times I found it very helpful to just read source code of libraries I use (aeson, base, whatever) to get a feeling of how the code should be structured. Eventually I developed my own style.
I find using formatters very disturbing however. Even in a small team it’s surprisingly hard to settle on one version of the same formatter or vscode remove-trailing-whitespaces (silly, I know!). This often results in huge diff noises.
There are some classics everyone should read (like “Parse, don’t validate” mentioned), otherwise there is just a multum of correct ways to write the exact same thing. I find style guides more than enough and satisfactory for this purpose. Some people like writing list comprehensions, some use list’s Monad instance. Some default to mtl, some prefer explicit IO. Make an informed decision and stick to it. Luckily, Haskell is just extremely easy to refactor, so the decisions can be changed.
I’m not a professional Haskeller, but I like to think Scott Wlaschin’s Domain Modelling Made Functional has served me well regarding general architecture and design. It’s an F# book so this is not a direct reply to your question about Haskell idioms though in my opinion it translates in a very pleasant way to (simple?) Haskell