top | item 37246932

Leaving Haskell behind

321 points| mpereira | 2 years ago |journal.infinitenegativeutility.com | reply

385 comments

order
[+] pyrale|2 years ago|reply
As someone that has also written haskell for about a decade and moved away from it as a breadwinner recently (but for other reasons - I simply wanted to filter job offerings based on social utility rather than language stacks), I definitely agree with the author's first point: the Haskell community values learning extremely strongly. That's great because you work with curious people that have always something to teach and learn. But the community is not so strong when it comes to discard ideas after trying them, and so a professional haskell codebase, if not curated strictly, often ends up with lots of things that you can do but that you probably shouldn't use.

I, however, disagree about tooling. Haskell's tooling sucks, but having used several other languages since (python, js, java, rust, elm), most tooling sucks. After this tour, I miss the Haskell toolchain. Sure, cargo is great, but that's one in many, and most older languages don't have this. I wonder whether Rust can escape this fate as it ages. The author also mentions being dismayed by Python, so I guess that's mostly me seeing the glass half-full though.

[+] ryukoposting|2 years ago|reply
In terms of tooling, Haskell has one thing that AFAIK no other language can compete with: Hoogle. Hoogle is amazing. You tell it, in Haskell, what you want, and it tells you, in Haskell, what you can do. It's extraordinary. Someone attempted something similar with Rust, and I even tried to make a Noogle (Nim), but it just doesn't work the same in languages where there's a clear divide between "passing arguments to a function" and "calling a function." I find myself looking at tangles of Rust code with all its Result<Option<Box<SomeEnum<Nonsense>>>, Box<dyn Error>> and I yearn for a Roogle that provides the same level of utility as Hoogle.

Other than that, Haskell's tooling has no redeeming qualities. Nothing (pun intended). A lot of that can be blamed on the community's instinct to make something "innovative" instead of improving what already exists. I feel the author's pain.

[+] runeks|2 years ago|reply
> The way that Haskell-the-language evolves — well, the way that GHC evolves, which is de facto Haskell since it's the only reasonable public implementation — is that it gradually moves to correct its past missteps and inconsistencies even in pretty fundamental parts of the language or standard libraries.

I would say that the biggest problem is that GHC is tied to a particular version of base (the standard library). So when changes are made to base, and a new version of GHC comes out that supports only this and not earlier versions, you're forced to change basically all of your dependencies, if you want to use this newer GHC version, as they all depend on base.

I still don't understand why this is necessary. Why must code compiled with GHC 9.6 use base version 4.18.0.0? Why should the binary that is GHC care about which version of the Data.List module the code that it compiles uses? I understand that all the GHC-specific stuff exposed by base is tied to a particular GHC version, but why all the rest?

There is, however, work in progress to split base into multiple packages to fix this (as I understand it): https://gitlab.haskell.org/ghc/ghc-wiki-mirror/-/blob/master...

[+] mrkeen|2 years ago|reply
> I still don't understand why this is necessary. Why must code compiled with GHC 9.6 use base version 4.18.0.0?

It's hinted at in the section you quoted. A newer ghc might reject older base code as invalid.

[+] tinco|2 years ago|reply
> I still don't understand why this is necessary. Why must code compiled with GHC 9.6 use base version 4.18.0.0? Why should the binary that is GHC care about which version of the Data.List module the code that it compiles uses?

Because the underlying data types might be different, so if different libraries linking to different `base` implementations pass each other instances of `Data.List`. Imagine for example a Data.List Data.List, could you append the results of functions out of two different libraries to that list?

[+] sclv|2 years ago|reply
Indeed, as you note, a reinstallable base is a goal everyone wants. Its basically historical reasons and coupling of primitives (tied to the compiler innards) to nonprimitives which has caused this situation, but sufficient elbow grease should improve things.
[+] pooya72|2 years ago|reply
If I had to choose the three big factors that contributed to my gradual loss of interest in Haskell, they were these:

