Written on 2019-07-08
When we talk about “using the right tools” in programming, that applies to a lot of different choices one can make. One is the choice of programming language, which we covered in the last article. Another important “tool set” to be aware of is that of programming paradigms.
Briefly, a programming paradigm is a way of thinking about programming. Specifically, it describes how we go about representing a real-world problem in computer code. A paradigm therefore includes both a mindset of how to analyse these problems, and a process for implementing the solution.
As computer science and programming practice advance, paradigms wax and wane in popularity. Here, I will briefly outline the three that are currently the most frequently used.
This could perhaps be considered the “default” paradigm. It is what is known as an imperative paradigm, meaning that programs are thought of as a series of commands for the computer. Programming, in the procedural paradigm, consists of step-by-step instructions to the computer about how to solve a given problem. (“First do this, next do that.”¹)
On the implementation side of things, code is structured in blocks: loops, conditionals, and functions. (Hence the name of its historical predecessor, “structured programming”.) Generally, functions are viewed not in their mathematical sense, but merely as a collection of instructions that are grouped together for convenience reasons. In short, they are not so much functions as “procedures”.
If you write a simple script, chances are you're using this procedural paradigm. Its approach is also very prominent in the next paradigm, object-orientation.
If you're taught software development today, OOP is what you're taught. This paradigm was developed in the eighties, caught on in the nineties and was standard for the development of larger programs by the 2000s.
Its core idea is to view the world as a collection of interacting objects of various types. To do this, it uses classes, objects, and methods.
For example, imagine you wanted to model a city. A city consists of houses, so
you would have a class House
. Houses can do things, like consumeElectricity()
–
this would be a method. But not all houses are alike: there are appartment
blocks, schools, factories, offices… Each of these subclasses inherit from
the base class House
, meaning they can all do that a basic house can (such as
consume electricity). But they can each have more specialised methods, too. For
example, the class School
may have the method teachChildren()
. (In the
context of OOP, a method is defined as a function that can only be applied to
values of a defined type; in other words, a function that is associated with a
class.)
But there's not just one school in a city, neither is there just one appartment block. Classes, therefore, are instantiated to produce objects. An object is a specific instance of a class – not just any school, but “Loughby Secondary School”. Each object has all the methods and attributes defined by its class and any superclasses, but has its own attribute values. (All schools have a name, but every school has a different name.)
This is a very intuitive approach to many programming problems, especially those that involve modelling real-world phenomena. It also encourages strong encapsulation and abstraction. Because of these strengths, it is currently the most popular paradigm for anything bigger than a 300-line script.
It does have its downsides, though. For starters, large object-oriented systems have a propensity to turn into a complicated tangle of classes and hierarchies – veritable phylogenies that are about as easy to understand as that of the prokaryotes. More significantly, not every programming problem lends itself to this way of thinking.
Data processing, for example. Although one can use OOP to represent a data pipeline (such as that from DNA sequencing data over alignment to genome analysis), it is more natural to think about it as a set of functions applied in sequence.
That is just what functional programming does. In FP, the real world is not abstracted into a bunch of classes and objects, but into series of interlocking functions, each representing one process. Functions here are viewed in their mathematical sense, as entities that take some input and provide some output. Another way of putting it is that FP is about “evaluating an expression and using the resulting value for something”¹.
This means that functional code uses a lot less assignment operations than the imperative paradigms (it is considered a declarative paradigm), as expressions can easily be nested. Less assignments mean less variables being used, which avoids bugs caused by the improper/unexpected modification of variable values. Indeed, “pure” functional programming dictates that functions should be side-effect free, meaning they must not modify any global state at all. Given identical input, a function should always return identical output – regardless of the conditions elsewhere in the program.
The major advantage of this is that such functions are almost trivially easy to test and debug, and can thereafter be considered “black boxes”. This greatly aids reliability and understandability. On the downside, deeply nested function calls can be troublesome to read, and completely pure FP is hard to achieve.
A second aspect of FP is that because functions are so important, they are made
“first-class objects”. That means that the programming language treats them like
any other variable type: you can pass functions as parameters to other functions,
or have functions that return functions. This opens up a whole world of
possibilities. Amongst others, it enables the map
function, which takes in a
function and applies it to each element of a given list (a quick alternative to
a for
loop).
Historically, FP has been around a lot longer than OOP. (Once again, Lisp was a pioneer in this department.) During the height of the “OOP-craze” it very much diminished in importance, but is now being rediscovered as a sensible solution for many scenarios.
Of course, functional programming isn't the Holy Grail either (even if some of its proponents are at least as zealous as many of those of OOP). Remember, the key message here is to Use the Right Tools.
There are many programming problems for which object-orientation is the most obvious solution, and there are many others for which a functional approach is a lot better. Equally, there are some programs that are so simple that you don't need either.
Use whichever is appropriate, and feel free to mix-and-match. Fortunately, most languages today are multi-paradigm and support more than one possibility. (Though, it has to be stressed, not all do equally well in all paradigms!) Just remember: not everything is a nail.
[1] Kurt Nørmark
Tagged as computers, programming