The description you give of "type-level hackery" and "monad wizardry" leads me to believe that you don't really understand what's going on. This is dangerous, because you are expecting these things to be complicated, but they are actually so simple that you've probably already invented them in your own computer programs. The Haskell community merely gives names to ideas you already know about.

This isn't "smart people showing off about how clever they can be", it's people giving names to things that they use everyday.

The problem people have with monads is that the word "Monad" does not describe something they're used to dealing with. A computer programmer is used to concrete things like lists, trees, functions, compilers; things you could hold in your hand (or at least draw on a whiteboard), poke at, and observe.

Monads can't be understood in this way, because they are not anything concrete; "Monad" is a word to describe concrete things that, individually, are all very different. It's like explaining the concept of "Green" to someone by showing them an apple, a tennis ball, and some grass clippings. They will never understand "Green", because you can't understand what Green is by seeing three examples of them. Green is a color, not some sort of weird tennis-ball-apple-grass-clipping hybrid.

Monads are the same way. List is a Monad. Reader is a Monad. State is a Monad. But when you look at these things and try to generalize, you might not see the parts that are the same, because the parts that are the same that make them all monads are hard to see.

I think the hard part is that you hear about what defines a monad ("return" and "bind" or "return", "fmap", and "join"), and don't see how some dual or triple like that helps you write computer programs. Each set of functions is so different from the other sets that the fact that they are all called "return" and "bind" doesn't seem to matter. And that's exactly right; at some level, it doesn't matter.

You use a List as a monad to combine computations that return multiple values, and you use Cont to write your programs in a continuation-passing style. Very different. Nothing similar at all, really. On the concrete level.

The similarity does matter at a higher level, though; noticing similarities means you can write parts of your program in advance, and just tweak those parts for each specific case.

An example is liftM2; it abstracts away the tedium of using a calling a two-arg function with monad-like semantics. If there were no similarities between List and Maybe, you'd have to write something like this:

liftList2 f xs ys = concat $ (\x ->
concat $ (\y -> [f x y])
`map` ys)
`map` xs

-- this doesn't compile; I want to use more descriptive words than the Data.Maybe API does :)
liftMaybe2 f x y = if has x then extract x -> \x'
if has y then extract y -> \y'
makeMaybe f x' y'
else Nothing
else Nothing

(Here's how to use the functions, in case you aren't sure:

liftList2 (,) [1,2] [3,4] ==> [(1,3),(1,4),(2,3),(2,4)]
liftList2 (,) [] [1..infinity] ==> []

liftMaybe2 (,) (Just 10) (Just 20) ==> Just (10, 20)
liftMaybe2 (,) Nothing (Just "oh hai") ==> Nothing

.)

These seem very similar. There is the same sort of nesting in both structures, we seem to be saying the same thing twice in both functions. We seem to be applying functions to values "inside" each of these things.

Maybe we can abstract those things away somehow, and make the two functions look a little more similar:

newList x = [x]
fmapList xs f = f `map` xs --
joinList xs = concat xs

liftList2 f xs ys = joinList $ fmapList xs $ \x ->
joinList $ fmapList ys $ \y ->
newList $ f x y
-- hey, the code looks nicer already!

newMaybe x = Just x
fmapMaybe Nothing _ = Nothing
fmapMaybe (Just x) f = Just $ f x
joinMaybe Nothing = Nothing
joinMaybe (Just x) = x

liftMaybe2 f x y = joinMaybe $ fmapMaybe x $ \x' ->
joinMaybe $ fmapMaybe y $ \y' ->
newMaybe $ f x' y'

Now that we've given List and Maybe some common terminology, it's very hard not to see how these lift2 functions are nearly identical. We just need to do some refactoring, and then we can use the same definition for both!

class Monad m where
new :: a -> m a
fmap :: m a -> (a -> b) -> m b
join :: m (m a) -> m a

instance Monad [] where -- haskell98 spells List as []
new = newList
fmap = fmapList
join = joinList

instance Monad Maybe where
new = newMaybe
fmap = fmapMaybe
join = joinMaybe

liftM2 f x y = join $ fmap x $ \x' ->
join $ fmap y $ \y' ->
new $ f x' y'

And there you go, that's what a Monad is. It's just refactoring, so we have less code to maintain! That's what any programmer would do, not just super-smart PhDs. You do the same thing every day!