Learning Elixir in Action
Examples that might help us learn Elixir...

A software developer who sometimes creates content — I'm very eager to get a deeper experience by implementing things from scratch.
I don't know anything about Elixir language, but when I want to learn a programming language, I try to make the syntax more understandable for myself with examples... I'm not even sure at this point if the title of this post should be "Learning Elixir in Action" or "Learning OCaml in Action"!?
Examples
Here we want to implement about 6 programs with different levels of difficulty — there are definitely many ways to implement them, but we will only write some of them (both due to lack of knowledge, and maybe to prevent the post from getting too long)!
Fizzbuzz
Choose two numbers and two labels. Now, if a number is divisible by the first number you chose, assign the first label to it; if a number is divisible by the second number you chose, assign the second label to it; and if a number is divisible by both of them, merge the labels together and assign it to that number...
defmodule Fizzbuzz do # Or `FizzBuzz`/`Main`/`M` and anything else
defp classify(n) when rem(n, 15) == 0, do: "Fizzbuzz"
defp classify(n) when rem(n, 3) == 0, do: "Fizz"
defp classify(n) when rem(n, 5) == 0, do: "Buzz"
defp classify(n), do: n
def fizzbuzz(a, b) do
for n <- a..b, do: classify(n)
|> IO.puts()
end
end
Fizzbuzz.fizzbuzz(1, 20)
defp means that the function cannot be invoked outside the module. In the fizzbuzz function, we defined a loop for the given range, and then we piped the current labeled number to IO.puts to see the result in the console.
Version 2 of this ridiculous game:
defmodule Fizzbuzz do
defp classify(n) when rem(n, 15) == 0, do: "Fizzbuzz"
defp classify(n) when rem(n, 3) == 0, do: "Fizz"
defp classify(n) when rem(n, 5) == 0, do: "Buzz"
defp classify(n), do: n
def fizzbuzz(a, b) do
a..b
|> Enum.map(&classify/1) # A reference to the classifier function that takes an argument
|> Enum.each(&IO.puts/1)
end
end
Fizzbuzz.fizzbuzz(1, 20)
As you can see in the above implementations, we can write comments in the code like most programming languages; but I will put the things that need more explanation in the main body of this post...
Implementations in Bash and C++ are available in my repository:
github.com/sheikhartin/fizzbuzz
Linear Search Algorithm
In search algorithms in general, we should return a Boolean to say whether the target element exists in the given list or not; and it's not bad to throw its index too...
defmodule LinearSearch do
def search(list, target) do
Enum.map(list, fn val ->
val == target
end)
|> Enum.any?()
|> IO.puts()
end
end
LinearSearch.search([1, 2, 3, 4, 5], 4)
Version 2:
defmodule LinearSearch do
def search(list, target) do
list
|> Enum.with_index()
|> Enum.find(fn val -> elem(val, 0) == target end)
|> case do
nil -> IO.puts "Not found!"
{_, idx} -> IO.puts "Found at index: #{idx}"
end
end
end
LinearSearch.search([1, 2, 3, 4, 5], 4)
Nonsense Authentication
Nothing, we just need to check the entered password in our database to find out whether the user exists or not. It is nonsense because by looking at the code you can see the username(s) and password(s)...
defmodule NonsenseAuthentication do
@username "sheikhartin"
@password "358"
def auth(username, password), do: username == @username && password == @password
end
NonsenseAuthentication.auth(
String.trim(IO.gets "Username: "),
String.trim(IO.gets "Password: ")
)
|> case do
false -> "You are not allowed to be here!"
true -> "Welcome back boss!"
end
|> IO.puts()
In version 2, we use a struct and another module to improve design and readability...
defmodule Hash do
def sha256(text), do: :crypto.hash(:sha256, text) |> Base.encode64()
end
defmodule User do
defstruct username: nil, hashed_password: nil
end
defmodule NonsenseAuthentication do
@users [
%User{username: "root", hashed_password: Hash.sha256("toor")},
%User{username: "sheikhartin", hashed_password: Hash.sha256("helloworld")},
]
def auth(username, password) do
hashed_password = Hash.sha256(password)
@users
|> Enum.find(fn user -> user.username == username end)
|> case do
nil -> {:error, "User not found!"}
%User{hashed_password: ^hashed_password} -> {:ok, "Welcome back `#{username}`!"}
_ -> {:error, "Incorrect password!"}
end
end
end
case NonsenseAuthentication.auth(
String.trim(IO.gets "Username: "),
String.trim(IO.gets "Password: ")
) do
{:ok, msg} -> IO.puts msg
{:error, reason} -> IO.puts reason
end
Calculator
The calculators that are implemented here should work based on user input to know more about IO...
defmodule BubbleSort do
defp bubble([head | rest]), do: bubble(rest, head, [])
defp bubble([], acc, sorted), do: Enum.reverse([acc | sorted])
defp bubble([head | rest], acc, sorted) when acc <= head, do:
bubble(rest, head, [acc | sorted])
defp bubble([head | rest], acc, sorted), do:
bubble(rest, acc, [head | sorted])
defp sort(list, 0), do: list
defp sort(list, n), do: list |> bubble |> sort(n - 1)
def sort(list) when is_list(list), do: sort(list, length(list))
end
[5, 8, 9, 11, 14, 15] = BubbleSort.sort([11, 15, 8, 5, 9, 14])
With @spec, you can define the type of parameters in a function and its return value.
Version 2 is more sensitive to errors:
defmodule Calculator do
@spec add(float, float) :: {:ok, float}
def add(a, b), do: {:ok, a + b}
@spec subtract(float, float) :: {:ok, float}
def subtract(a, b), do: {:ok, a - b}
@spec multiply(float, float) :: {:ok, float}
def multiply(a, b), do: {:ok, a * b}
@spec divide(float, float) :: {:ok, float} | {:error, String.t()}
def divide(a, b) when b != 0, do: {:ok, a / b}
def divide(_, _), do: {:error, "Cannot divide by zero!"} # Or `divide(_a, 0.0)`
@spec power(float, float) :: {:ok, float}
def power(a, b), do: {:ok, :math.pow(a, b)}
end
case {
IO.gets("Enter the first number: ")
|> String.trim()
|> Float.parse(),
IO.gets("And the second number: ")
|> String.trim()
|> Float.parse(),
} do
{{number1, ""}, {number2, ""}} ->
for {operation, result} <- [
{:addition, Calculator.add(number1, number2)},
{:subtraction, Calculator.subtract(number1, number2)},
{:multiplication, Calculator.multiply(number1, number2)},
{:division, Calculator.divide(number1, number2)},
{:power, Calculator.power(number1, number2)},
] do
case result do
{:ok, value} ->
IO.puts "#{Atom.to_string(operation) |> String.capitalize()}: #{value}"
{:error, reason} ->
IO.puts "#{Atom.to_string(operation) |> String.capitalize()}: #{reason}"
end
end
_ -> IO.puts "An error has occurred; make sure you enter valid numbers..."
end
I don't have a specific style guide yet, and I also don't know if the above solution is standard or not!?
Bubble Sort Algorithm
defmodule BubbleSort do
defp bubble([head | rest]), do: bubble(rest, head, [])
defp bubble([], acc, sorted), do: Enum.reverse([acc | sorted])
defp bubble([head | rest], acc, sorted) when acc <= head, do:
bubble(rest, head, [acc | sorted])
defp bubble([head | rest], acc, sorted), do:
bubble(rest, acc, [head | sorted])
defp sort(list, 0), do: list
defp sort(list, n), do: list |> bubble |> sort(n - 1)
def sort(list) when is_list(list), do: sort(list, length(list))
end
[5, 8, 9, 11, 14, 15] = BubbleSort.sort([11, 15, 8, 5, 9, 14])
Maybe it is better to use curr instead of acc, and tail instead of rest as the names of our variables.
For the first time, I want to analyze the code function by function!
bubble/1: This function initializes the bubble sort process for a single pass. It calls
bubble/3with the rest of the list, the head as the current element to be compared, and an empty list as the accumulator for sorted elements.bubble/3: Reverses the accumulator list and prepends the current element to it. In fact, it puts the largest element at the end of the sorted list.
bubble/3 when acc <= head: It calls itself recursively with the rest of the list, the head as the new current element, and the accumulator with the current element added to it.
bubble/3: This function continues the bubble sort pass when the current element is greater than the next element.
sort/2: This function returns the list as is, indicating that the sorting is complete.
sort/2: Recursively calls itself, decrementing
nby 1.sort/1: Calls the
sort/2function with the list and its length, which determines how many passes are needed to sort the list.
Rock, Paper, Scissors
You know it better than me!
defmodule RockPaperScissors do
@moves [
:rock,
:paper,
:scissors,
]
def play() do
for _ <- 0..5 do
computer_move = Enum.random(@moves)
user_move = case IO.gets("Choose your move [1/Rock 🪨, 2/Paper 𐇳, 3/Scissors ✂]: ")
|> String.trim()
|> Integer.parse() do
{move, ""} when move in 1..3 -> Enum.at(@moves, move-1)
_ -> :invalid
end
case {computer_move, user_move} do
{move, move} ->
IO.puts "It's a tie! Both chose #{move}."
{:rock, :scissors} ->
IO.puts "Computer wins! Rock 🪨 beats scissors ✂."
{:scissors, :paper} ->
IO.puts "Computer wins! Scissors ✂ beats paper 𐇳."
{:paper, :rock} ->
IO.puts "Computer wins! Paper 𐇳 beats rock 🪨."
{:scissors, :rock} ->
IO.puts "You win! Rock 🪨 beats scissors ✂."
{:paper, :scissors} ->
IO.puts "You win! Scissors ✂ beats paper 𐇳."
{:rock, :paper} ->
IO.puts "You win! Paper 𐇳 beats rock 🪨."
{_, :invalid} ->
IO.puts "Invalid move. Please enter 1, 2, or 3."
end
end
end
end
RockPaperScissors.play # Or `RockPaperScissors.play()`
In the above implementation, I tried to break my code into smaller parts... This is very good, but I am not a fan of using variables too much!
Version 2 for easier recognition of win conditions:
defmodule RockPaperScissors do
@moves [
:rock,
:paper,
:scissors,
]
@win_conditions %{
rock: :scissors,
paper: :rock,
scissors: :paper,
}
def play(n \\ 5) when n > 0 do
for _ <- 0..n-1 do
computer_move = Enum.random(@moves)
user_move = case IO.gets("Choose your move [1/Rock, 2/Paper, 3/Scissors]: ")
|> String.trim()
|> Integer.parse() do
{move, ""} when move in 1..3 -> Enum.at(@moves, move-1)
_ -> :invalid
end
cond do
user_move == :invalid ->
IO.puts "Invalid move. Please enter 1, 2, or 3."
computer_move == user_move ->
IO.puts "It's a tie! Both chose #{computer_move}."
@win_conditions[computer_move] == user_move ->
IO.puts "Computer wins! #{computer_move |> Atom.to_string() |> String.capitalize()} beats #{user_move}."
@win_conditions[user_move] == computer_move ->
IO.puts "You win! #{user_move |> Atom.to_string() |> String.capitalize()} beats #{computer_move}."
end
end
end
end
RockPaperScissors.play
The default value considered as the number of times to play the game is 5. And yeah, it doesn't look very functional!
Implementation in Bash:
github.com/sheikhartin/rock-paper-scissors
Last Words
In my opinion, to learn a language (of any kind), you have to work many hours so that you don't think about the syntax anymore... I'm still not sure if I should continue learning this language or not; but after a few years of procedural programming, it makes me think differently about how to code, which is lovely and interesting! Maybe I should write a more important project with Elixir and then decide what to do...
Other learning resources:


