To put it simply, the restrictions mean there are fewer correct ways to put things together, and first-class functions make it easier to factor out things like loop structures. Take the loop from this answer, for example:
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
String string = iterator.next();
if (string.isEmpty()) {
iterator.remove();
}
}
This happens to be the one safe imperative way in Java to remove an element from a collection while you're iterating through it. There are lots of ways that look very close, but are wrong. People unaware of this method sometimes go through convoluted ways to avoid the problem, like iterating through a copy instead.
It's not terribly difficult to make this generic, so it will work on more than just collections of Strings
, but without first-class functions, you can't replace the predicate (the condition inside the if
), so this code tends to get copied and pasted and modified slightly.
Combine first-class functions that give you the ability to pass the predicate in as a parameter, with the restriction of immutability that makes it very annoying if you don't, and you come up with simple building blocks like filter
, as in this Scala code that does the same thing:
list filter (!_.isEmpty)
Now think about what the type system checks for you, at compile time in the case of Scala, but these checks are also done by dynamic type systems the first time you run it:
list
must be some sort of type that supports the filter
method, namely a collection.
- The elements of
list
must have an isEmpty
method that returns a boolean.
- The output will be a (potentially) smaller collection with the same type of elements.
Once those things have been checked, what other ways are left for the programmer to screw up? I accidentally forgot the !
, which caused an extremely obvious test case failure. That's pretty much the only mistake available to make, and I only made it because I was directly translating from code that tested for the inverse condition.
This pattern is repeated over and over again. First-class functions let you refactor things out into small reusable utilities with precise semantics, restrictions like immutability give you the impetus to do so, and type checking the parameters of those utilities leaves little room to screw them up.
Of course, this is all dependent on the programmer knowing that the simplifying function like filter
already exists, and being able to find it, or recognizing the benefit of creating one yourself. Try to implement this yourself everywhere using only tail recursion, and you're right back in the same complexity boat as the imperative version, only worse. Just because you can write it very simply, doesn't mean the simple version is obvious.
final
on everything).