3 Reasons To Learn Functional Programming With Elixir

If you’ve ever thought about learning functional programming but hesitated to try it, you might have had one of the following thoughts:

  • Someone told me that to learn functional programming I’ll have to forget everything I know. I don’t want to forget everything I know! And if functional programming is so incompatible with everything I know then it’s probably not relevant to me anyway.
  • It’s too early in my career to learn a whole new paradigm. I haven’t even completely grasped the imperative programming paradigm. I need to do that before I can learn functional programming.
  • It’s too late in my career to learn a whole new paradigm. It’s taken me a long time to get this good at what I do and I’ve committed myself. There’s no way I’m going to change direction now.
  • Doesn’t functional programming involve a lot of calculus and theory? I’ll need a Ph.D. in math to learn it. I won’t be able to understand it. And isn’t it more academic than practical anyway?

I thought this way too, and I wasn’t sure if I wanted to set out down the functional path. It’s so different from the Java I normally work with in my day job. Where would I even begin? Then I came across Elixir. I decided to jump right in and try building a web app with this functional programming language. It wasn’t long before I thought “oh, I actually get this”.

But why should you learn it?

A new way of thinking about problems

Functional programming will change the way you think about solving problems, and I promise you don’t have to forget everything you know. Imperative programming and functional programming are two sets of problem-solving strategies. But you don’t have to throw one of them away to learn and use the other.

Immutability and statelessness

One of the key functional concepts is that data should be immutable; it can’t be modified after it is created. A related concept is statelessness. Functions in functional programming should be stateless. They don’t access outside values except for the arguments that are passed into them. Given the same inputs, a function should produce the same outputs every time, regardless of what else has happened outside.

Immutability and statelessness are two constraints that require us to think differently about solving problems. To give you an example, if you wanted to write a piece of iterative code, and you were thinking about it in an imperative way, one thing you’d probably think about using is a “for loop”.

# Log "Hello" five times
for (i = 0; i < 5; i++) {
  console.log("Hello");
}

# This violates the immutability rule!

We don’t use for loops in functional programming, because they rely on mutable data. We would have to increment a variable in our loop condition. That goes against our immutability principle.

What will we do without for loops?

If we want to write iterative code in functional programming, we can use recursion, which just means functions that call themselves. You might have been warned against using recursion, but I want to ask you to think a bit differently about that. Recursion is a strategy, in the same way, that for loops are a strategy. It allows us to maintain our principle of immutability, and there are situations where it’s quite appropriate to use it.

Before I show you an example of how you can apply recursion, I want to give you a little context by explaining the second reason you should learn functional programming with Elixir.

Pattern matching

Elixir’s pattern matching feature is really powerful. In Elixir, the equals sign isn’t an assignment operator, it’s a match operator. You have a pattern on the left side and a term on the right side and you’re comparing them to see if they match. If they match, the right side is bound to the left.

Pattern matching with lists

When we’re pattern matching with lists, often we want to treat the head and tail of the list separately. We can use the head|tail syntax to split a list, binding the first element to the variable “head” and the remaining elements to the variable “tail”.

# We can bind the head and tail separately
[head|tail] = [42, :ok, “cats”, 21]

# Now head = 42 and tail = [:ok, “cats”, 21]

Multi-clause functions

Another way we use pattern matching is by defining multi-clause functions. We can have multiple definitions for the same function depending on what arguments we pass into it. This allows us to avoid using switch statements or nested if statements, instead of keeping our conditional logic isolated and legible. We explicitly declare what inputs we expect to receive, and what should be done with them.

Now that we understand pattern matching with lists and multi-clause functions, we’re ready to look at our example of recursion.

# Double integers in a list
defmodule Calculator do
  def double_each([head|tail]) do
    [head * 2 | double_each(tail)]
  end

  def double_each([]), do: []
end

Here we have a function that takes a list of integers and multiplies each of the values in the list by two. The way it does this is by first binding the head and tail of the list, then doubling the value that is bound to the head then finally calling itself, passing the tail of the list as an argument. So it will call itself again and again as it goes through the list.

When we reach the end of the list, the argument being passed no longer matches the first definition, because it’s empty. Instead, it matches the second clause. The behavior defined in the second clause doesn’t include calling the function again, so we exit the recursive loop.

We just used pattern matching to carry out our recursion strategy for problem-solving. That’s what makes pattern matching so powerful. It enables strategies that work within our immutability and statelessness principles.

The actor model of concurrency

Another thing that is really powerful about Elixir is the way it handles concurrent code. Elixir is based on Erlang and it uses Erlang’s actor model of concurrency. This is important because it gives us safer concurrency, lightweight processes and fault-tolerance: three things that make Elixir applications scalable.

Safer concurrency

All code in Elixir runs inside processes, which don’t share mutable state. The actor model inherently conforms to our principles of immutability and statelessness. And that’s really important because it makes concurrent code safer. When a process acts on data, it doesn’t have to be concerned with what other processes might have done to that data.

process-example

I say safer, not 100% safe. That’s because sometimes applications *need* to store state that can be accessed by different processes. Even though processes don’t have shared memory, we can create shared-memory-like constructs to serve that need. So we still need to be careful and deliberate, when we make decisions about processes. Like whether to retrieve state and then process the data on the client side, or process the data atomically on the server side.

Lightweight processes

Processes in Elixir are not like operating system processes. The Erlang virtual machine that manages them uses a dynamic approach to memory management that makes them extremely lightweight in terms of memory and CPU. Because of this, you can have hundreds of thousands of processes running at the same time without exhausting nearly as much RAM.

You can spawn a new process using the spawn function. You pass the function you want to execute concurrently in as an argument. It can be an anonymous function or a named function.

# Spawn a process with an anonymous function
spawn(fn -> :timer.sleep 9000 end)

# Spawn a process with a named function
spawn(fn -> double_each([2,4,6]) end)

Fault-tolerance

Suppose we want the child process to let the parent process know if it fails or exits. We just use spawn_link instead of spawn. The linked process will send an EXIT signal to its parent if it fails, and the parent can spawn a new process in its place. Building these supervisory relationships between processes is an important way that the actor model helps us build fault-tolerance.

# Spawn a linked process
spawn_link(fn -> do_something)

# This is the first step to building fault tolerance!

The takeaway

I hope I’ve shown you that you don’t have to forget everything you know before you learn functional programming. There just are a couple of key principles you’ll need to understand and some strategies for working within those principles. In my experience, the best way to gain that understanding is to just jump in and start working with them.

It’s not too early or too late to learn. You don’t have to abandon imperative programming to learn functional programming. Think of it an exercise in working with a different set of constraints. By thinking about the problem of mutability and shared state, you may even come away with a better understanding of imperative programming.

Even if you don’t pick up Elixir, you can think about functional principles and how you might apply them in your code. If you’re a JavaScript developer and you’ve ever thought “I should use const here because I don’t want this variable to change”, you’re already starting to do it.

With Elixir, functional programming is powerful, and it’s certainly not just for academics. It’s for building scalable, fault-tolerant applications.

If you liked this post, follow me on Twitter at @emma_n_robbins

SHARE ON

The Author: Emma Robbins

3 Reasons To Learn Functional Programming With Elixir

You May Also Like

Leave a Reply

Your email address will not be published.