One really cool thing is that you do not you do not need to stick to lists. Many of these functions naturally generalize to other data types.

The simplest is map. It turns out that there are a ton of types that you can map a function over, and many of them are extremely useful. There are obvious examples like trees and arrays, but they don't even have to be containers. In Haskell, we call any type with a map function that behaves like the standard list function a functor. This is all a functor is--pretty simple.

The next interesting example is fold. We can also fold over non-list structures. In this case, the structures are usually containers like trees and arrays.In Haskell, this is what the Foldable class represents.

You can also think of zipping--in a very abstract sense of applying a multi-argument function to a bunch of structures--as generalized by applicative functors. However, lists by default form an applicative functor with pairwise application rather than zipping; there is a wrapper called ZipList that does a normal zip. Applicative functors also come up very often, not just for container types.

All these generalizations let you take all the powerful list functions you're familiar with and use them in new contexts. They also let you write more polymorphic code.

Interestingly, you can also generalize these functions in a different way--instead of taking a different structure, take a different function. This is where monads come in: you can generalize these functions over monads. In Haskell, the generalized versions are named by a trailing M: foldM, mapM and so on.

At a high level, this lets you customize how functions get applied. So you can have a fold that maintains internal state, propagates errors or even computes the result non-deterministically. The exact behavior is specified very declaratively by your choice of monad.

And, of course, as with any good abstractions, you can combine these. Particularly, you can take a foldable and use a bunch of the monadic functions on it. So you might end up using a non-deterministic fold on a tree. Just by itself, this seems exotic and hard to grasp. However, since it was built up step-by-step from familiar abstractions, it's actually easier to understand. Each step along the way is relatively simple and intuitive; you just have to put them together.

Now, what are the benefits to all this? The main one is that it gives your code more structure. A map a fold always behaves the same way for a given type where a for-loop could be doing anything. This makes it easier to read because you know the structure of the computation just from its name. It also makes the code easier to write since all the iteration logic is hidden away; you only have to write the function dealing with one "step"which means you cannot accidentally mess up how the steps are combined (no more off-by-one errors) and you can't accidentally couple the computation itself with the iteration logic. They're guaranteed to be separate because one is encapsulated into the higher-order function.

In many ways this parallels the transition from goto to structured programming.

Another advantage is that it lets you write more generic, more polymorphic code. This sort of code is easier to write because the types are more restrictive. There are simply less ways to mess up. Also, naturally, the code is more general, letting you reuse it in more places.