In Elixir, functions are first class types. This shouldn't be terribly surprising; it is a functional programming language, after all. Today, we'll have a look at:

  • Defining Anonymous Functions
  • Calling Anonymous Functions
  • Using Functions as first class types

Let's get started.

Defining Anonymous Functions

Anonymous functions are defined with the fn keyword. Let's see what that looks like:

print_name = fn
  {:person, first_name, last_name} -> first_name <> " " <> last_name
end

Functions take parameter lists and bodies, separated by arrows (->). The parameter lists are used for Pattern Matching (we actually saw function declaration in the last episode on Pattern Matching).

The print_name function above will match a tuple containing three elements, when the first element is the atom :person. It then matches to a body that will concatenate the first and last names together (where by convention, we assume they will be in the second and third positions in the tuple).

Calling Anonymous Functions

The syntax to call an anonymous function in Elixir is a little weird looking at first. Let's see what it looks like:

print_name.({:person, "Josh", "Adams"})

That's easy enough, although the dot makes calling them feel different from calling functions in modules.. What do you think happens if you try to call it with an argument that doesn't match any of the parameter lists?

print_name.('foo')

It throws a FunctionClauseError.

Let's define an anonymous function that behaves differently depending on the argument provided:

calculate_bill = fn
  [{:item, price}, {:item, price2}] -> price + price2
  {:item, price} -> price
end

Now, this is in fact a silly way to define this function (recursion would make more sense, so you could accept more than 2 items), but for the purposes of demonstrating Pattern Matching in functions it's acceptable.

Now you can call this function with either a single item or a list containing two items, and it will return the price. Let's try it:

calculate_bill.([{:item, 20}, {:item, 10}])
calculate_bill.({:item, 35})

So that worked, but like we said, it's pretty low-utility - it can only accept one or two items, tops. I was going to follow up here by defining the function recursively, but recursive anonymous functions in Elixir require a bit of finagling with the Y combinator, so I won't cover that yet.

One more fun thing that you can do with anonymous functions is invoke them immediately. For instance:

(fn -> "foo" end).()

Obviously that, too, is a pretty unlikely example, but it serves to show off immediate invocation, none the less.

Using Functions as First-Class Types

Since functions are first-class in Elixir, you can pass them as arguments or return them from other functions. Let's play with that.

add = fn
  num -> (fn num2 -> num + num2 end)
end

Here, add is a function that takes an argument, and returns a function that will add that argument to the new function's single argument. We can use this to generate a function that adds 3 to its argument:

add3 = add.(3)

add3.(5)

You can also write functions that take other functions as arguments. For example:

greet_person = fn
  greeter, {:person, first_name, last_name} -> greeter.(first_name <> " " <> last_name)
end

polite_greeter = fn
  name -> "Hello, #{name}, nice to meet you!"
end

terse_greeter = fn
  name -> "Hi #{name}"
end

person = {:person, "Josh", "Adams"}

greet_person.(polite_greeter, person)
greet_person.(terse_greeter, person)

Summary

That's the end of today's Sip. We covered the basics of Functions - defining them, calling them, and using them as first-class types. Play around with it and see what you can come up with. See you soon.