Ask HN: Why Haskell?
One of the replies said:
"I've seen experienced programmers failing at learning Haskell...the initial learning curve is very steep, and they don't see the value on climbing that wall...with Haskell, all those seemingly complex concepts come with a big reward." [1] [2]
My question is: I currently don't see the value proposition of learning Haskell. What is this great enlightenment I'm supposed to receive from studying Haskell? Can someone give me the TLDR version?
PS: If you say "functional programming", please elaborate. I know all about function pointers in C, and I've read the docs of Python's functools module [3].
[1] https://news.ycombinator.com/item?id=7478274
[2] I don't want this thread to degenerate into a favorite editor flamewar the way the other one did, so I'm leaving out the commenter's clearly preposterous claim that learning vim is worth your time.
[3] http://docs.python.org/3/library/functools.html
83 comments
[ 7.8 ms ] story [ 174 ms ] threadBefore Haskell, I used Common Lisp, Python, and Clojure. I was impatient, generally more interested in hacking than math, and was happy using untyped programming languages.
Haskell changes the equation, it (alone, for now) provides even better leverage and expressiveness than untyped languages like Clojure, Python, or Ruby. Further, it does so in a way that is more powerful and reliable because you have the leverage of the type system behind you.
Learning to think about code in terms of types is a transformative experience.
Types is where the future is headed.
I've been getting people started with Haskell for the last ~3-4 months. It's not that hard to learn at all if you use proper learning material and do the exercises.
Don't waste any time, just learn Haskell and find out for yourself.
My guide for learning Haskell:
https://gist.github.com/bitemyapp/8739525
- Easy wrapping to create new types: Ever switched the arguments of `mv` in your terminal (strings)? Read the User ID as an account balance (ints)? You can distinguish these statically, and it's easy to do so. (You can of course write a wrapper class in C++ around Int as well a la "class OtherInt { int value; int get(); set(int i) }", but how often do you do that?
- No IO in a type? It cannot do IO. Has "Monad" in the type? It can do Monadic stuff, but no operations specific to any Monad instance (such as Maybe, IO, ...).
- Types are forced documentation. Don't get me wrong, no documentation is as awful in Haskell as it is anywhere else. However, even without any ordinary comment, a type can give away a lot of what a function does; maybe even more importantly, it tells you what it cannot do. If you see something of type `(a -> b) -> (a -> a)`, you know that the first argument cannot possibly be used - it has to be a dummy parameter.
This is of course not counting the occasional comment explaining why/how you're doing something convoluted.
Oh yes but there is! It's called Haddock. It's widely (and probably exclusively) used. The entirety of the documetation you can find on Hackage [1] (example: [2]) is generated using it. The user guide can be found here [3].
[1]: http://hackage.haskell.org/ [2]: http://hackage.haskell.org/package/pipes-4.1.0/docs/Pipes-Pr... [3]: http://www.haskell.org/haddock/doc/html/
* Haskell is (very) strongly and statically typed and Python isn't. Haskell's type system can prove the correctness of many functions at compile time. In contrast, Python usually waits until runtime to throw exceptions, even for something obvious like a syntax error.
* Because the types are known, there's an incredibly powerful testing framework called QuickCheck that is capable of e.g. generating tens of thousands of valid inputs and seeing if it can automatically disprove your assertions about your code. Python's typing is insufficient for this.
* Qualitatively, writing Python feels like writing instructions for computing something, and writing Haskell feels like simply describing what something is, and the system works out how to compute it for you. It often feels compelling and magical.
Lastly, some tone stuff: Function pointers have nothing to do with functional programming. And why hate on a language you haven't studied? And why does there have to be a TLDR version of a mind-expanding experience like learning a new programming style? Why form a strong opinion about something you don't know in the first place, and why feel entitled to challenge someone to explain it all to you? It's pretty frustrating.
I think immutability also helps on this front.
It can automatically derive automatic randomized generation of your data-types FOR you. No other generative testing library (such as for Clojure or Erlang) can do this.
Further, the random data generation can be customized using the functional combinator API.
Haskell QuickCheck's source code is a compelling example of what can be done with static types and typeclasses in Haskell.
There's nothing about Haskell (or some extension to Haskell, I presume) that makes this possible that wouldn't make this possible in other languages like C#, Java, or, with a bit more verbosity, C++. If I'm wrong, please say what it is, I'd love to know.
That they aren't doing it is reason enough. Clojure and Erlang's QuickCheck libraries would derive Arbitrary if they could. :)
Edit: For example, to answer this question, you could define a type and explain how Haskell lets you automatically generate arbitrary instances of that type, where Java or C#, being as limited as you claim them to be, would be incapable of doing so. Edit: Also, Clojure and Erlang are irrelevant. My comment you originally replied to said that static typing was what makes QuickCheck possible.
Haskell doesn't let you magically generate random instances of Shape. The cool thing it gives is the "deriving(Generic)" part. Effectively, this causes the compiler to generate a description of Point and the Shape-Circle-Rectangle set of types. In many ways, it's like C#'s reflection. The QuickCheck library uses this reflection-like definition to generate random instances.
The core functionality isn't very different, but Haskell's approach has a number of advantages. Most importantly, it's type-checked at compile-time. In the C# version, I could call Generate.random<Cat> and the code would blow up at run-time, whereas we would just get a compiler error in Haskell. The other major advantage in this case is the reduction of boiler-plate. There's no need to register factory instances inside of some singleton object at startup, the type system can infer which Generic components are needed where.
C# and Haskell are both turning complete, so you can do anything in either language. Haskell makes certain types of programming more enjoyable by reducing boiler-plate and preventing annoying runtime errors.
EDIT - code formatting
You would only need to do that if you wanted to define custom behavior, instead of automatically derived behavior, and registering a few delegates is the same amount of work as manually implementing an instance of Arbitrary and CoArbitrary.
[1] http://scalacheck.org/
https://github.com/rickynils/scalacheck/wiki/User-Guide#gene...
It cannot derive random generation behavior (Arbitrary typeclass in Haskell) for arbitrary data types. You have to write the generators yourself using the Generator class.
Which goes back to my point about generics, typeclass instance derivation, and the boilerplate using Haskell kills dead.
Oh and you could probably always fall back to reflection and attributes, but that's just kind of cheating isn't it? ;)
You can't do this. Quick check can generate type classes instances, for example, for tuples, functions, etc. This is impossible in language like C#.
I believe you don't understand C# generics and you should look closer at the fundamental type calculus.
Just a nitpick, but that is not true. Well, not completely. Python combiles its code on runtime, and at that part SyntaxError exceptions are thrown. No need to actually reach the code, since the bytecode compiler traverses the whole module anyway.
I'm loving Haskell and am using it for all sorts of things now. I think this confusion deserves to be addressed though. When people first spoke to me about functional programming, they would say things like: "It's a paradigm shift because everything is based on functions and functions are first class citizens, you can pass them around just like anything else"
Unfortunately this description of functional programming only makes sense to functional programmers. As a C programmer (what I consider myself primarily) I was inclined to simply say: "So what? Functions are perfectly easy to use in C and I can also pass them around provided I'm willing to stomach a little indirection, why do I need the unfamiliar syntax of Haskell to do something I can already accomplish in C?"
So I can see where the function pointer remark is coming from. It might represent a misunderstanding of functional programming, but it's a misunderstanding potentially created by uncareful exposition. In my case I was thinking exactly the same thing before I started to understand the value of FP, and having approached FP many times in the past and failed to understand the point of it, could easily lead to an attitude of: "This is needlessly complicated, time to give up."
I've studied it well enough to understand its drawbacks. I don't understand Haskell's benefits very clearly, but I'm skeptical that the benefits of Haskell outweigh its drawbacks in the context it was being discussed.
> "So what? Functions are perfectly easy to use in C and I can also pass them around provided I'm willing to stomach a little indirection, why do I need the unfamiliar syntax of Haskell to do something I can already accomplish in C?" So I can see where the function pointer remark is coming from. It might represent a misunderstanding of functional programming, but it's a misunderstanding
It sounds like you understand exactly the mindset I have and the question I'm trying to ask. But you don't answer the question -- and neither does anybody else in this thread, even though obtaining an answer to this question was supposed to be the point of the original posting.
I suppose rephrasing it should be: "What does Haskell accomplish with functions that passing around function pointers in C does not?"
Your answer is, "Something totally different that I did not understand before I knew Haskell, and which I understood after I knew Haskell." This does not answer the question!
It's going to take me months of my spare time to really get Haskell. I've got a lot going on in my life; I've got plenty of other uses for that time. If I'm going to use that time to learn Haskell, I want to know that the payoff is going to be there.
And saying "once you learn it, you will become enlightened", as if Haskell were Zen Buddhism and the benefits cannot be rationally discussed, well, that's not a very useful reply.
On the other hand, trying to explain to people the benefits of something they don't understand is really hard, because they don't understand it well enough to understand the benefits.
I think a big part of the OP's attitude might be in response to the attitude of a number of Haskell adherents: Our language is the greatest, and if you don't get why, it's because you're ignorant. If you've looked into Haskell and still don't get it, then you must not be bright enough to see the greatness of Haskell.
I exaggerate, but only a little. That kind of attitude makes the non-Haskell people resent the Haskell crowd, even if it's only a minority of the Haskell crowd that reeks of arrogance.
[Edit: I should state more strongly that the arrogant types are by far the minority, especially on HN.]
Similarly, type checking does not eliminate the need for unit testing.
What worries me is that between type checking and thinking QuickCheck is some kind of magical logic checker (it is not), Haskell people will generally be more discouraged from writing good, valid unit tests, vs. for example Ruby people.
We don't want this endgame.
In other words, it's doesn't let you skip out by not thinking about the entirety of your code. It's the same line of benefits that TDD gives you, but TDD is, IMO, a stepping stone towards the pure, functional programming that Haskell provides.
Note that I am by no means a Haskell programmer. To me, Haskell is the "last level" in learning development. I hope to one day reach such a level.
No it doesn't (force). It's about as good as C++ on this front, with better error messages. What it does do is make it a lot easier to program in a style that handles all cases, but without forcing you to do so. You can pattern match on something instead of making a visitor. But you can still call head or tail.
With that out of the way, what are the benefits? There are quite a few. The following is not a complete list, but I'll try to hit the main points.
First, type safety. This is the primary argument for Haskell over something like Common Lisp or Clojure, and it is hands-down the strongest reason I choose to use Haskell. I used to hate static typing, because I thought static typing meant writing a bunch of boilerplate that didn't really get you anything and frequently got in your way. In Haskell this is not at all the case. As it turns out, type systems have improved dramatically over the past decades. In Haskell we have inference, so no boilerplate, and we have incredibly powerful facilities for polymorphism, so the type system provides real benefits. Being able to derive instances of type classes actually drastically decreases the amount of necessary boilerplate, even compared to dynamic languages. There's a common saying in the Haskell community that if it compiles, it probably does what you want it to. This has been my experience, and it is incredibly pleasant.
Second, rigorous isolation of side effects. Countless programmers have independently realized how good this is over the course of their careers. Haskell enforces it. Knowing that a given pure function can't possibly break something in another distant part of the application is wonderful, and completely prevents entire classes of bugs.
Third, the community. Haskell's community is often made fun of as being overly academic or too distant from the realities of programming, but in my experience the community's love of abstract mathematics is a massive benefit. One easy example is Edward Kmett's lens library. It is unabashedly abstract in its origins, and parts of it are still a little over my head. Despite that, it is perfectly usable without knowing the math behind it, and it is actually extremely useful in day-to-day programming. Things like functors, monoids, and monads are often poked fun at, but they are just way too useful for me to care. In this sense, the community is a huge asset, because people keep churning out these useful tools that you just won't see in other languages.
Fourth, conciseness really is power. I often find myself writing one liners that would require a dozen or more lines in the other languages I've worked in. This frees me up to focus on the business logic, which is what actually matters.
Parallelism is also a huge selling point, but I'll let someone else cover that because I don't have a ton of on-the-ground experience with it.
Honestly, when I have to sum up in one sentence why I love using Haskell, it's because it makes my job easier. At the end of the day, it's really that simple.
These kinds of typesystems were available in ML since 1973. SO they are available to us for 41 years.
[1] https://projecteuler.net/
If you're looking for Haskell beginner exercises, I recommend
a) implementing functions from Prelude, Data.List, Data.Tree yourself
b) doing the exercises given in RWH (even if you don't actually read the book - just do those you understand).
RWH: http://book.realworldhaskell.org/read/
Pure FP means no side effects. Side effects cause your state space to explode. (That is, no side effects means that the results of this function depend on its inputs only. Side effects means that the results of this function depend on its inputs, member variables of the object, file variables, global variables, the state of various things in the OS, user input read during the function, data read from the network, and maybe a few other things. It's much harder to understand such a function, get it working correctly, debug it, and prove that it correctly handles all corner cases.)
The size of the program state space is your enemy, not your friend. Control it or die. FP dramatically helps you control it.
1.) Algebraic Data Types let you enforce a huge amount of correctness in your programs simply by how you define your types.
For example, say you have a data type that represents a player's state in a game. If the player is alive, he has a "HP" and "inventory" record. If the player is dead, he should just be dead—HP and inventory may or may not be set to 0 and None, respectively. In C or OOP languages, you just get a player object/struct and you have to enforce this state logic manually, and you can easily write a function that puts the player in an inconsistent state (player.alive = False, but player.HP = 15). In Haskell, you can define the player such that code that produces this kind of inconsistent state will not compile.
Of course, you should still write tests in Haskell, but it's amazing to me that the type system can catch the vast majority of programming errors before your program even compiles. It takes longer to get something up and running, but you make up for this up front cost with long term gains in maintainability.
2.) Functional purity enforces two very useful properties:
a.) Data is never mutated by accident. This ensures you never have to look beyond the scope of the function you're working on (or closing over, in some cases) to know what's going on. You can then compose these functions to create more complex programs.
b.) When you do need mutability and side effects to happen, Haskell forces you to be very explicit about this and enforces a strict separation between "pure" and "effectual" code. This seems arbitrary until you realize that almost all of your errors not caught by the compiler happen because of IO. Whereas you have complete control over the pure part of your program, IO is nondeterministic and full of exceptions and edge cases. Separating this part of your code makes it far easier to reason about what's happening, adding to maintainability.
As a side note on the last point: people make the argument that this separation makes Haskell impossible to do printf debugging. Not true—the standard library provides ways of circumventing the type system specifically for this reason.
3.) "Monads" and other fancy sounding abstractions can be hard, but with practice they become as intuitive as inheritance in OOP. The real barrier to entry is the awful, awful documentation everywhere. I make no apologies for that: it's just really shitty and the community needs to do a hundred times better.
False. In OOP you can do this using the visitor pattern. The difference is that Haskell makes this real convenient, with its algebraic types feature baked into the language, while OOP makes it very cumbersome.
> As a side note on the last point: people make the argument that this separation makes Haskell impossible to do printf debugging. Not true—the standard library provides ways of circumventing the type system specifically for this reason.
It's still far more annoying when the language is lazily evaluated and has Haskell syntax.
I don't think we're on the same page here. I meant to say that Haskell protects against inconsistent states: you literally cannot create a dead player with HP and Inventory records. I don't have a strong grasp on the visitor pattern, but I'm not sure how it can be used to accomplish compile time guarantees that the properties of an object will always be correct.
> It's still far more annoying when the language is lazily evaluated and has Haskell syntax.
Python:
Haskell: Laziness is a separate issue altogether. Debugging lazy behavior is difficult in any language, and you would have similar problems in Python if you were testing deeply nested generators.That said, laziness should not be an issue if you are simply testing that functions are correct, since most functions are pure and you will almost never use lazy IO.
If you need to debug evaluation order or a memory leak, that's much harder, and admitted one of Haskell's biggest weaknesses. My answer here is that you should almost always prefer strict data structures and functions unless you really need laziness. That's not a cure all, but it is a good policy when programming in Haskell.
An example of the visitor pattern:
This is equivalent to the Haskell code including the fact that in this example the objects are immutable.Instead of using a case expression to pattern match over the player, you'd have to construct a PlayerVisitor and implement the visit methods on that type.
Things can be made a bit less cumbersome than that, for example in a language with lambdas, you can just have the Visit method take a lambda for each subclass, and each subclass's implementation calls one. That makes it arguably equally convenient to use the types (with some extra parentheses), but it's still much more annoying to define the types that way in the first place.
As an engineer, if you land a job coding haskell you're pretty much guaranteed to be an environment thats extremely engineering-centric, has minimal technical debt, and is staffed by lots of smart people. usedox.com, alpha heavy, jane street (ocaml but close enough), silk, tsuru, fobo are all exemplary examples of this. anyone of them is an amazing environment and you'll learn a ton. (the first one is my own company, so consider that one with a grain of salt)
As an employer of engineers, if you put out a call for haskellers, you'll get a far smaller applicant pool than say ruby, python, java, or c#. But the signal to noise ratio is incomparably higher. Very few mediocre engineers will claim haskell as a skill.
Please forgive the bluntness; as a guy who is about halfway down "the road to Haskell", it seems like none of the shops that say they value FP are willing to pay more for it. For me, it's been easier to extract money from a javashop that's starved for talent and just figure out a way to install FP there. DK effect might also be clouding my self valuation.
I can only speak to my current company (dox) and Jane Street, where I worked a long time ago.
At JSC, comp was above market and everyone was quite happy but they've quadrupled in size since my time so YMMV.
As Dox, we are currently bootstrapping off of a small round and everyone is working well below market in terms of cash but with commensurately higher equity. we will be raising a round later this spring and intend to roughly match or exceed comparable shops when that round closes. we've been completely transparent about that to the entire team as they've all taken pay cuts to work with us a this early stage, something we respect and are extremely appreciative of.
If immediate salary is a principle concern, probably best to look at JSC, Stan Chart, or Barcap rather than a place like us or one of the other startups.
http://en.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect
That's one thing to like. A lot. There are other things. Polymorphism/overloading through type classes is so easy and clean it's hard not to love. The list goes on and on.
For my part, I got hung up a bit on using monads. This was a few years ago, so maybe there are better articles/explanations now, but at the time there were a zillion monad tutorials, all of them written by people who almost but not quite "got it". I need to try Haskell again.
My advice: Monad is a typeclass, and typeclasses unify things that act alike. Learn Monad instances (List, Maybe, State, ...) separately without worrying about how they are "monadic". After some time you will develop an intuition for what do, <-, return, >>= etc. have in common. And that's what Monad is.
There's a certain imprecision that you see with mediocre programmers. They don't know how things work, and get used to fooling around with a certain squishiness that's tolerated on large object-oriented projects as long as there's one good programmer ("architect") in the batch to clean up messes. That's good enough to get by in the business world, but not if you really want to understand what you're doing and call yourself a true programmer.
Haskell forces you to build from the ground up, and a nice consequence of this is that, for any type, you can inspect its pieces and get down to the bottom. Functions will have easy-to-find type signatures and, sometimes, you can get a good sense of what the function does from its signature.
In fact, in Haskell, the type system can be used to make assertions about functions like "this function doesn't have any side effects" and it's trivial to do so.
In the long term, Haskell's heavy use of immutable data structures confers some major performance advantages as well.
Is it the right language for every purpose? No, but it's very good for many, and it's a source of great ideas that have percolated into other languages, like Clojure and Rust. Additionally, it will force you to think rigorously about your data and what is happening to it.
Asserting that a "claim that learning vim is worth your time" is "clearly preposterous" is not exactly the straight and true path through the valley of flamewars.
In C and Python, "a statement" is a different kind of thing than "an expression". Statements don't "have types" - the expression that makes it up may, but the value winds up discarded and the type doesn't matter (except maybe to produce a "unhandled value" warning, if you've set things up for that).
In Haskell, statements are just another kind of expression, and the types of those expressions determine how they get chained together. This means I can leverage the type system to help me avoid new kinds of errors - want to be sure you don't try to do I/O in an STM transaction? Or access that variable from the wrong thread? Or... In Haskell, that's straightforward. In C, that's not something you can express and you have to check it all by hand.
I wrote a blogpost awhile ago on the benefits of such a typing system, it's kinda long but you might find it interesting:
http://bsamuels.net/blog/2013/11/20/StaticTyping.html
Others have given lots of good views on the various upsides to using haskell, especially the ways in which the type system can catch bugs for you. But my favorite thing about the type system is it lets you create good abstractions. For example it's possible (and often quite easy) to create a library which cannot be used incorrectly. And because you finally have abstractions which are good (not brittle or leaky) you can build on them to create larger abstractions (by "abstraction" here, think: conceptual unit that is easy to reason about). And that's also one of the challenges with learning haskell: there's more to learn because we can create new abstractions that are worth learning.
I like talking about haskell, but certainly won't presume to tell you you should learn it. But if you want to learn and are put off by what you've seen of it on HN for whatever reason, I can tell you that the community is incredibly generous and full of very kind and smart folks (though some humility and an open mind, as always, will get you far). The community is why I actually have something of a career in software today.
I'm working on developing a new algorithm, which is most easily defined as a set of constant functions over my data structure (a DAG with about a dozen node types). There's a fair number of places where I want shortcuts to not calculate things if they're not needed, and with Haskell's lazy evaluation I'd be able to just let the compiler handle most of those (the pattern matching constructs would make my functions-by-cases cleaner to define too).
I'm doing some funny stuff with memo tables indexed by object pointers that would be trickier to do in Haskell, but I think there's likely a library I could leverage.
That's maybe 1% of functional programming, if that. And from how you write about it, it seems like you haven't even tried to dabble with that (I mean, merely "read the docs of the functools module"?).
But, if I may, I think the issue here is not about Haskell. Judging from the Haskell query, and what you write about learning Vim, I think you have a general aversion to anything that doesn't have an instant payoff and/or isn't trivial.
Of course you can go on not knowing Haskell or Vim and 100 other things. Many do, and don't lose much, because there are other things.
But if you go on avoiding anything that needs hard work and only offers a future pay-off, that is, anything with a learning curve, it will certainly hold you back. That is, if your ambition is to be a very good programmer (which might not be).
If, on the other hand, you just want to have fun, and are content with easier stuff, there's really no reason to learn Haskell or anything hard for that matter.
I was asking where I can find the other 99%.
> And from how you write about it, it seems like you haven't even tried to dabble with that
Yes, I have. I use functools.partial all the time, and I've written programs that use function pointers in C before. I'm just trying to figure out whether "functional programming" means "passing functions around functions as objects and the sorts of hacks you can do with args/kwargs in Python," or if there's something more than that. For all the replies I've gotten in this thread, the best answer I've gotten is "it's all of the above plus immutable state."
Well, "I read the docs" doesn't convey that. And "partial" is just, well, a very small part of it.
>and I've written programs that use function pointers in C before. I'm just trying to figure out whether "functional programming" means "passing functions around functions as objects and the sorts of hacks you can do with args/kwargs in Python," or if there's something more than that. For all the replies I've gotten in this thread, the best answer I've gotten is "it's all of the above plus immutable state."
Only very superficially. Passing function pointers in C is just dipping your toes in functional programming -- if that. Let's say that those are the tip of the iceberg -- and that the whole idea lies in the interplay of functional concepts (much further than what people usually do in C, unless they implement their own ad-hoc Lisp in it).
It seems like what you picked as "the best answer" from this thread is the one that reinforced a preconceived opinion, without having to actually try programming in a functional language.
How about this reply you got above: "Lastly, some tone stuff: Function pointers have nothing to do with functional programming." -- and others like it.
It should be permitted not to think Haskell is the best thing ever, but in practice downvoting is heavily used for certain disagreements of opinion and this is one of them.
There is nothing special about Haskell. Every single one of its qualities have been / will be reimplemented in other languages. Do not stare at the tree that is Haskell: talk about the forest that is PL research / features.
For example, Shen is a great mindbender close in "power" to Haskell. It's a Lisp, it has an [inferential] static type checker, and only 2 functions in Klambda aren't referentially transparent.
Personally I dislike Haskell's syntax and think Worse is Better, anyways. ;)