Ask HN: Why Haskell?

53 points by csense ↗ HN
I was hating on Haskell the other day. A lot of HN'ers apparently love it, because my comment was downvoted.

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 ] thread
Short Answer: The Actor Model
Haskell is not about the actor model. It is possible to use it this way, but not what the language is about, or why you should learn it in particular.
Edit:

Before 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

Have you ever used any other statically typed languages? How much of Haskell's benefit do you think it provides that simply having static typing doesn't provide?
When you say "other statically typed languages" do you mean ML and Agda, or Java and C?
Any. (Maybe not something as simple C or Go, and nothing like Agda, but something like ML or Java or C++.) There is something wrong with the grandparent post, because it's not making an argument that distinguishes Haskell from other statically typed languages.
Ok, well, for what it's worth I think that Haskell typing is equivalent in strength to ML. It's absolutely stronger than pre-generics Java; possibly isomorphic now. Roughly isomorphic to C++ with templates. In Haskell and ML, inference and clean syntax for type constructors makes it substantially easier to apply that power legibly (and structural pattern matching is a nice bonus). Monads let you extend that power to statements instead of just expressions.
no, haskell (ghc) has many extensions that extend hindley-milner by a great degree.
Yes, I wasn't trying to make any strong claims of theoretical equivalence. In my experience with Haskell and OCaml and much more limited with SML, I've not found myself wanting to express a lot in those languages that I'd have been able to in Haskell, types-wise. Remember that the original question was "How much of Haskell's benefit do you think it provides that simply having static typing doesn't provide?" I should perhaps have been clearer.
- Haskell types often guide development: the error message ("type mismatch: expected blabla") gives you strong hints about what sort of error you've made. The magnitude of this one is hard to convey by merely writing/reading about it though.

- 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.

How do you do documentation in Haskell? For Python and Clojure, there's docstrings. For Java there's Javadoc. For Haskell, there doesn't seem to be any well-established documentation practices.

This is of course not counting the occasional comment explaining why/how you're doing something convoluted.

There is haddock and literate haskell. Types also serve as some documentation.
I have used other statically typed languages (I used to do C# .NET for a living), but Haskell was the first time I felt like I was getting my money's worth so to speak.
Perhaps three things:

* 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.

> 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.

I think immutability also helps on this front.

Just being statically typed helps, there's nothing particularly special about Haskell that makes QuickCheck possible.
Have you read the source to Haskell's QuickCheck?

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.

> It can automatically derive automatic randomized generation of your data-types FOR you.

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.

You're wrong and you should look into how typeclasses, derivation, SYB, and GHC generics work.

That they aren't doing it is reason enough. Clojure and Erlang's QuickCheck libraries would derive Arbitrary if they could. :)

I already know how they work. Still waiting for an explanation of what they can do that you couldn't do in Java or C# or (with some verbosity) C++.

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.

Let's use circles and rectangels to motivate some analysis. In Haskell you might define:

  data Point = Point { x :: Float, y :: Float }
    deriving(Generic)
  data Shape =
    Circle { center :: Point, radius :: Float }
    | Rectangle { topLeft :: Point, bottomRight :: Point }
      deriving(Generic)
In C# you could similarly define

  class Point {
    public float _x, _y;
    public Point(float x, float y) {
      _x = x;
      _y = y;
    }
  }

  interface Shape {}
  class Circle {
    public Point _center;
    public float _radius;
    public Circle(Point center, float radius) {
      _center = center
      _radius = radius
    }
  }
  // etc, for Rectangle ...
Let's start from the C# end of things. Let's say we want to do some tests on random Shapes and that we want to get an instance of a random shape by calling Generate.random<Shape>(). There are a couple of different ways to do this. We could explicitly define a RandomShapeFactory and a RandomPointFactory and then wire those up into the Generate class. Maybe we could use reflection to dynamically generate instances of Points and Shapes from their definitions at runtime. It would probably end up looking like some combination of the two.

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

