You've got a lot of detailed answers, but I find the following quote conveys the feeling:
"Functional programming is like describing your problem to a
mathematician. Imperative programming is like giving instructions to an idiot." --- arcus, #scheme on Freenode (via http://community.schemewiki.org/?scheme-fortune-cookies)
You know how annoying it can be when you're trying to debug a function and it turns out it was acting weird because of a global variable? Functional programming is sort of like avoiding that sort of thing* , and then discovering that once you guarantee you won't mess with global state, it's suddenly possible to e.g. easily parallelize code, since you already know the processes won't interfere with each other's data. At the very least, it makes code much easier to test and debug, since there aren't "hidden variables".
* Maybe entirely, maybe just as a general rule -- long story.
When you have code that is "referentially transparent", that is, it doesn't have any side effects, there's also really no difference between doing something a thousand times or doing it once and caching the value, except the first wastes time. The language could potentially just cache it for you, or any of several other optimizations. (Or not waste time calculating results that it knows won't be used.)
Also, I'm of the opinion that when most people are talking about OOP, they're thinking OOP as implemented in C++ and/or Java. That's a very specific style of OOP, grafted somewhat uncomfortably onto C's type system. You may find OO makes more sense when you see how it works in Smalltalk, for example.
Alternatively, look at how it works in something like Lua, which is not in itself OO, but can easily have its core extended with any style of OO system by defining the language's hooks (called "metamethods") for handling table lookup and a few other things.
I agree that functional programming is very closely related to mathematics.
I think that for programmers who think mathematically, FP is a breath of fresh air, and to them it is obviously, intuitively, right. By "thinking mathematically", I mean people who think of all possible cases, and not just specific instances. For example, when you say "f(x) = x * x", you picture a curve that illustrates all cases. I think the mathematical mindset is to see all code in that way.
- Reliability: I personally don't have that mindset naturally - but what I find very valuable about it is the possibility of proving code. That is, not only does the code work, but you can be sure it will always work, for all valid inputs (i.e. that are in its domain, and its pre-conditions/assumptions are met). Reliability is one of the open problems of programming, and functional programming makes a contribution. I've also found that studying some FP languages has helped me understand some mathematics better, through familiarity (e.g. list comprehensions).
- Concurrency: FP is supposed to help with concurrency, because of immutability. There's a role here, but I have doubts: Erlang, the most promising candidate for concurrency, doesn't actually use this feature (its concurrency is by pure message passing, and it doesn't share any memory (even when it could) - this is for reliability: so that process death has no effect whatsoever on other processes). In other words, pure message passing (also used in webapps, and in SOA) seems to be the answer to concurrency, not FP (though FP can help).
- Hackability: There's also the great hackability of everything being an expression - you can put everything on one line, or divide it up however you like. I personally find this fun, but not (yet) useful. However, I imagine that in some cases, being able to divide up the problem exactly where you want to could be very useful - I just haven't yet encountered that myself (I'm very inexperienced in FP).
There's some functional programming going on in the Python community these days, and I'd suggest that as an accessible route. It's probably the least advanced FP, but that's what makes it accessible. I find the Lisp community is too advanced to bridge the gap to a newby if they lack the mathematical mindset. Haskell is more accessible, but there's still a big gap. I found Erlang more accessible still, but Python is the accessiblest - also because of its community.
It's difficult to do real FP in Python, though. Its lambda operator is crippled (one statement only), and it doesn't have tail call elimination* . Still, you can try out some functional ideas in it pretty easily, such as the map and filter functions.
* What tail-call elimination basically means is that when you call a function, but aren't waiting on its result as part of an expression at the current level (the difference between "1 + f(x, totalCount)" and "f(x, 1 + totalCount)", the language realizes that it doesn't need to keep a placeholder for doing stuff when that level is done on the stack. Therefore, calling a function in that manner can be used for (potentially infinite) looping without just piling more and more on the stack and eventually filling it up. If you try doing that in python, it will eventually blow the stack:
def houseOfCards(x):
if x == 0:
return "done"
else:
return houseOfCards(x - 1)
>>> houseOfCards(10)
'done'
>>> houseOfCards(100)
'done'
>>> houseOfCards(1000)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/tmp/py21945wPg", line 5, in houseOfCards
RuntimeError: maximum recursion depth exceeded
"Functional programming is like describing your problem to a mathematician. Imperative programming is like giving instructions to an idiot." --- arcus, #scheme on Freenode (via http://community.schemewiki.org/?scheme-fortune-cookies)
You know how annoying it can be when you're trying to debug a function and it turns out it was acting weird because of a global variable? Functional programming is sort of like avoiding that sort of thing* , and then discovering that once you guarantee you won't mess with global state, it's suddenly possible to e.g. easily parallelize code, since you already know the processes won't interfere with each other's data. At the very least, it makes code much easier to test and debug, since there aren't "hidden variables".
* Maybe entirely, maybe just as a general rule -- long story.
When you have code that is "referentially transparent", that is, it doesn't have any side effects, there's also really no difference between doing something a thousand times or doing it once and caching the value, except the first wastes time. The language could potentially just cache it for you, or any of several other optimizations. (Or not waste time calculating results that it knows won't be used.)
Also, I'm of the opinion that when most people are talking about OOP, they're thinking OOP as implemented in C++ and/or Java. That's a very specific style of OOP, grafted somewhat uncomfortably onto C's type system. You may find OO makes more sense when you see how it works in Smalltalk, for example.
Alternatively, look at how it works in something like Lua, which is not in itself OO, but can easily have its core extended with any style of OO system by defining the language's hooks (called "metamethods") for handling table lookup and a few other things.