What is Elixir and Why I wanted to learn ?
If you don't know already, Elixir is a dynamic, functional programming language designed for building scalable and maintainable applications. It is built on top of Erlang VM and compiles down to Erlang bytecode. Elixir has become one of the most loved programming language of recent times according to stackoverflow survey making this more exciting.
Functional programming was something that I knew by definition and the idea of using it practically sounded super fun! I did hear that I'd have to "unlearn" a bunch of things to pick up elixir, but another way to look at it would be to learn a new way of thinking about problems.
Interactive Shell
Once elixir is installed, we can use Elixir's interactive shell
which you can start by typing iex
in the terminal. Here, you can experiment with Elixir expressions and get results. IEX also provides a bunch of helpers which you can access by typing h()
.
Let's take a look at some features of Elixir now.
Pattern Matching and Immutability
So, where I come from when we use =
it's to assign a certain value to a variable. But now, where we're going this means something different. The operator performs something called pattern matching
and hence called match
operator. Here, like the name suggests both elements are compared to see if they match and if they do, the right side value is bound to the left side element.
#In OOP, this assigns value 10 to the variable a
a = 10
#In Elixir, value 10 is bound to a
iex> a = 10
Well, hold on. What if now I reverse the expression and play around with it?
#In OOP, we get a syntax error
10 = a
#In Elixir, this is valid since both the sides match
iex> 10 = a
10
#This will throw a match error
iex> 11 = a
** (MatchError) no match of right hand side value: 10
#Here, a is rebound with 11
iex> a = 11
Let's try this on more complex elements.
#Here, a = 1, b = 2, c = 3
iex> [a, b, c] = [1, 2, 3]
#Here, head = 1 and tail = [2, 3]
iex> [head | tail] = [1, 2, 3]
In the above example we were able to break the array into seperate elements. This is referred to as destructuring
.
Now that we have that cleared up, let's try to figure out what immutability
is. In Elixir all values are immutable
. When a variable once references some value it will always reference that value until it is discarded or gets garbage collected (which is actually fast here since we use different processes with its own heap). Any function that transforms this data will return a new copy of it.
Eeesh, yeah, that was a lot. Let's break it down. Consider the following snippet.
iex> list1 = [3, 2, 1]
iex> list2 = [4 | list1]
Now, we'd normally expect list2 = [4, 3, 2, 1]
where the value of list1 ie. [3, 2, 1] is copied into the list along with 4. But since we know that value of list1 will never change, list2 is now a new list with head as 4 and tail as list1.Another thing to know about lists in Elixir is that they're implemented as Linked Lists. This means that accessing the list length is an operation that will run in linear time (O(n)). For this reason, it is typically faster to prepend than to append.
Now that this makes a lot more sense, we can move on to writing functions.
Functions and Modules
Elixir is a functional programming language that makes functions the basic type. There are no classes here to bring structure to your program, we'll have to use functions
and organize them into modules
.
Modules basically provide namespaces for things we define. We can also have multiple functions inside a single module which we can reference from outside the module using the module's name as prefix.
Let's consider the following.
**Operations.exs**
defmodule Operations do
def sum(a, b) do
a + b
end
def sum(a, b, c) do
a + b + c
end
end
IO.puts Operations.sum(1, 2, 3)
IO.puts Operations.sum(3, 1)
# We run the program using the command
elixir math.exs
6
4
As you can see above, we were able to use mutli-clause functions
, as in have multiple definitions for the same function depending on what arguments are passed into it. The notation function_name/number_of_arguments(arity)
is used to represent functions. In the above example, sum/2 and sum/3 are totally separate functions as far as elixir is concerned.
Although this does allow us to invoke functions based on the number of arguments passed, let's look at what we have to do to distinguish based on arguments types.
Guard Clauses
These are predicates that we add to the function definition using one or more when keywords. Elixir now matches the number of arguments and then evaluates any when predicates, executing the function only if at least one predicate is true.
defmodule Guard do
def what_is(x) when is_number(x) do
IO.puts "#{x} is a number"
end
def what_is(x) when is_list(x) do
IO.puts "#{inspect(x)} is a list"
end”
end
Guard.what_is(100)
100 is a number
Recursion
Let's take a look at the following snippet.
for(i=0; i<5; i++) {
console.log("This violates immutability")
}
As the message suggests, this violates the concept of immutability because we try to mutate the variable i
. This iteration can be achieved in Elixir using recursion
which we can learn about when we write functions.
Looking back at for
loop earlier and knowing how to write functions and guard clauses, we can write a similar piece of code this way.
defmodule Recursion do
def print_multiple_times(msg, n) when n <= 1 do
IO.puts msg
end
def print_multiple_times(msg, n) do
IO.puts msg
print_multiple_times(msg, n - 1)
end
end
Recursion.print_multiple_times("This Works!", 3)
# This Works!
# This Works!
# This Works!
Here, when the function is first called, failing the guard clause n<=1
(since n = 3) the second function is executed. The second one in-turn calls itself and the first function respectively after decrementing the value of n.This is pretty much how iteration is taken care of using recursion.
The Pipe Operator |>
There have been instances where we need output of a particular function as an input to another which we wrote like this.
output_1 = function_1(input_1)
output_2 = function_2(output_1)
Using the pipe operator we can rewrite this so we take the result of the first function as the input for the second function.
output2 = input_1 |> function_1 |> function_2
There's so much more to functions and modules which you can read about here.
Well so far, I must say it has been fun and I can't wait to dig deeper. Stay tuned for part 2 :")
Cover photo by Marcus Wallis