> 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.

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.

Yep. The only really-real difference is compile time checking.
I'm interested in what you think of ScalaCheck[1]. It looks like at least someone is doing it.

[1] http://scalacheck.org/

Based on the documentation:

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.

Hmm, I think you could do something similar to QuickCheck using C# generics. The biggest problem you'd run into is probably the same as Haskell does (the expression problem). You can get some ideas from http://www.daimi.au.dk/~madst/ecoop04/index.html.

Oh and you could probably always fall back to reflection and attributes, but that's just kind of cheating isn't it? ;)

>Hmm, I think you could do something similar to QuickCheck using C# generics

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#.

No it's not. The feature C# calls reflection lets you inspect types and generate them automatically.
If we use reflection, we might know about error only in runtime. What haskell does is guaranteed to work at compile time. That's why it's so cool.
QuickCheck doesn't do anything interesting at compile time.
"What haskell does is guaranteed at compile time to work."
There are certainly cases which aren't expressible(things which require kinding, for example) but your examples aren't a problem. Both tuples and functions can be represented using System F, and thus can be completely transposed into C# generics.

I believe you don't understand C# generics and you should look closer at the fundamental type calculus.

> Python usually waits until runtime to throw exceptions, even for something obvious like a syntax error.

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.

*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? ....

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."

> why hate on a language you haven't studied

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!

I'm not the guy you're replying to, but:

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.]

it is quite well explained in pg' blub language text. The reason to learn haskell is twofold : make you a better programmer (functional programming breds good habits in any language and help you think differently about problems), learn something interesting (puzzle your brain for the pleasure of solving the puzzle).
Randomizing inputs is not "unit testing". Unit testing is deterministic.

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.

Short version: Most, if not all programming bugs are due to your code receiving something it did not expect. Haskell's type system helps eliminate this problem by forcing your code to declare exactly what it expects, and to handle all cases of said expectations.

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.

> Short version: Most, if not all programming bugs are due to your code receiving something it did not expect. Haskell's type system helps eliminate this problem by forcing your code to declare exactly what it expects, and to handle all cases of said expectations.

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.

Your comment misrepresents the reality for Haskell programmers.
If you wanted a useful reply you could explain how. The grandparent has things backwards -- it's possible for functions to force restrictions on their callers (by forcing them to pass a pure function as a parameter). The caller can't force the callee to consider all cases of its input.
Can you elaborate on this: "and to handle all cases..".
Look up "total functions"
Of course, not all Haskell functions are total...
I've always disliked the notion that Lisp or Haskell are only worth learning because of some revelation they give you which makes you more productive when you inevitably return to a mainstream language. Yes, there are some good insights that both languages give you, but the real argument for using them is that you can be more productive in those languages, and that it is reasonable to use them in a production environment for that reason. I don't think the "return to mainstream languages" is inevitable at all, and I am very happily using Haskell in production at my current job.

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.

> As it turns out, type systems have improved dramatically over the past decades.

These kinds of typesystems were available in ML since 1973. SO they are available to us for 41 years.

I'm general, yes. The particulars have been refined.
(comment deleted)
I think if the curve is too steep probably you are trying to learn too much at a time. Focusing on small and well defined problems will help you out at least initially. For instance I used Project Euler[1] to study some of the basics, because the tasks there are well constrained and you cannot do too much architecturally. You will learn the mechanics of writing small functions, defining types, and then you can move onto something bigger, where high level concepts are involved.

[1] https://projecteuler.net/

I don't think ProjectEuler is very helpful for learning a language. The first ten problems or so might be useful to learn the basic syntax, but after that it's really just about number theory, or optimizing brute force.

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/

exercism.io isn't bad either
Functional programming is where you end up if you ask the question, "how can I write better software more quickly with fewer bugs". I know of no shortcut to grokking why FP makes this possible other than just learning how to do it.
Cribbing someone else's answer to my similar question (a couple months ago):

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.

