We all know there's something to "get" -- but is it one massive "Eureka!" moment, or is it a series of smaller "Aha!" steps? I think that it's the latter, but that most functional languages drop them in your lap in one undigested lump, because I had an odd introduction to the style.
Back around the turn of the century, I encountered the famous Why Functional Programming Matters paper; and while I’m a mathematician by background, that was always in Applied -- crunching differential equations or re-casting multiple integrals as Fourier transforms -- and I simply didn't get it. How could you do anything in your programs if your variables didn't vary? Then after some years, I encountered an article -- no longer on the web, alas -- suggesting that Erlang was well placed to be the "next Java", and my curiosity was piqued.
Some while later, I spotted Joe Armstrong's Programming Erlang on the shelves of the local Borders, so picked it up; and though the syntax and conventions of the language were bizarre and alien -- very "spiky" looking -- the mystery of how you did things with immutables was dispelled in the first couple of chapters. And it was revealed to be quite mundane -- you keep your scopes small (general good practise, anyway) to avoid running out of sensible write-once names, and you recurse or use callbacks rather than iterate.
So, despite its unrelentingly immutable nature (process dictionary aside), it turned out that Erlang had a very low barrier to the program after “Hello world!” Writing non-trivial programs (a few hundred lines of networking code) that accomplished and outperformed what had taken me thousands of lines in ‘C’ was easy. Yes, there were compilation errors along the way, but those were mostly simple syntax (putting the correct punctuation marks at the ends of lines), and I debugged at run-time as usual.
Really, despite the alien syntax and strict immutability, this felt like OO in the abstract -- you send an
object process a message, and it responds in a Smalltalk-esque (or even Win32 message loop-alike) fashion. First class functions were just familiar callbacks with a different name, even in cases like a transformative map over a list. Indeed so OO is the paradigm that people talked about implementing Ruby in an "object → process" style on the Erlang VM, and then actually implemented such a near-Ruby.
Then I found out that this F# language was not Fortran.net, but a functional language on the CLR and gave it a whirl. And I felt like an utter dunce who “didn’t get it” for months; not helped by the lack of good introductory material -- like the original Foundations of F# book, which for the newcomer was frankly terrible on account of the volume of assumed knowledge it brought along.
In hindsight, the big shift was between Erlang's dynamic type model, and the static, inferred, typing of F#; the feature that means that a program that compiles will probably also work as well. Unfortunately, that also means that you have in a sense to debug your program at this stage, which is the massively unfamiliar bit. And it's not helped when your program fails to compile with a message that about as comprehensible as a C++ template failure -- it wasn't until F# for Scientists that I found my Wittgenstein's Ladder, a book that actually explained enough of the the types and type notations, and suddenly made sense of the bizarre arrow-filled compile failures (and API definitions).
Then, and only then, did the list-processing nature of LISP, and Haskell's mysterious monads, all suddenly become things that fall naturally out of the new style of thought. The extra constraint of type exactness is indeed what makes operations like
bind to transform types a meaningful and necessary concept rather than being approached ad hoc.
The other shift, related to the debugging at compile time is that, like template metaprograming in C++, suddenly computation can happen as compilation -- a pure function with fixed compile time inputs can in principle be evaluated at that point. And where your program does take inputs, the way of looking at it approaches being an aggregation of functions where input comes in and results come out through a transformative process built of little stateless sub-transformations; rather than as a collection of stateful objects talking to each other about the input.
And at that point, I think what is being practised is functional programming.