Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

ABC's of Functional Programming

ABC.livemd

ABC’s of Functional Programming

Different ways of thinking in FP

The goal of this document is to give a very opinionate view on a way of thinking about functional programming for those coming from a more imperative background be it popular web languages (JS, Python) or C like coding (inc Java).

Hopefully it will provide a useful way of thinking about tackling problems in a FP approach and how some issues become easier (definitely different)

Immutability

Possibly the first thing that you will hit in FP and will leave you scratching your head is immutability.

There is alot written about this topic and what benefits it can bring. I want to focus on how it can feel off coming from an Imperative background.

Typically in an imperative approach, each command is executed in order and we can modify or variables. Each time we modify a variable it gets update to the new value, so for example:

count = 0 
for (i = 0; i <= 4; i++) {
  count += i
}
# count = 10

At the end of this loop count will have the value 10. However if we try and do this in Elixir it will not work.

count = 0

1..4
|> Enum.each(fn x -> count + x end)
|> IO.inspect()

count

The each function just executes a function for each element in a collection (more on collections later in the doc) We can see that the count variable is not being changed. This is because immutability rules:

> simply don’t allow values in a certain memory location to change.

This is very important to running code concurrently see Immutability in Elixir

Lets see another example

# Another example
a = 1
# This line is declaring an anonymous function and assigning it to print_var_fn
print_var_fn = fn -> IO.puts(a) end

a = 2
print_var_fn.()

> The concept of data immutability is that the data once it is created in a memory location cannot be altered. Immutability must be preserved

The above example was taken from Elixir — Understand Data Immutability

Recursion

So what if I did actually want to do some sort of counting code (run a code some number of times and get a single result out)?

One technique you can reach for, is a recursive function. That is a function that calls itself.

When writing a recusive function you often think of a termination condition (e.g when a parameter reaches 0) and keep calling the function with new parameters till the condition is met.

Lets try with a simple count function we want to exit when a count down reaches zero and returned the value we have calculated. We will reproduce the for loop example we saw above.

defmodule SimpleCount do
  # Exit when we have counted down to 0
  def run(count, 0) do
    count
  end

  def run(count, n) do
    # Rerun the same function with updated parameters
    run(count + n, n - 1)
  end
end

SimpleCount.run(0, 4)

We are doing a number of interesting things in the above function. Hopefully it looks relatively clear what the code is doing and can be reasoned about. We are also doing pattern matching of the function signatures avoiding a messy if statement.

Thoughts on approach

I tend to think of FP coding in Eliixr as exercise in transformation. We begin with some collection of data and create a pipeline of modifications to return a result.

[Todo]

The following sections hope to flesh out this idea when working with collections of data. By collection it could be a list all of the users in your system, or all the responses in an API call.

Typically we will want to return another collection with the processing done on each element (Map / Filter) or accumulate something from the collection to a single value (Reduce).

Enum.Map

Enum.map Returns a list where each element is the result of invoking fun on each corresponding element of enumerable.

It is important to note that each function is called on the element independently of the other functions, so you will not have access to other results.

1..4
# x will be called first with 1, then 2 etc
|> Enum.map(fn x -> x + 1 end)
|> IO.inspect()
|> Enum.map(fn x -> x + 2 end)

Enum.Filter

Enum.filter returns only those elements for which fun returns a truthy value.

So we can write a way of filtering odd numbers

require Integer

1..10
|> Enum.filter(fn x -> Integer.is_even(x) end)

Enum.Reduce

Enum.reduce typically takes a collection and gets a single value, it can remember the results from the previous computations.

We are going to reimplement the recursive function we wrote above to so the same thing. Reduce takes a function as an argument in this case x, acc the x is the current value of the collection of the collection we are working with (1, 2, 3, 4 etc) and accumulator is the return value of the previous calculation.

1..4
|> Enum.reduce(fn x, accumulator -> accumulator + x end)