Powered by AppSignal & Oban Pro

Learn Elixir

livebook/learn-1.livemd

Learn Elixir

Numbers

[
  # Integer
  10,
  # hex
  0x0A,
  # float
  10.0
]

Please don’t write this ugly code

embrace immutability, adopt trasnforming structs

use pipe operator |>

step_1 = (1 + 2 - 1) * 4 / 2
step_2 = div(round(step_1), 4)
step_3 = step_2 * 4
result = rem(step_3, 3)

Atoms

Atoms are constants which value is the name :value

[
  :happy,
  :this_is_an_atom,
  :"this as well",
  :"45"
]

WARNING!

Atoms stored in memory

Don’t create atoms dynamically!

## NO Dont!!!
String.to_atom("this a string ")

Strings & Charlist

"This is a string"
"UTF-8? Com certça meu!"

Interpolations

my_value = 10_008_000
"This an interpolated value #{inspect(my_value)}"

Please don’t abuse of this tip

my_value = [10_008_000, :a, "s"]
"This an inspected value #{inspect(my_value)}"
char_list = ~c"This is a charlist"

ASCII

?x

Actually String are binaries

<<120, 121, 122>>

And Charlist exactly lists of chars

[120, 121, 122]
[120, 121, 122] == "xyz"
[120, 121, 122] == ~c"xyz"
<<97, 98, 99>> <> <<120, 121, 122>>
multi_line_string =
  """
    this is a
    multiline
    string,
    really?
  """
IO.puts(multi_line_string)
IO.puts(char_list)

Responses from functions

  • {:ok, value}

  • :error

  • {:error, :not_found}

Tuples

my_tuple = {"a", :b, 2}
elem(my_tuple, 1)

Remember about this tuple {:ok, [value_1, value_2]}

Maps (key-value)

WARNING: avoid to mix key types

my_map =
  %{
    "this is a key" => :this_is_a_value,
    :"atom key" => 789,
    :embedded_map => %{:key => "value"},
    another_key: "This is a value as well"
  }
Map.fetch(my_map, "this is a key")
Map.fetch!(my_map, :"atom key")
Map.take(my_map, [:another_key, :embedded_map, :new_key])
Map.get(my_map, :some_inexistent_key, "default value if not exists")
Map.get(my_map, :embedded_map)
get_in(my_map, [:embedded_map, :key])
Map.put(my_map, :new_key, 1000)

At this moment just accept a function can be writen like

fn value -> some_process(value) end
Map.update!(my_map, :"atom key", fn value -> value * 2 end)
Map.update(my_map, :new_key, 5000, fn value -> value * 2 end)
my_map.another_key
my_map["this is a key"]
put_in(my_map, [:embedded_map, :another_key], "amazing_value")

Lists

my_list = [5, 6, 7]
Enum.into(1..20, [])
for x <- 1..10, do: x
graph LR;
A-->B;
B-->C;
C-->D;
[head | tail] = my_list
IO.puts("This a head: #{head}")
IO.inspect(tail, label: :Tail)
my_list

Lists are prepend linked list, so think always to prepend the new item and user recursion for the entire list

*remember this for pipe operator

graph LR;
  id1((This is a new itme))-->A;
A-->B;
B-->C;
C-->D;
new_value = 10
[new_value | my_list]

Just remember for the future getting a Head and Tail with that syntax is because of pattern matching

That’s the reason concatenate a list & an item won’t work

my_list ++ new_value

You need to wrap it in a new list

my_list ++ [new_value]

With a large amount of data in lists you will see the performace difference

big_list = Enum.into(1..100_000_000, [])

Prepending the big_list will require to scroll all the values to connect the tail with the head of my_list

big_list ++ my_list
[1, 2, 3, 4, 5, 6, 7, 8, 9] ++ big_list
List.first(my_list)
List.last(big_list)
Enum.each(my_list, fn value -> IO.puts(value) end)
Enum.count(my_list)
Enum.empty?(my_list)
Enum.all?(my_list, fn value -> rem(value, 2) > 0 end)
Enum.all?(my_list, fn value -> value > 0 end)

Pipe Operator

initial = 1

initial
|> Kernel.+(2)
|> Kernel.+(-1)
|> Kernel.*(2)
|> Kernel./(2)
|> IO.inspect(label: :first_result_as_float)
|> round()
|> div(2)
|> IO.inspect(label: :second_result_as_integer)
|> Kernel.*(4)
|> rem(3)

what we build in real life with pipelines tents to abstract data processes and make the code readable for humans.

Transaction
|> find_the_customer
|> get_average_ticket_amount
|> maybe_discount
|> compute_total_amount