* the stylistic neophilia that celebrates esoteric code but makes maintenance a chore

* the awkward tooling that makes working with Haskell in a day-to-day sense clunkier

* the constant changes that require sporadic but persistent attention and cause regular breakages

Valid points. Back in 2010-2012, I spent a lot of time learning Haskell. The language itself is great, but the documentation and tooling was challenging to work with. The community went from Cabal (and the infamous Cabal hell) to Stack, and back to Cabal. Overall, the situation has improved.

On the other hand, other programming languages have incorporated elements of functional programming. Take Java, for instance. It has added features like Streams, functions, lambdas, algebraic data types, records, and pattern matching. While Java's syntax isn't as elegant as Haskell's, it does include the fundamental concepts of functional programming.

[+] JackMorgan|2 years ago|reply
Many of these reasons are why I moved to F# and haven't looked back (much). I sometimes miss Higher Kinded Types, but F# still has generics and if I'm being honest it forces me to write even simpler code then I would have in Haskell. I generally prefer this outcome.

However F# never feels "leet" like Haskell does. It's like Haskell, but all business. I get a lot done in F# and really enjoy it, but I'll catch myself looking back wistfully, like Haskell why couldn't we make it work. Sigh, the one that got away I guess.

[+] xupybd|2 years ago|reply
I'm glad to read this. I'm new to FP and enjoying F#. Because it's terse and down to business. I've often wondered if I'm missing out by not using Haskell. It seems, for my purposes, probably not.
[+] Shumer666|2 years ago|reply
First off, learning Haskell's like trying to decipher an alien language. If you're used to plain ol' if-else loops and straightforward variable assignments, prepare to have your brain twisted into knots. Haskell's got monads, and no, they're not some new type of space monster – they're these weird abstract things that'll leave you scratching your head and questioning your life choices.

Now, I know we all love libraries that make our lives easier. But with Haskell, you might find yourself on a treasure hunt for a library that actually does what you need. The Haskell library scene's like a half-empty thrift store – you gotta sift through a bunch of outdated, half-baked options before you stumble on something that kinda works. And don't get me started on documentation – it's like reading hieroglyphics half the time.

Oh, and performance? Sure, Haskell's got that reputation for being all slick and optimized. But in the real world, you might end up scratching your noggin over why your code's chugging along slower than a snail on a summer day. Lazy evaluation sounds all fine and dandy until your app's gobbling up more memory than it should and moving slower than molasses in January.

Let's talk job prospects, shall we? Unless you're hoping to work on some super niche project for a company that's all in on Haskell, you're gonna have a tougher time finding a gig than a polar bear in the Sahara. It's like showing up to a party where everyone's talking about the latest celebrity gossip, and you're there with your collection of 19th-century poetry – cool, but outta touch.

And let's not forget debugging. Imagine trying to find a needle in a haystack, except the needle's your bug and the haystack is a jumbled mess of functional hieroglyphs. Good luck trying ^^

[+] kajumix|2 years ago|reply
I have been using Scala. I have found that it can give you the best of both worlds. You can reason algebraically, and often after a refactor, if it compiles, it works. Type inference and monads works great too. You also get to benefit from being in the java ecosystem. In instances where it gets too esoteric, I can break rules and code more like java. I am curious what others think.
[+] inickt|2 years ago|reply
I have really enjoyed Scala for the same reasons. Has some escape hatches if needed, and a large ecosystem.

sbt drives me insane though, and is probably my least favorite part of the development experience.

