The purpose of this page is not to provide an introduction to Haskell or any sort of comprehensive tutorial, although you will find such things in the links section below. I've written this to try to clarify some of the insights that I've had about Haskell to hopefully save others a bit of time and frustration.
This started with the state monad, which I found confusing at first because I was looking at it the wrong way (the imperative way), and I felt frustrated because I couldn't find anything that seemed to explain it clearly. Many of the explanations that I've found only deal with it superficially and it seems that many people who use it don't care about what's going under the surface. When I finally went through it, step by step, it did become clear and I thought someone might appreciate it if I posted the steps to walk them through it too.
I'll expand this page and maybe split it up if I think of anything else to add.
Wrapping your head around Haskell when coming from imperative languages can be a challenge. Functional programming may require a very different way of thinking compared to what you're used to. To complicate things even further, many concepts in Haskell may appear simpler than they are and it is easy to fall into the trap of thinking you've understood something only to later realize that there was a depth to the concept that you failed to grasp the first time around.
Keep this in mind when reading what follows.
This page touches on this and there are plenty of tutorials around that explain partial application and currying, but the ones I've seen don't really emphasize what I consider to be the main point. Consider the following, with special attention to the type, where I've added the parentheses
add :: Int -> (Int -> Int) add x y = x + y
It looks like a function that takes two arguments and returns their sum, but it really isn't. It's a function that takes one argument and returns another function that itself takes one argument. The type is really
Int -> x, where
Int -> Int. This might seem like a trivial distinction but it really isn't.
Let's look at the example of partial application:
add :: Int -> (Int -> Int) add x y = x + y addOne :: Int -> Int addOne = add 1 z = addOne 4 -- `z` is now 5 because `addOne` has added 1 to 4
At this point you're probably familiar with currying so the definition of addOne seems natural. The insight lies in the realization that the currying occurs even when we do this:
z = add 1 4
add does not receive 2 arguments. Instead it receives one and then it sends a different function back to consume the next argument. It's as though someone went in and replaced
add 1 with the following in the code itself:
z = (\x -> 1 + x) 4
This is very different than the way we handle functions in imperative languages (at least the ones that I've seen) and understanding this aspect of Haskell makes higher-order functions much clearer.
The very nature of functional programming is to describe what is as opposed to an imperative language that describes how to do something. When we declare something like
z = 5, we are not assigning the value of 5 to the variable z as we would in an imperative programming language. We are saying that z IS 5, and thus z can be substituted for 5 in any context. They are just different names for the same thing.
You can indeed describe how to do things in a functional language too, but that's what monads are for (e.g. IO). Of course, when you run your functional program, the computer is doing something to evaluate your code, including the values of pure functions, but how the computer does that is not part of the language itself (there are some exceptions, such as seq and par, but generally speaking you describe what something IS with your pure functions and leave it to GHC to figure out how to actually calculate everything on your hardware).
Once you stop thinking in terms of assignments and start thinking in terms of definitions, things will begin to make more sense.