Killer Elixir-Tips

Killer Elixir-Tips

All Contributors

Elixir Tips and Tricks from the Experience of Development. Each part consists of 10 Unique Tips and Tricks with a clear explanation with live examples and outputs. These tips will speed up your development and save you time in typing code as well.

You can read specific parts with following links...

</div>

All Photos by Form on Unsplash

Explore All Parts

Part 1

1. Multiple [ OR ]

This is just the other way of writing Multiple OR conditions. This is not the recommended approach because in the regular approach when the condition evaluates to true , it stops executing the remaining conditions which saves evaluation time unlike this approach which evaluates all conditions first in list. This is just bad but good for discoveries.

# Regular Approach
find = fn(x) when x>10 or x<5 or x==7 -> x end
# Our Hack
hell = fn(x) when true in [x>10,x<5,x==7] -> x end

2. i( term) Elixir Term Type and Meta Data

Prints information about the data type of any given term. Try that in iex and see the magic.

iex> i(1..5)

3. iex Custom Configuration - iex Decoration

Copy the content into a file and save the file as .iex.exs in your ~ home directory and see the magic. You can also download the file HERE

# IEx.configure colors: [enabled: true]
# IEx.configure colors: [ eval_result: [ :cyan, :bright ] ]
IO.puts IO.ANSI.red_background() <> IO.ANSI.white() <> " ❄❄❄ Good Luck with Elixir ❄❄❄ " <> IO.ANSI.reset
Application.put_env(:elixir, :ansi_enabled, true)
IEx.configure(
colors: [
eval_result: [:green, :bright] ,
eval_error: [[:red,:bright,"Bug Bug ..!!"]],
eval_info: [:yellow, :bright ],
],
default_prompt: [
"\e[G", # ANSI CHA, move cursor to column 1
:white,
"I",
:red,
"❤" , # plain string
:green,
"%prefix",:white,"|",
:blue,
"%counter",
:white,
"|",
:red,
"▶" , # plain string
:white,
"▶▶" , # plain string
# ❤ ❤-»" , # plain string
:reset
] |> IO.ANSI.format |> IO.chardata_to_string
)
img

4. Creating Custom Sigils and Documenting

Each x sigil calls its respective sigil_x definition

Defining Custom Sigils

defmodule MySigils do
#returns the downcasing string if option l is given then returns the list of downcase letters
def sigil_l(string,[]), do: String.downcase(string)
def sigil_l(string,[?l]), do: String.downcase(string) |> String.graphemes
#returns the upcasing string if option l is given then returns the list of downcase letters
def sigil_u(string,[]), do: String.upcase(string)
def sigil_u(string,[?l]), do: String.upcase(string) |> String.graphemes
end

usage

Load the module into iex

iex> import MySigils
iex> ~l/HELLO/
"hello"
iex> ~l/HELLO/l
["h", "e", "l", "l", "o"]
iex> ~u/hello/
"HELLO"
iex> ~u/hello/l
["H", "E", "L", "L", "O"]

5. Custom Error Definitions

Define Custom Error

defmodule BugError do
defexception message: "BUG BUG .." # message is the default
end

Usage

iex bug_error.ex
iex> raise BugError
** (BugError) BUG BUG ..
iex> raise BugError, message: "I am Bug.." #here passing the message dynamic
** (BugError) I am Bug..

6. Get a Value from Nested Maps Easily

The get_in function can be used to retrieve a nested value in nested maps using a list of keys.

nested_map = %{ name: %{ first_name: "blackode"} } # Example of Nested Map
first_name = get_in(nested_map, [:name, :first_name]) # Retrieving the Key
# Returns nil for missing value
nil = get_in(nested_map, [:name, :last_name]) # returns nil when key is not present

Read docs: Kernel.get_in/2

7. With Statement Benefits

The special form with is used to chain a sequence of matches in order and finally return the result of do: if all the clauses match. However, if one of the clauses does not match, the result of the miss matched expression is immediately returned.

iex> with 1 <- 1+0,
2 <- 1+1,
do: IO.puts "all matched"
"all matched"
iex> with 1 <- 1+0,
2 <- 3+1,
do: IO.puts "all matched"
4
## since 2 <- 3+1 is not matched so the result of 3+1 is returned

