> One of the main reasons I don't care much about Haskell is because without any side-by-side comparisons of Haskell vs <insert procedural language> I don't understand what the Haskell advantages are, and I don't know when I'm dealing with a problem space where Haskell would help me.
This is one of haskell's biggest problems. It's just enough outside of the normal flow of imperative languages (yet usable for the same problems) that you can't tell how much of an improvement it is unless you try it. Also, people who write haskell are more inclined to share lofty/abstract/interesting-to-other-haskellers code rather than your normal day-to-day code that is massively improved/safer and benefited from haskell's features.
I've mentioned this before, but one example of where it became apparent to me how much haskell had changed what I expected from language was non-nullable types. It's starting to be really common in languages now (typescript, kotlin, etc), but if you are used to writing imperative languages, the worry of nil/None/null is ever present, and a concept like Optional<T> is actually quite foreign looking. If you really think about it, it means that none of your language is safe -- none of your functions are safe because they said they wanted a String but you might have gotten a null that looks like a String to the typechecker and will blow up at runtime.
Another key improvement in haskell is the removal of class-based code-sharing (i.e. inheritance) -- the separation of behavior and data is really important, and most languages are starting to come around to this now (go w/ structs + interfaces, java w/ data classes, kotlin w/ data classes, rust w/ structs + traits), but haskell (and other ML languages) have been there for a while.
Yet another key improvement in haskell is the errors-as-values paradigm that is everywhere. If some function has a possibility of failure, then it should return `Maybe TheThing` or `Either AnError TheThing` (see how nice and legible those types are?) -- this forces explicit checks on failure and allows cases where there isn't a chance of failure (just `TheThing`) to speed ahead without nullchecks and be fairly certain. This actually pressures you into trying to sequester failure across your codebase -- you try to write functions that have signatures like `TheThing -> SomeArgument -> OtherThing` (see how legibile that is?), to minimize on the amount of `Maybe x` or `Either error x` you have to deal with -- this is often if not always good for codebases.
Maybe this is something I can help with, I write about pedestrian haskell a bunch, and I've been meaning to do a blog post on why haskell is better <your language>, something to really rustle the jimmies.
BTW, the quote about resumable exceptions is actually referring to a concept called a monad, which can be incredibly hard to grasp if you don't look in the right places (there are a lot of bad tutorials out there), or don't give your brain long enough to marinate in the concepts. If I were to take a stab at explaining it simply, in this case it's like a combination of exceptions-as-values (i.e. not go's approach, and not java's approach) and the value that is being passed around has enough state in it to continue stop, fix itself, whatever else. When something goes wrong in most imperative languages, you kind of get the hell out of dodge, and you lose access (usually) to whatever work was done up until the function boundary -- it doesn't have to be this way but it usually is.
> Also, people who write haskell are more inclined to share lofty/abstract/interesting-to-other-haskellers code rather than your normal day-to-day code that is massively improved/safer and benefited from haskell's features.
When Rust started getting flooded with the "web" crowd of ex-Rubyists and the like there was a lot of push back from the traditional systems people (for better or worse). But one of the benefits is that these guys are typically far better at communicating and selling languages to the general developer public.
I too have run into countless examples of these "beautiful" Haskell code examples but when it came down to doing real work I felt like I was left to either figure it out myself, try to connect a more abstract blog post to more practical applications, or left reading some auto-generated Haskell/library API documentation (75% was the last one).
Maybe Github and Facebook et al can lend some of these resources to teaching Haskell to the public and releasing well-documented libraries which set a standard for others to follow? It may have a high learning curve like Rust, but it's far from impenetrable for your average developer.
> When Rust started getting flooded with the "web" crowd of ex-Rubyists and the like there was a lot of push back from the traditional systems people (for better or worse). But one of the benefits is that these guys are typically far better at communicating and selling languages to the general developer public.
I 100% agree -- this is the crowd that brings the hype (for better or for worse). I guess it's another one of those life lessons, but projects need both types of crowds (and to be honest it's not like there's a strict separation, lots of people fit in both camps).
I think Rust is actually going to eat a ton of what could have been Haskell's lunch -- it's a great typesystem for the traditional imperative language crowd and awesome performance for the ML crowd, and a completely new paradigm of data safety that neither of those crowds had before. These days I struggle to choose Haskell, but have settled on Rust for "performance critical" things (I don't really write truly low level software so take that with a grain of salt), and Haskell for everything else.
> I too have run into countless examples of these "beautiful" Haskell code examples but when it came down to doing real work I felt like I was left to either figure it out myself, try to connect a more abstract blog post to more practical applications, or left reading some auto-generated Haskell/library API documentation (75% was the last one).
> Maybe Github and Facebook et al can lend some of these resources to teaching Haskell to the public and releasing well-documented libraries which set a standard for others to follow? It may have a high learning curve like Rust, but it's far from impenetrable for your average developer.
Hugely agree, but I think it's gotta be a community effort. FPComplete is out there doing stuff, and there are lots of individual bloggers, but Haskell needs more people writing "pedestrian" programs. I think it's one of the main ways of contributing to a language that is often overlooked. I don't have any numbers, but learn you a haskell for great good has probably lead to thousands of new haskell devs over it's lifetime, even if the information in it is outdated (and some consider it not a good starting point).
To compound all this, haskell also has a documentation problem -- the machinery is there but it often doesn't get written, or people don't include the "getting started" use cases. Most popular libraries are workable but some others aren't, so it's intimidating until you really start to see the types as sufficient for understanding.
Small shameless plug, I try to write about haskell and am in the middle of a post where I make a CountMin data sketch right now, I'm not quite done with it but hope to have it done this weekend. I feel in that way I'm at least doing something to help the haskell community.
> (and to be honest it's not like there's a strict separation, lots of people fit in both camps).
In my experience it always has to be both (a developer with good communication/marketing skills). Any non developer pushing a language or platform is always the wrong choice and will probably scare away more of the devs, who want to take specific code not just genetic benefits, than help. Too many “developer advocates” have rubbed me the wrong way.
Besides most of it is good web design, writing good newbie friendly documentation and guides, answering questions on HN/Reddit (which Jose from elixir is really good at).
Then once you get past the early adopter phase you need to convince the CTOs, who listen to their developers but also take a strong long term risk analysis when judging it. Including things like hiring and support for core libraries.
None of this will happen without the initial group getting drawn in. So hopefully we’ll continue to see more blog posts like above by Github giving their honest practical feedback and publishes libraries.
I hear you! I was going to try and get the CountMin post done this weekend and update this but I guess I gotta let it roll with what I have already done:
One of my better Haskell posts is a series on writing REST APIs. It kind of gets off the rails type wise as I try to get more and more clever, but I rein it in:
I think you need to get rid of the margin-left and margin-right styles on '.article-content pre' for '@media screen and (min-width: 989px)'. It pushes the code off the bounds of the page on my screen.
The selling point for me to get into Haskell was how much better it's lowest bar is than pretty much every other language I've used so far. It's not perfect but it has a very good effort to power ratio.
For example, I have a database with millions of unstructured, schemaless JSON documents. The documents are mostly similar but the schema is defined by Javascript code that has been maintained and modified for many, many years and so the documents have many, many edge-cases.
I've dared my team to write a JSON-Schema document that could validate our format. It would take a lot of effort to get that going. And it would be verbose and hard to work with: JSON-Schema itself is written in JSON and there exists no tool that can understand the types in the schema, validate them, etc until you run the program on some documents.
Instead I spent a few hours and wrote a type that loosely described some of the more common types I've come across in Haskell. I used the wonderful JSON libraries to parse documents from our database using my small, limited type in a test suite. It failed initially but the type system pointed out where it was failing and why. So I added more cases to my type, improved the parser, until I could parse a handful of real-world examples.
From there I wrote a tool that scans the whole database, collects the parse results, and displays the top N parse failures with example documents to add to my test suite. I use the test suite to interrogate the parse results, add more cases to my type, extend the parser, etc, etc...
I've spent very little time working on this and got my tool successfully parsing almost 90% of the database. When I add new examples the type checker guides me as I interrogate the new case, fix the edge cases, and get the tests to pass. Once I can parse 100% of this database I can start writing a tool to migrate my messy data structure into a more clean, consistent one with a more simple parser and prove that the migration is total. And I suspect it will take even less effort to do that.
I'm not even leveraging anything more advanced than ADTs, type classes, and functions. For very little effort Haskell has given me the ability to solve valuable problems. Problems that scared other developers away using other languages.
This is an awesome usecase and a really good use of the expressiveness of haskell -- if your org has an engineering blog, please write about it I'd love to read more.
> changed what I expected from language was non-nullable types
I got the same revelation from the a lot more conventional [] looking Crystal, which don't solve it through optionals but through union types. Exposure to that kind of type safety to enforce non-nullability is really a watershed moment.
[] For values of convention that look like Ruby. Not everyone think that look is conventional enough.
It's interesting that any time I read about Haskell, I realize most of the features can be found in other languages.
Functional programming and lazy evaluation are common in Apache Spark ("analytics engine for large-scale data processing."). You cannot write a good pipeline if you think in terms of imperative language.
Non-nullable types can be found in Java (@NonNull) and in C++ (references). C++17 got std::optional type.
We have languages without inheritance, like Go, Rust and so on.
Errors-as-values are pretty common as well (C++'s boost had boost::error code for a while now, and of course there is Go again)
Even monads find themselves in other languages -- using org.apache.spark.rdd.RDD is pretty close to IO monad.
I find this an unfortunate downside of many Haskell tutorials -- they often claim there are unique features that are present in Haskell only, but on the closer inspection, it turns out those features are present / can be trivially added in many other languages as well
Hey that was kind of my point -- but I think you have it in reverse, Haskell has had a lot of this stuff for a long time (as in most of them since it's inception), and it's trickling down to other languages now.
But to make some concrete counter points:
- Apache Spark is not a general purpose programing language (you're totally right about FP and lazy evaluation being important in DAG-land of course)
- "Non-nullable types can be found in Java", yeah except them being the default is the big innovation, along with the recognition of the problem, and facilitation of the worldview that recognizes the issue. Optional didn't show up until a few years ago (Java 8?), first class functions weren't a thing without subclassing till around then too, Function references, Functional interfaces, etc. I'm less familiar with C++ and it's commendable that it's adopting new things and people are moving forward, but it's basically the gold standard of footguns with type-system scopes attached (again I don't write C++ on a daily basis and haven't felt just how much better the new editions are).
- Go and Rust learned from Haskell, Rust heavily so. BTW these days I'm more and more of the opinion that Rust is the one more worth praising of the two
- What go does is kind of error as values, but it's also kind of not -- I mean a near complete lack of use of exceptions at all. The distinction is subtle, but coding to always handle the error case (because it is the result) is different from having a sometimes-present error code that you sometimes check.
- Again, you're right that Monads are everywhere -- Haskell didn't invent the concept, but it is one of the places you can go to see it actually used functionally and learn from what people are doing with it (never mind all the novel papers).
Haskell is one of the few places that all these features come together to form a coherent whole.
> Functional programming and lazy evaluation are common in Apache Spark
Spark ain't a language, it's an engine. Anyhow, you could make the point for lazy evaluation in stream libraries in many languages. It's not really comparable to having this as a first class citizen in the language, just like Guava didn't make Java7 equal to Java8.
> Non-nullable types can be found in Java (@NonNull) and in C++ (references). C++17 got std::optional type.
The reason people talk about it isn't about having non nullable types, it's about not having nullable types (or, at least, not having them as a default).
> We have languages without inheritance, like Go, Rust and so on.
I don't believe not having inheritance is a language feature. People may say that inheritance was a mistake and that languages without it are better off, but you will rarely hear about no inheritance being a feature of a language.
> Errors-as-values are pretty common as well (C++'s boost had boost::error code for a while now, and of course there is Go again)
Haskell has an error system. Errors as values are a pattern enabled by other things of the language (ADTs, functors/monads), but not having errors is not a Haskell feature. Elm would be a better example of this.
> Even monads find themselves in other languages
See above point about first-class things in a language.
Your analysys about features is misguided because languages aren't things that can be compared feature by feature. They are a coherent set of things that produce a specific dev experience. Haskell is offering a specific experience that people enjoy, and therefore, people talk about it, and some other languages tend to adopt some of the features in an attempt to reproduce the experience.
This is a catch-22 of asking people to explain the advantages with simple side-by-side examples. If you want an example of something truly unique to Haskell, we'd have to talk about e.g. using the cataM function in a kind-polymorphic way. But you'd have to get a certain depth into the Haskell mentality before you could understand why that's useful or valuable. So we talk about the simple examples - but lots of languages solve those simple examples with limited/ad-hoc/special-case versions of things that Haskell does with more powerful general features. https://philipnilsson.github.io/Badness10k/escaping-hell-wit... has some examples of this phenomenon.
Apache Spark could probably never have been created without using the only other mainstream language with higher-kinded types (Scala) - now that the design has been proven a lot of it has been rewritten in a verbose Java style, but I doubt the work to come up with that design could have been done while thinking solely in Java.
Working without inheritance is only practical if you have typeclass derivation. Rust and Scala approximate this with macros. I don't think any other mainstream language has that functionality at all.
Non-nullable types and errors-as-values are only practical if you have higher-kinded types. (Rust has an ad-hoc macro that works for a handful of error-as-value types, but you can't write the general monadic library functions that you'd want to work with them properly).
Haskell is not entirely unique, but it's pretty close. The only other remotely mainstream language that has the combination of higher-kinded types and typeclass derivation (ish) is Scala, and even then you'll eventually get bitten by the lack of kind polymorphism.
This is one of haskell's biggest problems. It's just enough outside of the normal flow of imperative languages (yet usable for the same problems) that you can't tell how much of an improvement it is unless you try it. Also, people who write haskell are more inclined to share lofty/abstract/interesting-to-other-haskellers code rather than your normal day-to-day code that is massively improved/safer and benefited from haskell's features.
I've mentioned this before, but one example of where it became apparent to me how much haskell had changed what I expected from language was non-nullable types. It's starting to be really common in languages now (typescript, kotlin, etc), but if you are used to writing imperative languages, the worry of nil/None/null is ever present, and a concept like Optional<T> is actually quite foreign looking. If you really think about it, it means that none of your language is safe -- none of your functions are safe because they said they wanted a String but you might have gotten a null that looks like a String to the typechecker and will blow up at runtime.
Another key improvement in haskell is the removal of class-based code-sharing (i.e. inheritance) -- the separation of behavior and data is really important, and most languages are starting to come around to this now (go w/ structs + interfaces, java w/ data classes, kotlin w/ data classes, rust w/ structs + traits), but haskell (and other ML languages) have been there for a while.
Yet another key improvement in haskell is the errors-as-values paradigm that is everywhere. If some function has a possibility of failure, then it should return `Maybe TheThing` or `Either AnError TheThing` (see how nice and legible those types are?) -- this forces explicit checks on failure and allows cases where there isn't a chance of failure (just `TheThing`) to speed ahead without nullchecks and be fairly certain. This actually pressures you into trying to sequester failure across your codebase -- you try to write functions that have signatures like `TheThing -> SomeArgument -> OtherThing` (see how legibile that is?), to minimize on the amount of `Maybe x` or `Either error x` you have to deal with -- this is often if not always good for codebases.
Maybe this is something I can help with, I write about pedestrian haskell a bunch, and I've been meaning to do a blog post on why haskell is better <your language>, something to really rustle the jimmies.
BTW, the quote about resumable exceptions is actually referring to a concept called a monad, which can be incredibly hard to grasp if you don't look in the right places (there are a lot of bad tutorials out there), or don't give your brain long enough to marinate in the concepts. If I were to take a stab at explaining it simply, in this case it's like a combination of exceptions-as-values (i.e. not go's approach, and not java's approach) and the value that is being passed around has enough state in it to continue stop, fix itself, whatever else. When something goes wrong in most imperative languages, you kind of get the hell out of dodge, and you lose access (usually) to whatever work was done up until the function boundary -- it doesn't have to be this way but it usually is.