I.A.
Code
Steven Vandevelde

Functional Programming & Elixir – Pt. 1, The Basics

It might be difficult to get into functional programming because of the complexity and the terminology. The goal of these articles is to explain the terminology in a simple manner, reduce the complexity and at the same time, give code samples written in Elixir and show how Elixir helps us with functional programming.
This first article is all about functions, variables and patterns.

Functions

First-class functions

This doesn’t say much, but it basically just means that functions can be stored in variables, that you can pass them to other functions and invoke them there. In Elixir these are called anonymous functions.

some_function = fn(arg) -> do_whatever end

You also have named functions.

defmodule Do do
  def something do
    do_whatever
  end
end

These cannot be passed down to other functions. But you can convert them to anonymous functions.

some_function.( &Do.something/0 )

/0 is the amount of arguments the function accepts, ie. the arity

There’s also another syntax which you’ll see sometimes, for example:

Enum.reduce(["hello", " ", "world"], &(&2 <> &1))

Returns “hello world”

So what is this &(&2 <> &1) thing? It’s a concise anonymous function, a so-called partially applied function. Where &1 is the first argument of the function and &2 the second.

To fully explain the example, <> concats the two strings (ie. the two function arguments). Check the Elixir docs for more info about the Enum.reduce function.

Composing

Composing, function composition, is essentially executing a sequence of functions. That is, the result of each function is passed as the argument of the next.

add_one = fn(integer) ->
  integer + 1
end

multiply_by_four = fn(integer) ->
  integer * 4
end

# execute functions
multiply_by_four.(add_one.(1))             # returns '8'
multiply_by_four.(add_one.(add_one.(2)))   # returns '16'

Elixir provides a way to make it more clear what we’re trying to do here. This operator is called the pipe operator.

# This is easier to read, especially for mathematical operations.
# ((2 + 1) + 1) * 4
2 |> add_one.() |> add_one.() |> multiply_by_four.()

This is the syntax for anonymous functions, which looks a bit odd, if I were using named functions I could omit the trailing .()

Closures

A closure is a function which has the following properties:

x = 2

f_x = fn ->
  y = :math.pow(x, 2)
  y
end

f_x.() # returns 4

x = 3

f_x.() # still returns 4

:math is a Erlang module, which in Elixir are represented by atoms. Elixir runs on top of the Erlang VM, which makes it possible to use Erlang code with Elixir

Higher-order functions

A higher-order function (HoF) is a function which takes one or more functions as arguments and/or a function that returns a new function, which is a closure.

Example

in_p_tags = &("<p>" <> &1 <> "</p>")
in_nav_tags = &("<nav>" <> &1 <> "</nav>")
in_body_tags = &("<body>" <> &1 <> "</body>")

wrap = fn(wrapping_functions) ->
  fn(html) ->
    reducer = fn(function, acc) -> function.(acc) end
    Enum.reduce(wrapping_functions, html, reducer)
  end
end

wrap.([in_p_tags, in_body_tags]).("Text")

# result:
# <body><p>Text</p></body>

wrap.([in_nav_tags, in_body_tags]).("<a href=\"...\">Link</a>")

# result:
# <body><nav><a href="...">Link</a></nav></body>

This example sums it up, we have:

Pattern matching & Immutability

In Elixir the = operator doesn’t only do assignment, but also pattern matching. That’s why it’s called the match operator. This also means that variables aren’t really places in memory that values are stored in, but rather labels for the values. That is, Elixir/Erlang allocates memory for the values, not the variables.

# attached the value '1' to the label 'nr'
nr = 1

# rebind the 'nr' label to the value '2'
nr = 2

# let's see why this is called the match operator
# the following is a valid expression,
# because of the previous expression
2 = nr

# this is not, because 3 does not match 2,
# this will raise a MatchError
3 = nr

We can also do destructuring:

# 1. destructure by using tuples
{ a, b } = { :ok, "Hi" }

do_something_with(a)

# this won't work,
# we need 3 variable names on the left side
{ a, b } = { :ok, "Hi", "extra-extra" }

# this will work
{ a, b, type } = { :ok, "Hi", "extra-extra" }
{ a, b, _ } = { :ok, "Hi", "extra-extra" }

# 2. destructure by using lists
[ a, b ] = [ "first", "second" ]

_ is used to ignore a value

Why this is useful

Say you have a function that returns a tuple,
but you don’t want to do this all the time:

result = some_function()
state = elem(result, 0)
message = elem(result, 1)

elem gets a specific item out of the tuple.

Wouldn’t it be way easier if we could just write:

{ state, message } = some_function()

Yes, it is.

Immutability

With that in mind, in Elixir, all data types are immutable.

tuple = { :number, 1 }

# if we would change the tuple, we would get a new one
# for example, change the second value in the tuple:
put_elem(tuple, 1, 1000)

# returns new tuple: { :number, 1000 }
# tuple variable still points to the tuple: { :number, 1 }

Why this is useful

Imagine if we didn’t have immutable date types.

state = %{ example: "Property" }

func_a = fn(s) ->
  # remove 'example' property from the state map
  Map.delete(s, :example)
end

func_b = fn(s) ->
  str = s.example <> ": example"
end

func_a.(state)
func_b.(state)

%{} is a key-value store, a map

In this case func_b would break, because it depends on the example property and we deleted that property in func_a.

If we apply immutability, for example, Map.delete produces an error, or as in Elixir, Map.delete returns a new map and doesn’t touch the original map, func_b will work fine.

Conclusion

I think we have discussed all the basics of functional programming and Elixir. Except for some basic Elixir types, but you can look those up easily. Elixir has some pretty good getting-started guides.

Next up in the functional programming category is what to do and what not do with functions. Or to put in technical terms, limiting side-effects, data-first and using map and reduce instead of a loop.

Credits: Thanks to Brooklyn Zelenka and Izaak Schroeder for all the help!