[+] valenterry|2 years ago|reply
Agreed, I feel the same. It also allows for a smooth gradual transition from imperative/oop code to a pfp style.
[+] xwowsersx|2 years ago|reply
It seems to me that the stewards and maintainers of the language actually _intend_ for Haskell to be friendly to research, experimentation and academic pursuit. That is fine, as far as it goes, but obviously this will, at some point, be at odds with the interests of programmers looking to use Haskell as a practical, stable tool. It sounds to me like what is needed is the ability to mark all the experimental, envelope-pushing bleeding edge stuff to a different track (whether through pragmas or even a package level declaration) so that programmers know just by looking at the package whether it's something they want to pull in. This would allow practically-minded developers to adopt a policy along the lines of "we only use Haskell stable" or whatever it'd be called. The dynamic I'm describing is the "Avoid success at all costs" phrase, right? The idea being that if Haskell adoption gets "too high" then the language will become inertial and be unable to continue pushing bleeding edge concepts. What I'm proposing is a way to allow that to happen but also maintain a separate track at the level of the language stewardship that formally acknowledges and recognizes that day-to-day programmers need some way to opt out of some of the edgier stuff and to stay within a more limited subset of stable Haskell.
[+] mrkeen|2 years ago|reply
> That is fine, as far as it goes, but obviously this will, at some point, be at odds with the interests of programmers looking to use Haskell as a practical, stable tool.

That's what Stackage is.

Stackage provides consistent sets of Haskell packages, known to build together and pass their tests before becoming Stackage Nightly snapshots and LTS (Long Term Support) releases. [1]

Java will never get this.

[1] https://www.stackage.org/

