Elixir is built on Erlang, and in both Erlang and Elixir a function is not just identified by its name, but by its name and arity. Remember: Everything in Elixir is an expression.
To give you a clear example of this, below we have four functions, but all defined with different arity.
def sum, do: 0 def sum(a), do: a def sum(a, b), do: a + b def sum(a, b, c), do: a + b + c
To provide a concise way to work with data, we can use guard expressions like so:
def sum(a, b) when is_integer(a) and is_integer(b) do a + b end def sum(a, b) when is_list(a) and is_list(b) do a ++ b end def sum(a, b) when is_binary(a) and is_binary(b) do a <> b end sum 1, 2 #=> 3 sum [1], [2] #=> [1, 2] sum "a", "b" #=> "ab"
Guard expressions such as when is_binary(a)
allow us to check for the correct type before performing an operation.
Unlike Erlang, Elixir allows for default values in its functions via usage of the \\
syntax like so:
def mul_by(x, n \\ 2) do x * n end mul_by 4, 3 #=> 12 mul_by 4 #=> 8
Anonymous Functions
In the previous part, we discussed anonymous functions briefly as a data type. To elaborate on this further, take this example:
sum = fn(a, b) -> a + b end sum.(4, 3) #=> 7 square = fn(x) -> x * x end Enum.map [1, 2, 3, 4], square #=> [1, 4, 9, 16]
We see this powerful shorthand here on the first line fn(a, b) -> a + b end
. With this we are able to produce a basic operation sum.(4, 3)
and get the output in just two lines of code.
Now, looking to the square method, fn(x) -> x * x end
, can it really be any simpler? Working now with the map
, we can perform the square anonymous function over the whole map—again in just two lines of code!
Pattern Matching
Arithmetic is all fun and good, but let's see what we can do with text.
f = fn {:simon} = tuple -> IO.puts "Good morning Sir #{inspect tuple}" {:kate} = tuple -> IO.puts "Lovely day #{inspect tuple}" [] -> "Empty" end f.([]) #=> "Empty" f.({:simon}) #=> "Good morning Sir {:simon}" f.({:kate}) #=> "Lovely day {:kate}"
Here, with pattern matching we can define several outcomes in our control flow, again in hardly any code. Elixir's syntax is rapid to work with and mightily powerful, as we will see in the next example.
First-Class Functions
The anonymous functions we just covered are first-class values. This means that they can be passed as arguments to other functions and also can serve as a return value themselves. There is a special syntax to work with named functions in the same way:
defmodule Math do def square(x) do x * x end end Enum.map [1, 2, 3], &Math.square/1 #=> [1, 4, 9]
Here we define a Math
module with defmodule
and define the square function. Then we can use this in conjunction with the map method demonstrated earlier and the Math
module we just defined. We use the same operator &
, allowing us to pass our function Math.square/1
to capture the square function's output for each entry in our list.
That's a whole lot of power for just one line. This is referred to as a partial function capture in Elixir.
Control Flow
We use the constructs if
and case
to control flow in Elixir. Like everything in Elixir, if
and case
are expressions.
For pattern matching, we use case
:
case {x, y} do {:a, :b} -> :ok {:b, :c} -> :good other -> other end
And for comparison logic, we use if
:
test_fun = fn(x) -> cond do x > 10 -> :greater_than_ten x < 10 and x > 0 -> :less_than_ten_positive x < 0 or x === 0 -> :zero_or_negative true -> :exactly_ten end end test_fun.(44) #=> :greater_than_ten test_fun.(0) #=> :zero_or_negative test_fun.(10) #=> :exactly_ten
For ease of use, Elixir also provides an if
function that resembles many other languages and is useful when you need to check if one clause is true or false:
if x > 10 do :greater_than_ten else :not_greater_than_ten end
Dynamic Functions
This is possible in Elixir via usage of the unquote
method mentioned earlier. For example, to check some hard-coded Admins in our system, we can do the following:
defmodule Admins do [:manager, :super] |> Enum.each fn level -> def unquote(:"check_#{level}")() do IO.inspect("Access Level: #{unquote(level)}") end end end Admins.check_manager # => "Access Level: manager" Admins.check_super # => "Access Level: super"
Here we have created the methods Admins.check_manager
and Admins.check_super
from the atom names.
Conclusion
Everything is an expression in Elixir, and that means we can get a whole heap of power out of writing very little code.
For me, Elixir looks similar to CoffeeScript, Ruby, Python or any minimalist syntax as the form is so direct and to the point, but Elixir is far more powerful than these languages due to the meta-programming aspect.
Going forward, we will see how to utilise control flow and functions more to create interactivity in our app.
No comments:
Post a Comment