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

Functions

functions.livemd

Functions

Mix.install([
  {:kino, github: "livebook-dev/kino", override: true},
  {:kino_lab, "~> 0.1.0-dev", github: "jonatanklosko/kino_lab"},
  {:vega_lite, "~> 0.1.4"},
  {:kino_vega_lite, "~> 0.1.1"},
  {:benchee, "~> 0.1"},
  {:ecto, "~> 3.7"},
  {:math, "~> 0.7.0"},
  {:faker, "~> 0.17.0"},
  {:utils, path: "#{__DIR__}/../utils"}
])

Navigation

Return Home Report An Issue

Setup

Ensure you type the ea keyboard shortcut to evaluate all Elixir cells before starting. Alternatively you can evaluate the Elixir cells as you read.

Functions

Elixir is a functional programming language. So you can imagine that functions must be important. But what is a function?

Input and Output (IO)

A function is a set of repeatable instructions. A function accepts some input, and returns some output.

  flowchart LR
    Input --> Output

Black Box

How the function converts some input to some output is often referred to as a black box. It’s a black box because you don’t need to know (or can’t know) the details of how it works.

  flowchart LR
    Input --> B[Black Box] --> Output

Creating A Function

Let’s create a function called double which will take in a number and double its value.

flowchart LR
  A[2] --> B[double] --> C[4]

Now, let’s create our first function. At first, it’s going to do nothing. A function must have an output. We can return nil for now.

double = fn -> nil end

You may see some weird-looking output like #Function<45.65746770/0 in :erl_eval.expr/5>. Don’t worry too much about that. It’s how Elixir represents a function internally.

Parts of A Function

Let’s break down what we did above.

  1. double is a variable name. Often you’ll refer to it as the function name. It can be any valid variable name.

  2. We bind double to an anonymous function. The anonymous function is everything from the fn to the end.

    flowchart LR
     A[function name] --> B[=]
     B --> C[anonymous function]
  3. Elixir uses the fn keyword to define a function.

  4. The next value -> separates the function head and the function body.

  5. The function head describes the input of the function. In this example, it’s empty.

  6. The function body contains the function’s implementation or black box . In this example, it returns nil.

  7. Elixir uses the end keyword to stop creating a function.

flowchart LR
   direction LR
   a[function name] --> B
   b[function head] --> A
   b[function head] --> B
   c[function body] --> C
   subgraph a[Breaking Down A Function]
      direction LR
      A[fn] ---- B
      B[->] --- C
      C[nil] --- D
      D[end]
   end

Calling A Function

Our double function doesn’t do much at this point, but let’s see the output that it returns.

We use the .() syntax in Elixir to get the function’s output. We often say we are executing or calling a function.

double.()

double should return nil because that’s all we’ve told it to do so far. However, we want it to multiply a number by 2.

To do that, we need to make the function accept some input. To do this, we define a parameter in the function like so.

double = fn parameter -> nil end

You’ll notice a warning above. That’s because Elixir is smart and lets us know that we’ve created a parameter, but we’re not using it.

In Elixir, you can ignore this warning for unused variables by starting them with an underscore _

double = fn _parameter -> nil end

No more warning 😀 But we actually want to use that parameter, so let’s modify the function to return the parameter instead.

double = fn parameter -> parameter end

The parameter is named parameter here for the sake of example. But it works a lot like a variable, and it can be named any valid variable name.

Let’s rename it to number to clarify that we expect the input to be a number.

double = fn number -> number end

Now the function head takes in a value. We have to pass it an argument when we call it. The argument will be bound to the parameter when the function executes. We’ll give it the integer 2.

double.(2)

Notice that if you try to call the function without an argument, it fails because it expects an argument. Not all languages do that, but Elixir is pretty smart 😎

double.()

Great, now all that’s left is to multiply the parameter by 2. You should be familiar with this from the previous sections.

double = fn number -> number * 2 end

And you can use it to double any number.

double.(10)
double.(11)
double.(10 ** 2 - 1)

Under the hood, when the function runs, the parameter is bound to the argument’s value.

Let’s break down how a function executes step by step in the following slideshow.

Utils.slide(:functions)

As expected, double.(3) returns 6.

double.(3)

Implied Return Values

Some languages require explicit return values.

However, in Elixir the output of a function is always the last line.

For example, notice that the return value below is first + second, which equals 3.

multiline_function = fn ->
  first = 1
  second = 2
  first + second
end

multiline_function.()

Your Turn

:walnuts In the Elixir cell below, create a function is_even? that checks if an integer is even or not. The function should return true if the integer is even and false if the integer is odd.

Replace nil with your anonymous function.

Hint

You can use rem to determine if an integer is even with rem(integer, 2) == 0.

is_even? = nil

Utils.feedback(:is_even?, is_even?)

Multi-Parameter Functions

Functions can accept multiple inputs. Separate parameters with commas , to create a multi-parameter function.