[+] BellsOnSunday|2 years ago|reply
There is the Simple Haskell initiative, which encourages what you're talking about, but no flag or pragma that says "this project uses simple Haskell". Obviously, simplicity is in the eye of the beholder. Fancy type features do have their use cases where they make types more expressive, the code safer and even simpler, so long as you've internalised how they work.
[+] joeyh|2 years ago|reply
This is a pretty good post. The weak part of it to me is that I have never felt pushed to use any particular new fancy type stuff if I don't want to. Don't want servant's type-level http apis? Drop down a level and use warp. Libraries are often layered this way because it's understood that excessively complicated types can be a trap. One does have to develop an intuition for how far to go, which will involve making mistakes.
[+] cflewis|2 years ago|reply
Do you know of a good "I don't know Haskell well but this Servant idea sounds incredible, please educate me?" article? I read the Servant docs and they (rightfully) assume I have more Haskell understanding than I do :(
[+] mattgreenrocks|2 years ago|reply
This really resonates with me.

I’ve been using it in a decidedly industrial application for about 1.5 years now. I had some fairly significant experience with it prior (https://github.com/mattgreen/hython).

For the first time in a long time (20 years experience) I’ve needed to learn a significant amount of things. It’s a combo of the domain and the language. It’s rather exhilarating, and also exhausting. Could also be a lot to bite off on with a busy home life too.

Regardless, the language is brilliant. My manager exhorts me to generally write in a top-down manner a lot because Haskell’s flexibility really conveys dev intent well, so think hard about how it should read, and start from there. This is a huge mindset shift from most langs, where you can feel your brain shut off to save cycles as you type “function” over and over. It really feels like it is meant to be write-friendly. Point-free functions are wonderfully terse to write. I joke that TH is my favorite language: a type-checked macro language that lets me write almost anything I want.

And there’s the rub: even with controlled effects via monads, the syntax is still hard for me to scan and read. I don’t know if this comes eventually or what, but this feels like a function of how dense a line could be. I miss early return dearly, and understand why it isn’t a thing (except if you have a MonadZero at hand) but I know it’s a syntactic transformation that won’t make it in. I really miss the amazing Rust LSP. Haskell’s recently lost the ability to flesh out pattern matches due to Haskell internals shifting with 9.x. I still hate and screw up stacking monads. Compile times can be brutal, esp if you hit the lens library. Finally, I’m not a big fan of pervasive laziness: the community has sort of admitted that Haskell programs are far more prone to space leaks developing from this default to the point that many programs may have them go undetected for quite awhile. The systems programmer in me screams out.

I really think the community is one of the strongest group of programmers I’ve ever seen. I don’t want to belabor this and dwell on the big brain memes, it’s more that they think hard on this stuff and actually push forward, vs just telling each other that web frameworks are rocket science and it’s impossible to do better than what it exists.

Ultimately, Haskell fits like a glove for our domain of program analysis. Beyond that, I’d still be a bit wary. I’m still thirsty for a PL that is essentially OCaml but with a better syntax. But that’s just me.

[+] runeks|2 years ago|reply
> I miss early return dearly, and understand why it isn’t a thing (except if you have a MonadZero at hand) but I know it’s a syntactic transformation that won’t make it in.

Early return is mandatory for readability sometimes. I suggest using ExceptT for this: https://www.stackage.org/haddock/lts-21.8/mtl-2.2.2/Control-....

You don’t even have to expose ExceptT in your interface; you can just use it internally for early return and have your function return an Either.

[+] girvo|2 years ago|reply
> I’m still thirsty for a PL that is essentially OCaml but with a better syntax. But that’s just me.

Not just you, me too! In fact it’s why I went in deep on Reason when it arrived initially. Shame it never really got traction.

[+] GregarianChild|2 years ago|reply
> PL that is essentially OCaml but with a better syntax.

Scala 3!

Python-ish syntax, much larger library ecosystem (due to JVM) than either Haskell or Ocaml. Better integration of OO and FP than Ocaml. So similar to Ocaml that idiomatic Ocaml has a simple transliteration.

[+] d_burfoot|2 years ago|reply
As someone who uses a lot of "core" Java (ie not the messy ecosystem), and gets a lot of really complex stuff done with it, I read these articles about high-tech language features like algebraic data types and ultra-strict typing, and I think, what are these people actually doing? The vast majority of software engineering consists of simple operations that move data from one place to another - from a DB to a JSON file, from a REST endpoint to a browser screen. Is all this machinery really helping? Are you sure?
[+] yayitswei|2 years ago|reply
The Blub Paradox is relevant here: http://www.paulgraham.com/avg.html

It's hard to know what you're missing if you haven't tried it. If you see patterns moving data around, it's nice to abstract those out. And higher-level languages give you more powerful abstractions.

[+] sullyj3|2 years ago|reply
Possibly you were using "algebraic data types" as a general stand in for fancy type system stuff, but algebraic data types are actually one of the least fancy haskell features. They're much more straightforward than the name might lead you to believe. I think there's a broad consensus that new statically typed languages ought to have them, eg they've been adopted by rust and swift. I find languages that don't support them very irritating to use.
[+] root_axis|2 years ago|reply
> The vast majority of software engineering consists of simple operations that move data from one place to another

All computing "consists of simple operations that move data from one place to another", that's essentially the foundation of computer science.

It seems you're trying to say "most software isn't that complicated", which obviously isn't true, in fact, the opposite is true: most software is a complicated mess.

[+] LinXitoW|2 years ago|reply
Obviously ADTs are probably just a standin, but I think they are a feature absolutely every language ever should have. They are also not very expensive, esp. runtime wise they're (afaik) equivalent to the clunkier solutions (like classic enums).

Have you never had a thing that could be EXACTLY one of two things, ever? And you wanted the compiler to make sure that, anywhere you used that thing, you had to take care of BOTH of those possibilities? That's one of the main uses of ADTs.

[+] grumpyprole|2 years ago|reply
If Java is so great at solving these "simple" problems, then why do hugely complex frameworks like Spring exist? The language features of Haskell that you mention can describe your "simple operations" symbolically, you then just need to write a few different interpreters, one for real services, one for testing etc. No dependency injection, aspect-oriented programming or AbstractSingletonProxyFactoryBeans needed. You might feel that the complexity has just moved, but I'd much rather invest my time in solutions that are not ad-hoc.
[+] consilient|2 years ago|reply
> Is all this machinery really helping? Are you sure?

Absolutely: ADTs and general-purpose crud stuff are a perfect fit. It's the delicate numeric stuff where typed functional languages are at their least helpful (though I think still somewhat better than imperative ones, except at the ultra-high performance end).

[+] freilanzer|2 years ago|reply
Really complex stuff like moving data from one place to another? That's trivial.
[+] tome|2 years ago|reply
> gets a lot of really complex stuff done with it ... The vast majority of software engineering consists of simple operations that move data from one place to another - from a DB to a JSON file, from a REST endpoint to a browser screen

Is the complex stuff you get done with Java the same as the "majority of software engineering" you describe? I'm having trouble reconciling those two claims ...

[+] blueberry87|2 years ago|reply
What's your workflow for adding a new feature to something? In OCaml, with it's "high-tech language features like algebraic data types and ultra-strict typing", my entire workflow is: - Modify the types to add the new thing. - Fill in all the pattern matching cases that the compiler tells me I need to fill in. - Maybe write a little bit extra logic. And then i'm done, with a solution I know is type safe, will never crash unless i've explicitly let it, etc. How does this help with moving data? Well, wouldn't it be handy if the compiler could stop your program from crashing when something goes wrong, or you could have the compiler track where you've safely and unsafely done operations, or a million other things. Maybe this is too strong a statement, but if you can't see the advantage of that, I don't want to work with code you've written, because you're not doing everything you can to make it as safe as possible.
[+] Tyr42|2 years ago|reply
When writing a compiler it helps for sure.

Though most of my time is protobuf in Java, I still wouldn't mind having an ADT or two for when I got lists of things and the things aren't exactly uniform but I don't want to make a type hierarchy.

[+] chpatrick|2 years ago|reply
I recently went to one of the largest Haskell meetups in Europe and pretty much no one used Haskell any more (including some formerly core people), it was almost just a social gathering.

I think in 2023 many of the things that made Haskell appealing compared to other languages before have been widely adopted, while the developer experience and ecosystem for Haskell is as bad as it was. I wouldn't use it for a new project outside of some specific areas.

[+] nh2|2 years ago|reply
I went to the same meetup (ZuriHac), and arrived at the opposite conclusion.

I gave a lightning talk there on how the Haskell job market has been growing steadily since 2008 [1] [2].

The GHC bug tracker is full of new people filing bugs from production environments.

Consultancy blogs such as [3] regularly show industry-sponsored improvements to GHC, which was much more infrequent 10 years ago.

A this year's ZuriHac, around 50% of attendees were new to Haskell / had never visited ZuriHac before (this was an audience question).

In the past, there were a few well-known companies that used Haskell, in specific niches. Today, the big niches are diminished, and there are more companies that use it in more niches.

> the developer experience and ecosystem for Haskell is as bad as it was

The developer experience improved significantly over the last years.

Today, you can get a good quality IDE environment with VSCode and Haskell-Language-Server that works in both simple and complex environments, and includes all the features you'd expect (completions, immediate type error checking, scoped renames, go-to-definition, find-all-references, call hierarchy, docs-on-hover).

[1] https://news.ycombinator.com/item?id=36742311

[2] https://github.com/nh2/haskell-jobs-statistics

[3] https://well-typed.com/blog/

[+] Capricorn2481|2 years ago|reply
What were some things people were using?
[+] friend_and_foe|2 years ago|reply
I just started learning Haskell last year (at my own pace on my own time) and from the minute I wrote a recursive function I understood how freeing and smooth writing in Haskell was going to feel. Once I got used to the new concepts this was going to be like butter...

The two problems I have with it are the archaic standard library and the tooling. Because everything is atomic and strongly typed, you basically have to rote memorize the standard library before you can use any of it. The tooling is just clunky, I can't come up with a better term. The author is right IMO. I haven't been writing it long enough to get the rug pulled out from under me with regard to breaking changes and that, I guess we will see how that goes.

But the language itself... It's like finding this magical thing. I wish Haskell had better tooling and at least more approachable documentation for the prelude.

[+] regularfry|2 years ago|reply
Hm. Not really convinced by either Ruby counter-example. The first one's ok, ish, but doesn't take advantage of the fact you can pass a block to `zip` so you don't need the `map` call.

The second one's just wrong. You wouldn't use `flat_map` for that if you didn't want indentation, you'd use `Enumerator#product`:

    def all_flavors
      flavors = [:vanilla, :chocolate, :strawberry]
      containers = [:cone, :cup]
      toppings = [:sprinkles, :nuts, :fudge]
      flavors.product(containers, toppings).map { |flavor, container, topping|
        ["a #{container} of #{flavor} ice cream with #{topping} on top"]
      }
    end
While that's not quite doing the same as what `pure` and `<>` do in the Haskell example, the complaint was in terms of visual layout and in that respect it's very similar.
[+] Tainnor|2 years ago|reply
The point is that Haskell's do notation works for every monad, whereas "product" in Ruby works for just a cartesian product of sets, and not any other use of monads (e.g. generating random values, async code, operations that might return errors, IO operations, and so on).
[+] paldepind2|2 years ago|reply
You're absolutely right. That is particular example is bad because you don't actually need the power of flapMap/bind. But, if the available containers depended on the flavor and the available toppings depended on the flavor and/or container, then you would need flapMap/bind and do-notation would pull its weight.
[+] lykahb|2 years ago|reply
I've maintained a Haskell library for databases for over ten years. Here is my take:

Haskell is a complex and a flexible language. It pushes you toward correctness, but in other dimensions is less opinionated than other languages. If you choose to stay away from the low-level Template Haskell (the types for it are updated often) and the bleeding edge type system tricks, Haskell would be quite stable.

The idea to use only the simple parts of Haskell is great - but what is simple is a rather subjective judgment. Oftentimes the technical choices would be made before the the impact on dev ux and effort to maintain becomes clear. Luckily, doing large refactoring in a complex Haskell project is safe, and improving over the initial choices is easier than in most other languages.

[+] yakshaving_jgt|2 years ago|reply
It's strange that under any article like this, there's always commentary along the lines of "Hmm, yes, indeed $LANG is bad. What shall we all migrate to instead?"

Reminder that this post represents one person's opinion.

Haskell is still just fine as a programming language for getting actual work done.

[+] lihaoyi|2 years ago|reply
If you like Haskell but want something else, you really should consider Scala.

It's not the same. But it has many of the same niceties around the rich type system, but with generally good tooling, the amazingly rich JVM ecosystem (tooling, libraries, learning materials), and a somewhat more pragmatic bent to it.

Scala has a bad rep, justifiably so, due to a lot of its problems in the past: community, libraries, tools, etc. But many of those past problems are much better today. Scala today is a much better platform than it was at peak-hype circa 2015, despite some ongoing warts. Nowhere near perfect, but pretty good overall

I see people in the comments looking for a "better OCaml" or a "industrial Haskell", those folks should definitey try Scala

[+] reidrac|2 years ago|reply
Scala has mostly all the issues the post is discussing. Perhaps once you're on Scala 3 you're going to enjoy stability, but your dependencies most likely won't give you that.

And the JVM, and compilation times, and poor Scala 3 support on editors / IDEs.

I prefer the Haskell tooling TBH.

[+] beanjuiceII|2 years ago|reply
"I also… don't really want to deal with them on a day-to-day basis. My personal experience has been that very often these sort-of-experimental approaches, while solving some issues, tend to cause many more issues than is apparent at first."

This one really hits home for me

[+] kstenerud|2 years ago|reply
It's a similar issue with frameworks in imperative languages. Everything's fine until one day you find that you have to step outside of its envisioned bounds in some way. Then the sadness begins.