Elixir 1.3
Chapter 5
Anonymous Functions
An anonymous function is created using the fn keyword.
Anonymous Functions
fn (parameter-list) -> body end
fn
parameter-list -> body
parameter-list -> body ...
end
iex> sum = fn (a, b) -> a + b end
#Function<12.17052888 in :erl_eval.expr/5>
iex> sum.(1, 2)
3
The dot indicates an anonymous function call, and the arguments are passed between parentheses.
If your function takes no arguments,
you still need the parentheses to call it.
Anonymous Functions
iex> greet = fn -> IO.puts "Hello" end
#Function<12.17052888 in :erl_eval.expr/5>
iex> greet.()
Hello
:ok
iex> f1 = fn a, b -> a * b end
#Function<12.17052888 in :erl_eval.expr/5>
iex> f1.(5,6)
30
iex> f2 = fn -> 99 end
#Function<12.17052888 in :erl_eval.expr/5>
iex> f2.()
99
You can omit the parens in the function definition.
Elixir uses pattern matching for function params.
We can perform more complex pattern matching when calling a function.
Functions and Pattern Matching
iex> swap = fn { a, b } -> { b, a } end
#Function<12.17052888 in :erl_eval.expr/5>
iex> swap.( { 6, 8 } )
{8, 6}
A single function definition lets you define different implementations. We can use pattern matching to select which clause to run.
One Function, Multiple Bodies
1: iex> handle_open = fn
2: ...> {:ok, file} -> "Read data: #{IO.read(file, :line)}"
3: ...> {_, error} -> "Error: #{:file.format_error(error)}"
4: ...> end
5: #Function<12.17052888 in :erl_eval.expr/5>
6: iex> handle_open.(File.open("code/intro/hello.exs")) # this file exists
7: "Read data: IO.puts \"Hello, World!\"\n"
8: iex> handle_open.(File.open("nonexistent")) # this one doesn't
9: "Error: no such file or directory"
Line 2 and 3 define a function that takes a single tuple as a param. On line 6 we call File.open and pass the result to the function. Depending on the result a different clause is run.
Functions Can Return Functions
iex> fun1 = fn -> fn -> "Hello" end end
#Function<12.17052888 in :erl_eval.expr/5>
iex> fun1.()
#Function<12.17052888 in :erl_eval.expr/5>
iex> fun1.().()
"Hello"
# more obvious inner function
iex> fun1 = fn -> (fn -> "Hello" end) end
#Function<12.17052888 in :erl_eval.expr/5>
iex> other = fun1.()
#Function<12.17052888 in :erl_eval.expr/5>
iex> other.()
"Hello”
fun1 is bound to a function. When you call fun1.() it returns the inner function.
To call that inner function you need to call fun1.().()
Functions Remember Their
Original Environment
iex> greeter = fn name -> (fn -> "Hello #{name}" end) end
#Function<12.17052888 in :erl_eval.expr/5>
iex> dave_greeter = greeter.("Dave")
#Function<12.17052888 in :erl_eval.expr/5>
iex> dave_greeter.()
"Hello Dave”
Elixir automatically carry with them the bindings of variables in the scope in which they are defined.
This is a closure.
Parameterized Functions
iex> add_n = fn n -> (fn other -> n + other end) end
#Function<12.17052888 in :erl_eval.expr/5>
iex> add_two = add_n.(2)
#Function<12.17052888 in :erl_eval.expr/5>
iex> add_five = add_n.(5)
#Function<12.17052888 in :erl_eval.expr/5>
iex> add_two.(3)
5
iex> add_five.(7)
12
What happens with both functions take an argument?
When we set the value n
it gets carried over to the inner function.
Passing Functions As Arguments
iex> times_2 = fn n -> n * 2 end
#Function<12.17052888 in :erl_eval.expr/5>
iex> apply = fn (fun, value) -> fun.(value) end
#Function<12.17052888 in :erl_eval.expr/5>
iex> apply.(times_2, 6)
12
Functions are just values,
so we can pass them to other functions.
We use the ability to pass functions around pretty much everywhere in Elixir code.
iex> list = [1, 3, 5, 7, 9]
[1, 3, 5, 7, 9]
iex> Enum.map list, fn elem -> elem * 2 end
[2, 6, 10, 14, 18]
iex> Enum.map list, fn elem -> elem * elem end
[1, 9, 25, 49, 81]
iex> Enum.map list, fn elem -> elem > 6 end
[false, false, false, true, true]
Pinned Values and Function Parameters
defmodule Greeter do
def for(name, greeting) do
fn
(^name) -> "#{greeting} #{name}"
(_) -> "I don't know you"
end
end
end
mr_valim = Greeter.for("José", "Oi!")
IO.puts mr_valim.("José") # => Oi! José
IO.puts mr_valim.("dave") # => I don't know you
You can use pinned (^) params in a function.
The & Notation
iex> add_one = &(&1 + 1) # same as add_one = fn (n) -> n + 1 end
#Function<6.17052888 in :erl_eval.expr/5>
iex> add_one.(44)
45
iex> square = &(&1 * &1)
#Function<6.17052888 in :erl_eval.expr/5>
iex> square.(8)
64
iex> speak = &(IO.puts(&1))
&IO.puts/1
iex> speak.("Hello")
Hello
:ok
The strategy of creating short helper functions is so common that Elixir provides a shortcut.
The & operator converts the expression that follows into a function. The placeholders &1, &2, ... correspond to the first, second, and subsequent parameters of the function. So &(&1 + &2) will be converted to
fn p1, p2 -> p1 + p2 end.
[] and {}
iex> divrem = &{ div(&1,&2), rem(&1,&2) }
#Function<12.17052888 in :erl_eval.expr/5>
iex> divrem.(13, 5)
{2, 3}
# divrem = fn (a, b) -> { div(a,b), rem(a,b) } end
Because [] and {} are operators in Elixir, literal lists and tuples can also be turned into functions.
Here’s a function that returns a tuple containing the quotient and remainder of dividing two integers.
More &
iex> l = &length/1
&:erlang.length/1
iex> l.([1,3,5,7])
4
iex> len = &Enum.count/1
&Enum.count/1
iex> len.([1,2,3,4])
4
iex> m = &Kernel.min/2 # This is an alias for the Erlang function
&:erlang.min/2
iex> m.(99,88)
88
There’s a second form of the & function capture operator. You can give it the name and arity (number of parameters) of an existing function, and it will return an anonymous function that calls it.
More More &
iex> Enum.map [1,2,3,4], &(&1 + 1)
[2, 3, 4, 5]
iex> Enum.map [1,2,3,4], &(&1 * &1)
[1, 4, 9, 16]
iex> Enum.map [1,2,3,4], &(&1 < 3)
[true, true, false, false]
The & shortcut gives us a wonderful way to pass functions to other functions.
Thank you!
Exercise 1
list_concat.([:a, :b], [:c, :d]) #=> [:a, :b, :c, :d]
sum.(1, 2, 3) #=> 6
pair_tuple_to_list.( { 1234, 5678 } ) #=> [ 1234, 5678 ]
Create and run the functions that do the following:
Answer 1
iex(1)> list_concat = fn (list1, list2) -> list1 ++ list2 end
#Function<12.99386804/2 in :erl_eval.expr/5>
iex(2)> list_concat.([:a, :b], [:c, :d])
[:a, :b, :c, :d]
iex(3)> sum = fn(a, b, c) -> a + b + c end
#Function<18.99386804/3 in :erl_eval.expr/5>
iex(4)> sum.(1,2,3)
6
iex(9)> pair_tuple_to_list = fn a -> Tuple.to_list(a) end
#Function<6.99386804/1 in :erl_eval.expr/5>
iex(10)> pair_tuple_to_list.( { 1234, 5678 } )
[1234, 5678]
iex(11)>
Exercise 2
Functions-2
Write a function that takes three arguments. If the first two are zero, return “FizzBuzz.” If the first is zero, return “Fizz.” If the second is zero, return “Buzz.” Otherwise return the third argument. Do not use any language features that we haven’t yet covered in this book.
Answers 2
iex(11)> fizzbuzz = fn
...(11)> (0, 0, _) -> "FizzBuzz"
...(11)> (0, _, _) -> "Fizz"
...(11)> (_, 0, _) -> "Buzz"
...(11)> (_, _, a) -> a
...(11)> end
#Function<18.99386804/3 in :erl_eval.expr/5>
iex(12)> fizzbuzz.(1, 2, 3)
3
iex(13)> fizzbuzz.(0, 2, 3)
"Fizz"
iex(14)> fizzbuzz.(0, 0, 3)
"FizzBuzz"
iex(15)> fizzbuzz.(1, 0, 3)
"Buzz"
Exercise 3
Functions-3
The operator rem(a, b) returns the remainder after dividing a by b. Write a function that takes a single integer (n) and calls the function in the previous exercise, passing it rem(n,3), rem(n,5), and n. Call it seven times with the arguments 10, 11, 12, and so on. You should get “Buzz, 11, Fizz, 13, 14, FizzBuzz, 16.”
(Yes, it’s a FizzBuzz solution with no conditional logic.)[9]
Answers 3
iex(22)> use_fizzbuzz = fn(n) -> fizzbuzz.(rem(n,3), rem(n,5), n) end
#Function<6.99386804/1 in :erl_eval.expr/5>
iex(23)> use_fizzbuzz.(10)
"Buzz"
iex(24)> use_fizzbuzz.(11)
11
iex(25)> use_fizzbuzz.(12)
"Fizz"
iex(26)> use_fizzbuzz.(13)
13
iex(27)> use_fizzbuzz.(14)
14
iex(28)> use_fizzbuzz.(15)
"FizzBuzz"
Exercise 4
Write a function prefix that takes a string. It should return a new function that takes a second string. When that second function is called, it will return a string containing the first string, a space, and the second string.
iex> mrs = prefix.("Mrs")
#Function<erl_eval.6.82930912>
iex> mrs.("Smith")
"Mrs Smith"
iex> prefix.("Elixir").("Rocks")
"Elixir Rocks"
Answer 4
iex(35)> prefix = fn s1 -> fn s2 -> "#{s1} #{s2}" end end
#Function<6.99386804/1 in :erl_eval.expr/5>
iex(36)> mrs = prefix.("Mrs")
#Function<6.99386804/1 in :erl_eval.expr/5>
iex(37)> mrs.("Smith")
"Mrs Smith"
Exercise 5
iex(41)> Enum.map [1,2,3,4], fn x -> x + 2 end
[3, 4, 5, 6]
iex(42)> Enum.each [1,2,3,4], fn x -> IO.inspect x end
1
2
3
4
:ok
Use the & notation to rewrite the following.
Answer 5
iex(43)> Enum.map [1,2,3,4], &(&1 + 2)
[3, 4, 5, 6]
iex(46)> Enum.each [1,2,3,4], &(IO.inspect(&1))
1
2
3
4
:ok
Programming Elixir 1.3 Chapter 05
By Dustin McCraw
Programming Elixir 1.3 Chapter 05
- 1,041