Functional purity makes I/O a major mess. Monads are complex, unintuitive and unwieldy. I think I spent over one month only trying to warp my mind around that. It does not help that Haskellers keep repeating that "monads are really simple", there is a reason why they are the most asked-about topic in newsgroups.
I'm suspecting that's because they really are quite simple. You can think of monads in general as a way to formally define types of computation that may have a context in which they operate. There're error monads like Maybe, where the computation may fail at a step, aborting the rest of the computation. State monads like State, where the computation has access to implicit state. Non-determinism in List, which computes all possible results. And IO, which is a bit like a state monad where your state is the whole of the rest of the world.
Try reading Real World Haskell? The text is available online.
The worst thing is the Haskell community's coding standards. Single-letter variables are common, and I actually read some delirious rant about this being necessary "because it's so abstract you cannot name it". If it's so abstract you cannot name it, you abstracted too much, or you don't understand what you are doing. There seems to be a proliferation of operators, since Haskell foolishly allows to define new ones, even completely useless ones like $. Coding function with undocumented one-liners seems to be considered a virtue.
OK. Let's take the simple example of map:
-- | Gives the list obtained by applying the given function to each element of the given list.
map :: (a -> b) -> [a] -> [b]
map f (e:es) = f e : map f es
map _ [] = []
What longer variable names would you use there and would those really make it more understandable?
Presence of one-letter variable names generally indicates that the function doesn't care about the internal details of the value in that variable. That could be due to either the function being polymoprhic (where it can't access the internals) or operating on simple types like numbers (where there are no internal details to care about).
$ is occasionally handy when creating a partial application. For example:
fs :: [a -> b]
v :: a
map ($ v) fs
That's in addition to cutting out extra parens when you're applying multiple functions:
f1 (f2 (f3 (f4 v)))
vs.
f1 $ f2 $ f3 $ f4 v
One of the places where custom operators are very usefull is with combinator libraries. Making the most commonly used low-level combinators operators helps keep the code using the combinators reasonably short and thus more readable than if they were bloodyLongNamesFullySpelledOut.