I was actually trying to think if there was ever a case where I had spent time learning a language (or library) that didn't pay some sort of dividend in terms of increasing my skill as a programmer.
I was actually trying to think if there was ever a case where I had spent time learning a language (or library) that didn't pay some sort of dividend in terms of increasing my skill as a programmer.
So you end up learning from the libraries written in those languages, not the languages themselves. It isn't a bad outcome, especially since learning a new language that's similar to something you already know becomes second nature and doesn't take too long. It takes me something like 2 weeks to learn a new language.
Haskell is on a whole new level though. I don't know Haskell, but in the last couple of months I've been building stuff with Scala and lots of functional programming related idioms, both for personal stuff and for our startup and I've learned more than I learned in the last 5 years. There's lots of good stuff to learn in the Scala ecosystem, like how to build rock solid pieces of functionality with referential transparency, how to work with Futures/Promise, how to do concurrency with shared-transactional memory, how to do non-blocking and safe I/O using Iteratees, how functional data-structures are designed and so on.
Of course, Scala is tainted by its hybrid and JVM-related nature. It's much too easy to cheat if it's easier than alternatives that are referentially transparent and even if your interface is pure, that interface is probably wrapping something which isn't pure. In practice it works great for building stuff, but unfortunately I'll have to take the plunge and learn Haskell to be able to take my knowledge to the next level.
One example where Scala falls short is in its implementation of lazy data structures. You see, even though Scala does have really good lazy data structures, it cheats here and there because it makes sense to do so ... like for instance a Stream is a lazy list and it does behave lazily, except when it doesn't. sort() on a lazy stream is actually implemented eagerly, which means algorithms using sort() will not have the complexity characteristics of lazy algorithms. There's nothing stopping one from implementing a lazy sort() in Scala, but it's probably not going to have any benefits so people don't do that. That's actually the reason why I want to learn Haskell, because the best implementations for lazy data-structures and algorithms are written in Haskell.
Many idioms that people use now in Scala or in other languages have been used in Haskell since years ago. Like Iteratees or type-classes.
Also lazy data structures are built into many mainstream programming languages. Python generators are the first example that comes to mind. I think you are probably excited about Haskell because it incorporates lazy evaluation which is a separate concept.
The big idea about Haskell isn't to avoid all state. Not at all. The big idea is to control state. Haskell expects you to use state if it makes the logic clearer or the code much faster.
What you do not do in Haskell is use state for program organization. You avoid global state as much as possible. This means that communication between disparate parts of your program are explicit--you don't have implicit dependencies everywhere. This is definitely a good thing.
I think one of the problems is that people without much experience in Haskell interpreter "purely functional" too strictly. It just means you do not have state by default; you can still use it where it makes sense.
The good thing about not having it by default is that it forces your program to be simpler and more decoupled and it lets the compiler and libraries make many more assumptions about your code. All sorts of awesome features like STM and data parallel Haskell depend on having this sort of control over side-effects. But that's all it is--control. You can still have state and side effects; however, unlike most other languages, you can also not have them.
The only problem is that many times you need mutable references to immutable data-structures, with no easy way out and this is where you can still have problems with side-effects and multi-threading. But dealing with such references is easier, because (a) you tend to do it less and less often and (b) you can use STM or just plain atomic references or @volatiles, as in many instances you need to just ensure the visibility of updates and you only really need locks only on writes. Also, I tend to encapsulate and hide really well pieces of code that deal with mutability.
A good standard library of immutable data-structures is very practical for day to day use. I wasn't talking about immutable data-structures though, as the data-structures themselves are only a small part of the puzzle. Much more difficult is to deal with inter-modules communications, processing streams of data, I/O and stuff like that. And that's also where things break and where I feel the urge to look for better techniques.
On lazy data structures, Python is an awful example.
Sorry, I started fantasizing about Assembly again, my actual question was: "If you could find a metaphor that describes how Haskell works with Data, what'd be that metaphor?"
You might want to think a little bit about the fact that you are predisposed to believing that your efforts are not wasted.
If you spend the time to learn a new programming language, you're then predisposed to believing that it wasn't a waste of time, and are thus susceptible to confirmation bias, which causes you to ignore all evidence to the contrary.