These languages have functional features, but are not functional languages. In my experience, functional languages like Erlang or ML have other features that lend a certain safety to programming that mainstream languages don't. One of these is the use of recursion and argument pattern matching to control program looping. You can use these kinds of constructs for flow control as well. Another is taking constant assignment seriously. I say constant assignment here because in these languages once you've bound a value to a variable, that variable is permanently bound to that value until it falls out of scope. The downside to these kinds of features is the difficulty in learning how to develop software using them. For those of us who have grown up using imperative languages, it's a hard transition.
Recursion and Pattern Matching
Functional programming languages feature runtimes optimized for recursion. Using tail call elimination, these runtimes are able to produce recusion environments that are as efficient as looping by reusing the same stack frame for each recursive call. This, coupled with argument pattern matching, allows you to express functions in the same way you would an inductive proof. You have a basis step and an inductive step. The basis step terminates the recursion, while the inductive step continues the recursion. This way, you can define functions that naturally apply to list or collection processing. Each variable in the functions can also only be bound once per call, making variable assignment easier to manage. A pseudocode example would look something like this:
looper(_) := return 0 looper(h, t) := return t + looper(h)
Here, we define a function looper(.) to sum the contents of a list. The first step is the basis step—if the list is empty, we return 0. The second step is the inductive step—if the list has a head element and a tail element, then we add the tail element to the result of a recursive callback to looper(.). If there's only one element left in the list, that element is assigned to t, the recursive call maps to the basis step (as h is empty), and the recursive structure unwinds.
In a property functional language, tail call elimination guarantees stack frame reuse, so this essentially maps to the same structure as a for or while loop in, say, C.
This is very difficult to get used to in functional languages. Maintaining volatile state is pretty tough if the values you use to contain that state can't change, after all. So how do you work around this?
Well, remember, assigned variables are only constant in the current scope. So how do you get around this? You keep the scope small, and bind the needed variables at function invocation. You can't write code that changes state during, say, a sequence of loops. So instead, write code that binds state at function invocation, and recurse. This way, you can maintain changing state, and do so in a way that makes it more difficult to mistakenly assign incorrect values to state variables.
And don't use global variables. Tough to get those to fall out of scope.
That's two of the key differences you'll see in true functional languages, rather than languages that have bolted on functional programming capabilities. Functional programming techniques can give you a new level of reuse, and make programs more clear, but at a potential cost in non-optimized environments.