Foundation and Principles of Functional Programming
Looking into the world of functional programming.
Introduction -- TLDR: Functional will be more important -- be prepared
As a programmer, I care about my profession. I sometimes think about the future of programming. And in my vision, the future is functional. Ok, it may or may not be fully functional but it will more functional than now. Actually, it already happens! Almost all alive programming languages already start adapting it. Java 8 is a good example. Therefore, I believe it is important for us programmers to start learning/collecting experiences on functional programming (FP). This article discusses the foundation and principles of functional programming.
NOTE: Just for the record, I do not think that OOP is bad and should be thrown away to adapt FP. It is just that FP has a lot to teach us and we should be prepare for that. Watch this interesting talk -- Brian Goetz - FP is Dead Long Live FP.
Functions -- TLDR: Functions are just data-in and data-out.
Functional Programming is a programming paradigm that uses functions as the main execution units. Functions can be manipulated like any other values. They can be used as inputs to and can be returned out of other functions. Functions used as objects are called first-class functions and functions that take or return other functions are called high-order functions.
Purity -- TLDR: FP treat effect-full and effect-free functions differently
In FP, a function is simply a map between its inputs to its output. The mapping must not require external information -- all its need to create output must be in the input. No file read, no database access, no reading current time, no random numbers nor anything unpredictable. There is also no actions or observable effects of any kind -- no file write, no database update, no display of information, not even changing object properties in place. So, only just mapping of the data coming in to the data going out. This is where category and set theories come in as it is a study of similar kinds of data and the transformations between them.
Of course, there are lots more interesting things outside of data processing and FP does support that. It is just that in FP, those got special treatments. FP manages its effects separated from processing. Functions that only process data is called "Pure functions" and the ones that also have effects are called "Impure functions". I personally think that these are not very good names. I prefer to call them "functions" and "procedures" respectively.
The uses and benefits of pure functions had been known and well understood to the FPers since its inception many decades ago. Without any external factors, pure functions are easy to reason with. Everything you need to know is within its boundary and its input. Pure functions tend to be small -- even very small -- and tend to only do one thing. Without effects, the functions can be replaced with its output (a quality known as "referential transparency") and can be run at later time and place (on different machines) as long as the inputs are the same, the output will be the same. Optimization, as well as parallelism also benefit from these characteristics. Programs that run as part of blockchain, for example, MUST be pure, as they might be run at any time and on any machine that processes the transactions containing the programs.
The impure functions, on the other hands, are a bit of a struggle. Many tactics have been used to tame them including the infamous "monad" and many of its variations but all still present challenges as it is not as straightforward comparing to just doing the effect right where you need it. In my opinion, these techniques are unintuitive and convoluted (I am looking at you, Scalaz and Cats). More works will be needed to make them more friendly. The interesting ones are Elm-command and Kotlin-coroutine. For us working in the non-functional programming languages (including Scala and F#), we have more options as we can mix and match or relax the mixture of purity and impurity. Techniques such as continuation (aka. callbacks) can be used to simplify the problems. Or sometimes, just ignore purity that completely and just call effect-full operations outright. All in all, the benefits of managing or contains effects away from data processing are clear and desirable.
Immutability and Exception Handling -- TLDR: It is not too long ... just read it.
Further implications of pure functions are that pure functions cannot modify objects in place and cannot throw exceptions. Hence, FP strongly prefers immutable objects or stateless objects. Many FP languages ban mutability outright and some have a special mechanism for that. Clojure, for example, treats modification as some sort of a database transaction -- Atom. Also, without changing values nor having external input, there can't be loops because looping requires termination conditions. Any repeating operations are done with recursion.
Without throwing exceptions, the error has to be communicated differently. This will be discussed in the section "Totalism" below.
Composition -- TLDR: Functions are composable and reusable.
Calling functions is a single action. In OO, you will need to create and configure an object. You may also talk to it many times after. This time dimension makes things a bit more complicated. Objects, in other words, has more surface area that needs to be correctly connected to work as desired. Calling it in a wrong order might get different results which might be hard to predict. This is not an issue for functions. Plugging the output of a function to another function is straightforward. Thus, functions are much more composable; and, therefore, more reusable. This is why FPers often compare functions to LEGO brick and objects to jigsaw puzzles.
Another implication of this is that FP code tends to be some sort of flow where the output of a function is used directly as an input of another. We, Java programmers, might recognize this as "fluent style" of coding.
Declarative -- TLDR: Functions composible and reusable.
Since functions are very composable and can be passed around like objects in OO, we can often create a program that hides moving part inside a function and allowing customization (in the form of other functions) to come in as inputs. When do it right, the customization can specify what the caller needs without concerning much of the moving part inside it. This style of programming is called "declarative style" of programming as oppose to "imperative style". The good example is the Java 8 stream API.
var secretIdentities = superHeroes.stram() .filter (hero -> hero.hasSecretIdentiy()) .map (hero -> hero.secretIdentity()) .collect(toList());
Totalism -- TLDR: There must be output for any given input.
Functions cannot throw an exception as that is considered an observable effect.
Thus, they need a different way to communicated error.
Another way to look at this is there must be output for any given input -- totalism.
There are generally two strategies for this.
First is to make sure that only valid inputs are allowed.
This tactic is used in static-typed functional languages.
These languages provide many different ways to create types that can specifically limit what values are considered valid.
One of the technique is algebraic data types -- the mixing of product and sum types.
Another way is to communicate the problem in the output.
A well-known example (for us Java developers) of this is the
Optional (known as MayBe or Option or Nullable in other languages).
It basically let the function return "no value" as the output.
In many FP languages, developers are forced to handle both value and no value cases.
There are other variations of these such as
Either (one type of value or another type),
Result (value or error) and others.
I believe functional programming is an unavoidable trend for programming. Functional programming is programming with functions. Functions are mapping of inputs to outputs without effects -- purity. From this foundational concept, immutability, composition, declarative and totalism emerged as important principles for functional programming.
This article discusses the foundation and principles of functional programming. There are a lot more to it but this should be enough to understand what FP is why FPers do things they way they do. It is should reasonable efficient to start learning more about the programming in FP-style. For Java programmers, Java has become more functional friendly. There are also libraries that facilitate programming in the more functional ways like Guava, FunctionalJava, Vavr and FunctionalJ. In the next article, we will discuss one of the library -- FunctionalJ and how it bring functional programming to Java.