What is Functional Programming?

What is Functional Programming?

·

7 min read

Given that this blog is called "Functional Programming in PicoLisp", I think we should now finally clarify what functional programming actually means.

In this post, I want to introduce two definitions: First the fairly academic one that can be found anywhere, like for example on Wikipedia, and a more hands-on definition by Eric Normand from lispcast.com.


What is Functional Programming?

As Wikipedia tells us, functional Programming is a paradigm where programs are constructed by applying and composing functions. Functions can be passed on and returned from functions just like any kind of data ("first-class citizen").

To me, this is a typical example of a self-referencing definition that doesn't help at all. "Functional programming uses functions", thank you. Let's reframe the question:


What are functions, actually?

The term "Functions" is quite commonly used in programming, but unfortunately in a slightly different meaning. This makes it quite confusing.

In the context of many programming languages, functions are simply "sets of instructions bundled together to achieve a specific outcome, in order to avoid repetitive blocks". According to this definition, we can consider for example object methods in Java also as "functions".

Unfortunately, this is not precisely what we mean by "functions" in the context of "Functional Programming".


In the context of functional programming, the idea of functions is closer to the original, mathematical context: A function "takes" in arguments and "returns" a result.

If it is a pure function, it additionally fulfills the following two conditions:

  • The function return values are identical for identical arguments (no variation with local static variables, non-local variables, mutable reference arguments or input streams),
  • The function application has no side effects (no mutation of local static variables, non-local variables, mutable reference arguments or input/output streams). function return values are identical for identical arguments.

Why this definition is not very helpful too

Let's return to the initial definition:

Functional Programming is a paradigm where programs are constructed by applying and composing functions. Functions can be passed on and returned from functions just like any kind of data ("first-class citizen").

Now we know that functions always have input and return values, and if possible, no side-effects. But still it doesn't tell us much about how such a paradigm can look like, right?

I think the main problem with these kind of definitions is that terms like "functions", "side-effects", "first-class" already have different meanings in other contexts, which makes the terminology so unclear and fuzzy. This is why I really like the definition from Eric Normand, because the wording is much less ambiguous:

"Functional programming is a programming style where you make a clear distinction between actions, calculations, and data."


Data, Calculations and Actions

Again, let's clarify the terminology first. Unlike terms like "functions" and "side-effects", the definitions for "data, calculations and actions" are much more compliant with what you'd expect.

  • Data are basically facts, or more precisely: "recorded facts about events". Data is not executable. For example, this is data: ["abc", "def"], 27.11.2021, {name: "Mia"}.

  • Calculations always give the same output for the same input. It doesn't change if you run the calculation one time or 200 times.

  • Actions is anything that depends on when it is run, or how many times it is run. Examples: sending emails, getting the current timestamp, deleting an object from the database...

Now, if you compare that to our first definition, you will see that "calculations" correspond to "pure functions" and "actions" correspond to "side-effects", only that it is intuitively more clear what is meant.


Why it is a good thing to separate between data, calculations and actions

Let's look at an example to show what it means to differentiate between data, calculations and actions. I think it's easier to demonstrate this on a bad example.

This is the functionality we want to implement: Say we have a form sheet where the user can change their nickname. If that happens, we update the user data, then we save it to the database and send a confirmation email.

What do you think of this?

The following code is meant as a kind of JavaScript pseudocode. Please don't take it literally.

var currentUser = { firstName: "Tom", lastName: "Miller", nickname: "tommy", email: "tom@miller.com" }

function updateUserNickname( newName ) {
  currentUser.nickname = newName;
  currentUser.saveToDatabase();
  sendConfirmationEmail( GlobalUser.email );
}

// Run it
updateUserNickname("tom");

I'm sure you agree with me that this is not very elegant. But let's try to figure out what is the problem. First of all, we try to distinguish between calculation, data and actions.

  • data: currentUser,
  • calculations: none,
  • actions: updateUserNickname, saveToDatabase, sendConfirmationEmail

Do you see the problem? Inside our function updateUserNickname, we have a wild mix of different things. We modify the global user data, we save it to the database and we send a confirmation email. This causes a number of problems. Let's sort them by category:

Testing and debugging:

  • if we want to test updateUserNickname, we also need to simulate the database savings and confirmation emails.
  • If we don't receive a confirmation email, we need to check which part of our function did go wrong.

Maintainability:

  • Our function only works for the "currentUser", because we are accessing the global variable. If we want to have the same functionality for another user, we need to create another function.
  • If we want to modify the "lastName", we need to create another function.
  • There might be use cases where we don't want to send out confirmation emails.

Concurrency Problem:

  • We can perceive strange errors, for example when the user is updating their name two times shortly after each other. Due to network issues, it can happen that the confirmation email of the second change is sent out before the first change, leaving the user confused.
  • Maybe the record could not be saved due to network errors. However this is not transparent because the global variable CurrentUserdisplays the new value.

Making it more functional

We learned that functional programming prefers to separate calculations and actions, and avoids to directly mutate data. Let's try!

Disclaimer, again: This example is just for the sake of illustration. Don't take it literally.

var currentUser = {firstName: "Tom", lastName: "Miller", nickname: "tommy", email: "tom@miller.com"}

updateObj(obj, key, value) {
   obj[key] = value;
   return obj;
}

saveUpdate(obj) {
  if (obj.saveToDatabase() == successful)   
    return true;     // alternatives: return database ID, user object etc.
 else
    return false;
}

// run it

var updatedUser = updateObj(currentUser, "nickname", "tom")
if (saveUpdate(updatedUser)) {
  sendConfirmationEmail(updatedUser.email);
}

Why are these changes "functional"?

In functional programming, we aim to create functions without side effects that are as reusable (i. e. generic) as possible. This means we have to separate actions from calculations.

We split our updateUser() function into three parts:

  • updateObject, which is a calculation (no side effects, return value)
  • saveUpdate, which is an action and also returns a value,
  • sendConfirmationEmail, which executes based on the return value of saveUpdate.

The second "functional" change is that we don't modify the global variable currentUser directly ("immutability"). Instead we created a local variable updatedUser which holds the changes. We could write it back to currentUser once all actions have finished successfully.


But can't I do this in any language?

Now, of course data, calculations and actions are always part of any program. The separation we illustrated above also has nothing to do with the programming language itself, because it's basically a way of coding and thinking.

Nevertheless, some languages make it more easy to follow this kind of paradigm than others. Imperative languages usually require the programer to define precisely how to accomplish a certain task: with loops, conditional statements, breaks and so on. These can be already considered as "actions", which means that it requires a lot of effort and abstraction to separate data, calculations and actions cleanly.

In functional languages like PicoLisp, this can be much easier: For example in PicoLisp, every function has a return value by default. There is no need to define one explicitly. Also, functions can be used as input and return values, which allows more powerful constructs than our pseudocode above.


In the next post, we will highlight some of the functional programming features of PicoLisp.


Sources

Eric Normand: Thoughts on Functional Programming
Eric Normand: Grokking Simplicity: Taming Complex Software with Functional Thinking (Manning 2021)
guru99.com/functional-programming-tutorial...
en.wikipedia.org/wiki/Functional_programming
en.wikipedia.org/wiki/Pure_function