8. Writing Protocols

Define a Protocol

A Protocol is a way to dispatch to a particular implementation of a function based on the type of the parameter. The macros defprotocol and defimpl are used to define Protocols and Protocol implementations respectively for different types in the following example.

defprotocol Triple do
def triple(input)
end
defimpl Triple, for: Integer do
def triple(int) do
int * 3
end
end
defimpl Triple, for: List do
def triple(list) do
list ++ list ++ list
end
end

Usage

Load the code into iex and execute

iex> Triple.triple(3)
9
Triple.triple([1, 2])
[1, 2, 1, 2, 1, 2]

9. Ternary Operator

There is no ternary operator like true ? "yes" : "no" . So, the following is suggested.

"no" = if 1 == 0, do: "yes", else: "no"

10. Advantage of Kernel.||

When using pipelines, sometimes we break the pipeline for or operation. For example:

result = :input
|> do_something
|> do_another_thing
# Bad
result = (result || :default_output)
|> do_something_else

Indeed, || is only a shortcut for Kernel.|| . We can use Kernel.|| in the pipeline instead to avoid breaking the pipeline.

The code above will be:

result = :input
|> do_something
|> do_another_thing
|> Kernel.||(:default_output) #<-- This line
|> do_something_else

This above tip is from qhwa

Part 1 Part 2 Part 3 Part 4 Part 5 Part 6 Part 7 Part 8 Part 9

Part 2

1  Code Grouping

Code grouping stands for something great. It shows you how your code is grouped when you write multiple lines of code in single line with out using braces. It will be more clear with the following example.

one 1 |> two()

If you want to see how this line of code is grouped into, you can check in the following format..

quote(do: one 1 |> two()) |> Macro.to_string |> IO.puts
one(1 |> two())

So, by using the quote and Macro.to_string you can see how our code is grouped.

This tip came out in discussion with the creator of Ecto MichalMuskala in the Elixir forum.

2  Elixir Short Circuit Operators && — ||

These replaces the nested complicated conditions. These are my best friends in the situations dealing with more complex comparisons. Trust me you gonna love this.

The || operator always returns the first expression which is true. Elixir doesn’t care about the remaining expressions, and won’t evaluate them after a match has been found.

||

false || nil || :blackode || :elixir || :jose

Here if you observe the first expression is false next nil is also false in elixir next :blackode which evaluates to true and its value is returned immediately with out evaluating the :elixir and :jose . Similarly if all the statements evaluates to false the last expression is returned.

&&

iex> true && :true && :elixir && 5
5
iex> nil && 100
nil
iex> salary = is_login && is_admin && is_staff && 100_000

This && returns the second expression if the first expression is true or else it returns the first expression with out evaluating the second expression. In the above examples the last one is the situation where we encounter to use the && operator.

3  Comparing two different data types

I have self experience with this. When I was a novice in elixir, I just compared "5" > 4 unknowingly by an accident and to my surprise it returned with true.

In Elixir every term can compare with every other term. So one has to be careful in comparisons.

img
iex> x = "I am x "
"I am x "
iex> x > 34
true
iex> x > [1,2,3]
true
iex> [1, 2, 3] < 1234567890
false

Order of Comparison

number < atom < reference < fun < port < pid < tuple < map < list < bitstring (binary)

4  Arithmetic Operators as Lambda functions

When I see this first time, I said to my self “Elixir is Crazy”. This tip really saves time and it resembles your smartness. In Elixir every operator is a macro. So, we can use them as lambda functions.