sum3 = fn param1, param2, param3 -> param1 + param2 + param3 end

sum3.(2, 3, 4)

Keep in mind that the first argument will be the value of the first parameter, and the second argument will be the value of the second parameter. You can repeat this with as many parameters as you want!

to_list = fn a, b, c, d, e -> [a, b, c, d, e] end

to_list.(1, 2, 3, 4, 5)

But usually, you want to avoid having too many parameters because it makes your function hard to understand.

A parameter can be bound to any valid data type, so you could instead use an associative data structure like a map or keyword list.

Function Arity

The number of parameters your function accepts is called the arity of the function.

A function with no parameters has an arity of zero. A function with one parameter has an arity of one, and so on.

You refer to the function as function_name/arity thus a function named add_two with two parameters is called add_two/2.

Your Turn

Create a multi-parameter calculate_force/2 function. calculate_force/2 should accept mass and acceleration and return mass * acceleration.

Replace nil with your anonymous function.

calculate_force = nil

Utils.feedback(:calculate_force, calculate_force)

Shorthand Syntax

Anonymous functions can be defined using a shorthand syntax. It is only an alternative and shorter version to define a function. You will sometimes see shorthand syntax, so it’s helpful to understand it. However, it should not be over-used. Otherwise, your program may be less clear.

You can still bind the anonymous function to a variable with the shorthand syntax. However, you define the function with &() and put the function body between the brackets.

Here’s the same double function using short-hand syntax.

double = &amp;(&amp;1 * 2)
double.(5)

&1 means the first parameter. If the function had more parameters, you could access them with &2, &3, and so on.

add_two = &amp;(&amp;1 + &amp;2)
add_two.(2, 3)

Your Turn

Using shorthand syntax, create a calculate_force function which multiplies mass and acceleration.

Replace nil with your answer.

calculate_force = nil

Utils.feedback(:calculate_force, calculate_force)

First-class Functions

Functions in Elixir are first-class citizens.

For our purposes, this means we can bind functions to variables, store them in other data types, pass them as arguments to other functions.

If a function takes in another function as a parameter, it’s called a higher-order function.

For example, we could make a function named call_twice, which calls a function on a value twice.

call_twice = fn function, parameter -> function.(function.(parameter)) end

We’ll use our double function and create a quadruple result.

double = fn number -> number * 2 end

call_twice.(double, 3)

We don’t need to bind the passed function to a variable first. We can pass in the anonymous function directly.

call_twice.(fn number -> number * 2 end, 5)

Whenever you pass a function into a higher-order function with the expectation that the higher-order function is going to call it, the passed function is referred to as a callback function.

Your Turn

Create a call_with_20 function. the call_with_20 function should accept a function as it’s argument and call that function with the integer 20.

Replace nil with your anonymous function.

call_with_20 = nil

Utils.feedback(:call_with_20, call_with_20)

Pipe Operator

To create more complex behavior, you’ll often compose smaller functions together. Composing functions together reflects nature of problem-solving where we take large problems and break them down into smaller ones.

To help compose functions together, Elixir provides the pipe |> operator. That’s the | symbol likely above your enter key, and the greater than > symbol side by side to make |>.

The pipe operator allows you to take the output of one function and pass it in as an argument for the input of another function.

flowchart LR
  A[Input] --> B[Function1]
  B --> C[Pipe]
  C --> D[Function2]
  D --> E[Output]

Why is this useful? Without the pipe operator you can wind up writing deeply nested function calls.

four.(three.(two.(one.())))

Or rebinding values between function calls.

a = one.()
b = two.(a)
c = three.(b)
d = four.(c)

But with the pipe operator, you can chain functions together.

one.() |> two.() |> three.() |> four.()

If a function is called with multiple arguments, the function piped in will be the first argument.

two(1, 2) # how to call two/2 by itself.

# How to use the pipe operator
# to call the two/2 with one/1 as the first argument.
one.() |> two.(2)

You can also pass in a value to a pipe. It’s generally non-idiomatic to use the pipe operator for a single value and function.

# non-idiomatic
1 |> two.()

# idiomatic
1 |> two.() |> three()

The pipe operator doesn’t change the behavior of a program. Instead, the pipe operator exists as syntax sugar to improve the clarity of your code.

Your Turn

Use the add/2, subtract/2, and multiply/2 functions provided. Use the pipe operator to:

  1. start with 10.
  2. add 2.
  3. multiply by 5.
  4. subtract by 4.

Replace nil with your answer.

add = fn int1, int2 -> int1 + int2 end
multiply = fn int1, int2 -> int1 * int2 end
subtract = fn int1, int2 -> int1 - int2 end

answer = nil

Utils.feedback(:pipe_operator, answer)

Commit Your Progress

Run the following in your command line from the project folder to track and save your progress in a Git commit.

$ git add .
$ git commit -m "finish functions section"