Table of Contents
- 1. Elixir elixir
- 1.1. Debug in IEx
- 1.2. Inspect failing tests
- 1.3. Create custom sigils
- 1.4. Get a value from nested maps
- 1.5. Get all values for a given map key in a list
- 1.6. Arithmetic operators as lambda functions
- 1.7. Pattern match [ vs ] destructure
- 1.8. Data Comprehension with filters
- 1.9. Comprehension with binary strings
- 1.10. Replace keys in map
- 1.11. Group things
- 1.12. Find N slowest tests
- 1.13. Custom inspection for structure
- 1.14. Querying Ecto for a subset of the fields along with relation info ecto
1 Elixir elixir
This is a notebook for all the useful info I can find or read about Elixir.
1.1 Debug in IEx
break!
is the way to go when you want to set a break point in your
code from within IEx:
iex(1)> break!(MyModule.my_func/1) iex(1)> break!(MyModule, :my_func, 1)
You'll often want to break on a recursive function in which case you can pass an additional argument specifying how many time you want to stop in this function:
break!(MyModule, :my_func, 1, 10)
You can also list all breakpoints:
iex(1)> breaks() ID Module.function/arity Pending stops ---- ----------------------- --------------- 1 MyModule.my_func/1 1
Once a break point is set you can go through your code and inspect it:
iex(2)> MyModule.my_func("args") Break reached: MyModule.my_func/1 (lib/my_module.ex:2) 1: defmodule MyModule do 2: def my_func(args) do 3: "#{args}" 4: end pry(1)> args "args"
When you're done with debugging you can resume the execution by cleaning break points:
pry(2)> respawn
1.2 Inspect failing tests
Sometimes it can be handy to inspect our failing test using a
debugger. Elixir covers our back by allowing us to use IEx.pry
in
tests:
require IEx; IEx.pry
You can then run iex -S mix test
with the --trace
option:
iex -S mix test --trace path/to/test.exs
1.3 Create custom sigils
Sigils calls a function to know how to act.
You can define a sigil this way:
defmodule MySigils do # returns the downcased string, if option l is given then returns the list of downcased letters def sigil_l(string,[]), do: String.downcase(string) def sigil_l(string,[?l]), do: String.downcase(string) |> String.graphemes # returns the upcased string, if option l is given then returns the list of downcased letters def sigil_u(string,[]), do: String.upcase(string) def sigil_u(string,[?l]), do: String.upcase(string) |> String.graphemes end
1.4 Get a value from nested maps
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: "nico"}} # Example of Nested Map first_name = get_in(nested_map, [:name, :first_name]) # Retrieving the Key
It will return nil
if the key is missing.
1.5 Get all values for a given map key in a list
You can also use get_in
in conjunction with Access.all/0
to get
all the values in a map for a given key:
iex> users = [%{"user" => %{"first_name" => "john", "age" => 23}}, %{"user" => %{"first_name" => "hari", "age" => 22}}, %{"user" => %{"first_name" => "mahesh", "age" => 21}}] iex> get_in(users, [Access.all(), "user", "age"]) [23, 22, 21] iex> get_in(users, [Access.all(), "user", "first_name"]) ["john", "hari", "mahesh"]
You can use the same technique to update all the values for a given key:
iex> user = %{name: "john", books: [%{name: "my soul", type: "tragedy"}, %{name: "my heart", type: "romantic"}, %{name: "my enemy", type: "horror"}]} iex> update_in(user, [:books, Access.all(), :name], &String.upcase/1) %{books: [%{name: "MY SOUL", type: "tragedy"}, %{name: "MY HEART", type: "romantic"}, %{name: "MY ENEMY", type: "horror"}], name: "john"}
1.6 Arithmetic operators as lambda functions
In Elixir every operator is a macro. It makes it possible to use them as 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
1.7 Pattern match [ vs ] destructure
=
is dedicated to pattern matching but you cannot do [a, b, c] = [1,
2, 3, 4] because the terms count is different so it will raise a
MatchError
.
iex(11)> [a, b, c] = [1, 2, 3, 4] (MatchError) no match of right hand side value: [1, 2, 3, 4]
If you want to do that, you have to use destructure/2
:
iex(1)> destructure([a, b, c], [1, 2, 3, 4]) [1, 2, 3] iex(2)> {a, b, c} {1, 2, 3}
iex> destructure([a, b, c], [1]) iex> {a, b, c} {1, nil, nil}
1.8 Data Comprehension with filters
When using a for
loop (a comprehension), you can add conditions to
filter values used:
iex> for x <- [1, 2, 3, 4], y <- [5, 6, 7, 8], rem(x * y, 2) == 0, do: {x, y, x * y} [{1, 5, 5}, {1, 7, 7}, {3, 5, 15}, {3, 7, 21}]
In this example rem(x * y, 2) == 0
is acting as a filter, so tuples
that are not matching the condition aren't processed.
1.9 Comprehension with binary strings
You can also use comprehension with strings using bitstring notation:
iex> string = "Bounga" "Bounga" iex> for << x <- string >>, do: x + 1 'Cpvohb'
1.10 Replace keys in map
Sometimes you'll have to modify keys for a provided map. Most of the time that's because you're getting a map out of an API call and you want to change key names for your internal use:
iex> location = %{latitude: 38.8951, longitude: -77.0364} iex> Enum.into(location, %{}, fn {:latitude, lat} -> {:lat, lat} {:longitude, long} -> {:long, long} end) %{lat: 38.8951, long: -77.0364}
BTW you can also use Enum.into/2
to simply merge things:
Enum.into([1, 2], []) [1, 2] iex> Enum.into([a: 1, b: 2], %{}) %{a: 1, b: 2} iex> Enum.into(%{a: 1}, %{b: 2}) %{a: 1, b: 2} iex> Enum.into([a: 1, a: 2], %{}) %{a: 2}
1.11 Group things
Let say you have a list of structs or well-defined maps and you want
to group them by a given key value, you'll use Enum.group_by/2
:
iex> hotel_bookings = [ %{name: "John", country: "usa", booking_status: :success}, %{name: "Hari", country: "india", booking_status: :fail}, %{name: "Mahesh", country: "austria", booking_status: :pending}, %{name: "Ruchi", country: "paris", booking_status: :success}, %{name: "Nitesh", country: "malasia", booking_status: :success}, %{name: "Manoj", country: "japan", booking_status: :fail}, %{name: "Akhilesh", country: "china", booking_status: :pending}, %{name: "Rajesh", country: "india", booking_status: :fail}, %{name: "Payal", country: "london", booking_status: :success}, %{name: "Kumar", country: "france", booking_status: :fail} ] iex> Enum.group_by(hotel_bookings, &Map.get(&1, :booking_status)) %{ fail: [ %{booking_status: :fail, country: "india", name: "Hari"}, %{booking_status: :fail, country: "japan", name: "Manoj"}, %{booking_status: :fail, country: "india", name: "Rajesh"}, %{booking_status: :fail, country: "france", name: "Kumar"} ], pending: [ %{booking_status: :pending, country: "austria", name: "Mahesh"}, %{booking_status: :pending, country: "china", name: "Akhilesh"} ], success: [ %{booking_status: :success, country: "usa", name: "John"}, %{booking_status: :success, country: "paris", name: "Ruchi"}, %{booking_status: :success, country: "malasia", name: "Nitesh"}, %{booking_status: :success, country: "london", name: "Payal"} ] }
You can also go further and get a list one of a key value only (here we'll extract the names) in groups rather than the whole map:
iex> Enum.group_by(hotel_bookings, &Map.get(&1, :booking_status), &Map.get(&1, :name)) %{ fail: ["Hari", "Manoj", "Rajesh", "Kumar"], pending: ["Mahesh", "Akhilesh"], success: ["John", "Ruchi", "Nitesh", "Payal"] }
1.12 Find N slowest tests
It can be useful to find out what are your slowest tests in your test
suite so you can act accordingly, Mix
helps with that:
mix test --slowest 3
will show us the three slowest tests.
1.13 Custom inspection for structure
When you're examining your code, you'll often find yourself using
IO.inspect
to get info about your structure.
In Elixir, when you define a structure you can also declare its
implementation for the Inspect
module:
defmodule Student do defstruct name: "John", place: "Earth" end defimpl Inspect, for: Student do def inspect(student, _opts) do """ -----------|--------------------- Name : #{student.name} -----------|--------------------- Place : #{student.place} -----------|--------------------- """ end end iex> %Student{} -----------|--------------------- Name : John -----------|--------------------- Place : Earth -----------|---------------------
1.14 Querying Ecto for a subset of the fields along with relation info ecto
When querying your database you often don't need to retrieve all the data for each row, you use-case may only need a subset of it.
Ecto provides a clean way to do that even if you need to also retrieve relation computed info along in a single query:
query = from u in "users", join: c in "comments", on: c.user_id == u.id, where: u.active == true, group_by: [u.name, u.email], select: %{name: u.name, email: u.email, comments_count: count(c.id)} Repo.all(query) # => [ %{comments_count: 23, email: "gandalf@greypilgrim.com", name: "Gandalf"}, %{comments_count: 45, email: "aragorn@rangers.com", name: "Aragorn"}, %{comments_count: 566, email: "sonofgloin@lonelymountain.com", name: "Gimli"}, %{...}, ... ]