iex> Enum.reduce([1,2,3], 0, &+/2)
6
iex> Enum.reduce([1,2,3], 0, &*/2)
0
iex> Enum.reduce([1,2,3], 3, &*/2)
18
iex> Enum.reduce([1,2,3], 3, &-/2)
-1
iex> Enum.reduce([1,2,3], 3, &//2)
0.5

5  Binary pattern matching

This is my recent discovery. I always encounter a situation like converting "$34.56" which is a string and I suppose do arithmetic operations. I usually do something like this before binary pattern matching..

img
iex> value = "$34.56"
iex ... |> String.split("$")
iex ... |> tl
iex ... |> List.first
iex ... |> String.to_float
34.56

Tip Approach

This tip makes my day easy. I recently used this is in one of my projects.

iex> "$" <> value = "$34.56"
"$34.56"
iex> String.to_float value
34.56

6  Recompiling Project

At beginning stage, I used to press ^c ^c twice and restart shell as iex -S mix whenever I make changes to the project files. If you are doing this now, stop it right now. You can just recompile the project.

iex -S mix
iex> recompile

Warning: The changes in the config/config.ex are not reflected. You have to restart the shell again.

7  Logger Module

Logger is one of my favorite modules. This come by default and starts along with your application. You have to just require this module. When I was new to Elixir, I always used to write the console outputs as IO.puts "This is value of data" for code debugging but, those lines get mixed up with other lines of information and It became hard to trace those lines.

This Logger module solved my problem. It has many features but, I use three definitions very often warn info and error Each definition prints the information with different colors which is more easy to find the statement at a glance.

The best side of this module is that it prints along with the time, that means it also prints the time while executing your statement. So, you can know the direction of flow of execution.

Before using the Logger module one has to do require Logger so all macros will be loaded inside your working module.

img
iex> require Logger
Logger
iex> Logger.info "This is the info"
15:04:33.102 [info] This is the info
:ok
iex> Logger.warn "This is warning"
15:04:56.712 [warn] This is warning
:ok
iex> Logger.error "This is error"
15:05:19.570 [error] This is error
:ok

This tip is from Anwesh Reddy

8  Finding All Started Applications

We can check the all the applications which are started along with our application. Sometimes we have to check whether a particular application is started or not. So, it helps you in those situations. If you are a beginner, you don’t feel will be using this much. But I am pretty sure of this tip will become handy when you work with multiple applications.

iex> Application.started_applications
[{:logger, 'logger', '1.4.0'}, {:iex, 'iex', '1.4.0'},
{:elixir, 'elixir', '1.4.0'}, {:compiler, 'ERTS CXC 138 10', '7.0.1'},
{:stdlib, 'ERTS CXC 138 10', '3.0.1'}, {:kernel, 'ERTS CXC 138 10', '5.0.1'}]

9  Advantage of Map keys as :atoms and binary(strings)

Before I let you to use this tip, I just want to remind you that :atoms are not garbage collected. Atom keys are great! If you have a fixed number of them defined statically in your code, you are in no danger. What you should not do is convert user supplied input into atoms without sanitizing them first because it can lead to out of memory. You should also be cautious if you create dynamic atoms in your code.

But, you can use the . to retrieve the data from the keys as map.key unlike the usual notation like map["key"] . That really saves on typing. But, I don’t encourage this because, as programmers we should really care about memory.

img
iex> map = %{name: "blackode", blog: "medium"}
%{blog: "medium", name: "blackode"}
iex> map.name
"blackode"
iex> map.blog
"medium"

Be sure that when you try to retrieve a key with . form which is not present in the map, it will raise an key error instead of returning the nil unlike the map["key"] which returns nil if key is not present in map

iex> map["age"]
nil
iex> map.age
Bug Bug ..!!** (KeyError) key :age not found in: %{blog: "medium", name: "blackode"}
Bug Bug ..!!

10 Color Printing

Elixir >=1.4.0 has ANSI color printing option to console. You can have great fun with colors. You can also provide background colors.

iex> import IO.ANSI
iex> IO.puts red <> "red" <> green <> " green" <> yellow <> " yellow" <> reset <> " normal"
iex> IO.puts Enum.join [red, "red", green, " green", yellow, " yellow", reset, " normal"]
red green yellow normal

The red prints in red color, green in green color, yellow in yellow color and normal in white. Have fun with colors…

For more details on color printing check Printex module which I created for fun in Elixir.

img
img

Part 3

Part 1 Part 2 Part 3 Part 4 Part 5 Part 6 Part 7 Part 8 Part 9

1. Functions as Guard Clauses

We cannot make use of the functions as guard clauses in elixir. It means, when cannot accept functions that returns Boolean values as conditions. Consider the following lines of code…

defmodule Hello do
def hello(name, age) when is_kid(age) do
IO.puts "Hello Kid #{name}"
end
def hello(name, age) when is_adult(age) do
IO.puts "Hello Mister #{name}"
end
def is_kid age do
age < 12
end
def is_adult age do
age > 18
end
end

Here we defined a module Hello and a function hello that takes two parameters of name and age. So, based on age I am trying IO.putsaccordingly. If you do so you will get an error saying….

** (CompileError) hello.ex:2: cannot invoke local is_kid/1 inside guard
hello.ex:2: (module)

This is because when cannot accept functions as guards. We need to convert them to macros Lets do that…

defmodule MyGuards do
defmacro is_kid age do
quote do: unquote(age) < 12
end
defmacro is_adult age do
quote do: unquote(age) > 18
end
end
# order of module matters here.....
defmodule Hello do
import MyGuards
def hello(name, age) when is_kid(age) do
IO.puts "Hello Kid #{name}"
end
def hello(name, age) when is_adult(age) do
IO.puts "Hello Mister #{name}"
end
def hello(name, age) do
IO.puts "Hello Youth #{name}"
end
end

In the above lines of code, we wrapped all our guards inside a module MyGuards and make sure the module is top of the module Hello so, the macros first gets compiled. Now compile and execute you will see the following output..

iex> Hello.hello "blackode", 21
Hello Mister blackode
:ok
iex> Hello.hello "blackode", 11
Hello Kid blackode
:ok

Starting on Elixir v1.6, you can use defguard/1.

The defguard is also a macro. You can also create private guards with defguardp. Hope, you got the point here. Consider the following example.

NOTE: The defguard and defguardp should reside inside the module like other macros. It raises a compile time error, if some thing that don't fit in the guard clause section when.

Suppose, you want to check the given number is either three or five, you can define the guard as following.

defmodule Number.Guards do
defguard is_three_or_five(number) when (number===3) or (number===5)
end

Usage

import Number.Guards
defmodule Hello do
def check_favorite_number(num) when is_three_or_five(num) do
IO.puts "The given #{num} is on of my favourite numbers"
end
def check_favorite_number(_num), do: IO.puts "Not my favorite number"
end

You can also use them inside your code logic as they results boolean value.

iex> import Number.Guards
Number.Guards
iex> is_three_or_five(5)
true
iex> is_three_or_five(3)
true
iex> is_three_or_five(1)
false

Check the following execution screen shot.

ScreenShot Defguard Execution

2. Finding the presence of Sub-String

Using =~ operator we can find whether the right sub-string present in left string or not..

iex> "blackode" =~ "kode"
true
iex> "blackode" =~ "medium"
false
iex> "blackode" =~ ""
true

3. Finding whether Module is loaded or not

Sometimes, we have to make sure that certain module is loaded before making a call to the function. We are supposed to ensure the module is loaded.

Code.ensure_loaded? <Module>
iex> Code.ensure_loaded? :kernel
true
iex> Code.ensure_loaded :kernel
{:module, :kernel}

Similarly we are having ensure_compile to check whether the module is compiled or not…

4. Binary to Capital Atom

Elixir provides a special syntax which is usually used for module names. What is called a module name is an uppercase ASCII letter followed by any number of lowercase or uppercase ASCII letters, numbers, or underscores.

This identifier is equivalent to an atom prefixed by Elixir. So in the defmodule Blackode example Blackode is equivalent to :"Elixir.Blackode"

When we use String.to_atom "Blackode" it converts it into :Blackode But actually we need something like “Blackode” to Blackode. To do that we need to use Module.concat

iex(2)> String.to_atom "Blackode"
:Blackode
iex(3)> Module.concat Elixir,"Blackode"
Blackode

In Command line applications whatever you pass they convert it into binary. So, again you suppose to do some casting operations …

5. Pattern match [ vs ] destructure.

We all know that = does the pattern match for left and right side. We cannot do [a, b, c] = [1, 2, 3, 4] this raise a MatchError

iex(11)> [a, b, c] = [1, 2, 3, 4]
** (MatchError) no match of right hand side value: [1, 2, 3, 4]

We can use destructure/2 to do the job.

iex(1)> destructure [a, b, c], [1, 2, 3, 4]
[1, 2, 3]
iex(2)> {a, b, c}
{1, 2, 3}

If the left side is having more entries than in right side, it assigns the nil value for remaining entries..

iex> destructure([a, b, c], [1])
iex> {a, b, c}
{1,