This post is part of a series called functional fundamentals. See the introduction and index. That's also where you can request specific topics
Note that I am not an authoritative figure on these subjects, if you see something you think might be incorrect or incomplete, please point it out with a comment, let's prevent mistakes from spreading :-)
By this point in the series you should already know a lot about the advantages of (pure) functional programming:
- Safe composition (easy refactoring, guaranteed integration, real monads...)
- Strong type systems (no run-time errors, no backdoors)
- Explicit data-dependencies (good for various optimizations)
And now it's time to talk about the catch.
Functional code doesn't do anything. It's just a value. Even
readLn "Hello, World!" (the
print("Hello, World!") equivalent in Haskell) is just a value of type
The program needs to be evaluated, it needs to be applied to inputs at the very least. It needs to interact with the world, which unfortunately, is not functional, at least not in a way that is useful to us.
In places where this happens, we have a functional program boundary, where the functional world ends and the imperative one begins. Haskell crosses this boundary using
do blocks (EDIT: this is not entirely accurate, see comments), Elm has The Elm Architecture which is specific to websites.
On the boundaries, run-time exceptions become possible, composition is non-trivial and the type system is often more generic.
The larger the boundaries, the less use you will have out of functional programming.
Boundaries can be shifted however. Functional programming only cares about data transformations, not so much where or when they happen.
Big data applications have leveraged this by extending the boundary over a network of computers rather than single ones. AWS lambda and Google Functions are using a similar concept, treating imperative programs as pure functions. In fact, mapping a pure function over an array, list or dict is embarrassingly parallel.
Elm, using TEA, instead hides the imperative world by segmenting the problem into functional parts, abstracting away the imperative glue into commands and messages. This way the programmer is only bothered with the functional stuff.
In short: how a functional language, library or framework represents a problem can greatly affect how large your functional boundaries are, smaller boundaries generally being better.