John Puopolo is, with Sandy Squires, the author of the F# Survivial Guide, on which this article is based. The F# Survivial Guide covers all of the essential elements of functional programming and the F# language.
There are many ways to examine problems and model their solutions. In software, we tend to look at problems through the lens of our experiences and the tools that we have at our disposal. In other words, our experiences and the tools we know influence how we approach solving problems. For example, in the days of structured programming, we solved many problems using procedural decomposition and subroutines, focusing on internal cohesion and loose coupling. Building on these successful techniques, we transitioned to modeling solutions using objects and object-oriented techniques, further encapsulating imperative code and its data.
While object-oriented design and object-oriented programming will continue to play a significant role in years to come, I believe we are facing challenges that require new ways of thinking and new ways of implementing solutions. For example, massive computing power via multicore architectures and cloud computing will make developing concurrent systems and parallel algorithms a virtual necessity. Functional programming provides us with many of the tools and technique we need to make this a reality. Additionally, functional programming gives us new ways of developing scalable systems that are easy to debug and test.
What is Functional Programming?
Functional programming is a specific way to look at problems and model their solutions. Pragmatically, functional programming is a coding style that exhibits the following characteristics:
- Power and flexibility. We can solve many general real-world problems using functional constructs
- Simplicity. Most functional programs exhibit a small set of keywords and concise syntax for expressing concepts
- Suitable for parallel processing. Via immutable values and operators, functional programs lend themselves to asynchronous and parallel processing
Functional programming treats computation, e.g., running a program or solving a numeric calculation, as the evaluation of functions. Not surprisingly, in dealing with functional programming, we will use the term function quite often and in many contexts. So, before we continue, let's make sure we understand what a function is:
A function is fundamentally a transformation. It transforms one or more inputs into exactly one output.
An important property of pure functions is that they yield no side effects. This means that the same inputs will always yield the same outputs, and that the inputs are not changed as a result of the function. This idea of "no side effects" is one (of several) that makes functional programming particularly attractive when writing multi-process and multithreaded systems. The key concepts that make functional programming attractive for solving certain types of problems are described in F# Survivial Guide.
Some programming languages are "purely functional languages." This means that they follow strict functional rules, e.g., no imperative looping constructs and no implicit way to introduce state changes. For example, Haskell, a pure functional language, disallows state changes and side effects altogether. Programs written in pure functional languages are almost solely made up of functions that accept arguments and return values. Unlike imperative and object-oriented languages, they allow no side effects and use recursion instead of loops for iteration. Languages like Haskell are often criticized as being too extreme in not allowing side effects, giving them limited penetration in mainstream development environments. Operations such as reading a file become awkward to code in a language where no side effects are allowed. In contrast, impure, multi-paradigm languages such as F#, a strongly typed, first-class .NET programming language designed by Don Syme and others at Microsoft Research, discourage side effects and imperative constructs, but make them available alongside their functional brethren.
From the abstract mathematical theory of functions comes a variety of interesting concepts that have broad application and use in functional computing. For example, functions can take other functions as arguments, and functions can return other functions as results. This ability results in flexible and powerful mechanisms that enable us to implement ideas simply and elegantly -- ideas that are difficult to express in non-functional languages.