Here's a handful of things I really appreciate about Haskell, although some of this apply to other ML family languages as well:

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.

> 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).

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.

To be fair, if the place you're trying to debug is not evaluated (be it due to laziness or because it's generally dead code) that should be a pretty strong indicator something is wrong.
And generally a good clue as to what is wrong (or at least where to put the next couple checks...).
> 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.

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:

  def f(x,y):
      print x, y
      return x + y
Haskell:

  f x y = traceShow (x, y) (x + y)
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.

> 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.

An example of the visitor pattern:

    class LivePlayer;
    class DeadPlayer;

    class PlayerVisitor {
    public:
      virtual void VisitLive(LivePlayer *p) = 0;
      virtual void VisitDead(DeadPlayer *p) = 0;
    };

    class Player {
    public:
      virtual void Visit(PlayerVisitor *v) = 0;
      virtual ~Player() { }
    };

    class LivePlayer : public Player {
    public:
      void Visit(PlayerVisitor *v) { v->VisitLive(this); }
      LivePlayer(int hp, std::vector<InventoryItem> inventory)
        : hp_(hp), inventory_(inventory) { }
      int hp() const { return hp_; }
      const std::vector<InventoryItem> &inventory() const { return inventory_; }
    private:
      int hp_;
      std::vector<InventoryItem> inventory_;
    };

    class DeadPlayer : public Player {
    public:
      void Visit(PlayerVisitor *v) { v->VisitDead(this); }
      DeadPlayer() { }
    };
This is equivalent to the Haskell code

    data Player = LivePlayer Int (Vector InventoryItem) | DeadPlayer
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.

I think even the visitor pattern isn't required here, all you need is subclassing. Although this clutters things up more by having abstract methods in the base class, technically that's all you need.
One aspect that doesn't get mentioned much is recruiting from both sides.

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.

Hi. At your company and other Haskell teams that say they hire elite engineers, is an engineer's compensation higher than comparable non-FP shops?

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.

DK effect?

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.

A while back I was learning Haskell. I had a problem I wanted to poke at a bit, so I thought I'd give it a try. So I made various types in Haskell's nifty type system, which is NOT hard or tricky. I made some functions to work with the types. Easy. I worked all this stuff up and tried to compile, and being that I wrote a fair amount of code of course there were a couple of typos, etc. But then there was another part that wouldn't compile because I'd made an error, and the type system called me out on it. It's like getting the benefit of unit analysis (or dimensional analysis) from the compiler.

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.

The Monad tutorial fallacy is an in-joke in the Haskell community nowadays, born out of what you observed: when people feel like they understand monads, they are so happy about it that they write a bad tutorial about it.

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.

Haskell encourages you to think upfront about the shape of your data, because you have to design the types upfront and your constraints are checked at compile time. You can build a level of reliability and quality into your software that's hard to achieve (not impossible, but rare without great programmers) with a dynamically typed language.

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.

I like the balanced tone you employ here. Not a fanatic but reasonable about the analysis.
"[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."

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.

Haskell's biggest win is the power of its type system, including its ways of getting types places other languages simply can't.

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.

When haskell programmers say "functional" they mean "functions, like in math": yes, mathematics has higher-order functions (derivative, etc) but the main thing is a mathematical function doesn't fire missiles or mutate variables (what we call "side-effects"). Writing code that's like math is "declarative"; you describe what things are not what to do.

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've spent the last two months wishing I'd written the project I'm working on now in Haskell instead of C++ - the main reason? Lazy evaluation.

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.

>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

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.

> That's maybe 1% of functional programming, if that.

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."

>Yes, I have. I use functools.partial all the time

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.

How do you know why Haskell is superior to whatever you're using? Haskellers will tell you.

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.

(comment deleted)
Stateless programming is what the craze is all about. A lot of programmers grew up on C-style "x = 3;" kind of programming, so to write 500 good lines of code without an assignment operator can be a bit like walking down the street on your fingertips.

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. ;)