Damien Gonot
Home Blog Notes About

Elixir

Homepage / Notes / Computer Science / Programming Languages / Elixir

Language Features

Numbers

1 + 1
2

Ranges

1..10 |> Enum.map(&(&1))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Stepped range

Starting from Elixir 1.12:

1..10//2 |> Enum.map(&(&1))
[1, 3, 5, 7, 9]

Parse int from string

"1" |> String.to_integer
1

Parse int to string

1 |> Integer.to_string
"1"

Strings

Interpolation

name = "world"
"Hello, #{name}!"
"Hello, world!"

Concatenation

"This is a " <> "concatenated string"
"This is a concatenated string"

Split

String.split("header1,header2,header3", ",")
["header1", "header2", "header3"]

Ends With?

String.ends_with?("bonjour", "jour")
true
String.ends_with?("bonsoir", "jour")
false

Multiline string

multiline = """
this is a
multiline
string
"""
"this is a\nmultiline\nstring\n"

Replace

s = "2021-10-28T08:55:01"
s |> String.replace("T", " ")
"2021-10-28 08:55:01"

Lists / Enums

Access element by index

["a", "b", "c"] |> Enum.at(1)
"b"

Length

[1, 2, 3] |> length
3

Prepending

list = [1, 2, 3]
[0 | list]
[0, 1, 2, 3]

Appending

list = [1, 2, 3]
list ++ [4]
[1, 2, 3, 4]

Concatenation

[1, 2] ++ [3, 4]
[1, 2, 3, 4]

Head / Tail

hd(["a", "b", "c", "d", "e"])
"a"
tl(["a", "b", "c", "d", "e"])
["b", "c", "d", "e"]
[head | tail] = ["a", "b", "c", "d", "e"]
["a", "b", "c", "d", "e"]
head
"a"
tail
["b", "c", "d", "e"]

First element

["a", "b", "c"] |> List.first
"a"

Last element

["a", "b", "c"] |> List.last
"c"

Element in List?

["a", "b", "c"] |> Enum.member?("c")
true
["a", "b", "c"] |> Enum.member?("d")
false

Delete element at index

["a", "b", "c"] |> List.delete_at(1)
["a", "c"]

Empty?

[1, 2, 3] |> Enum.empty?
false
[] |> Enum.empty?
true

Filter

Enum.filter([1, 2, 3, 4], fn(x) -> x > 2 end)
[3, 4]

Sort

Enum.sort([4, 2, 3, 1])
[1, 2, 3, 4]
Enum.sort([4, 2, 3, 1], :desc)
[4, 3, 2, 1]

Sort by

Enum.sort_by([%{id: 1, value: "Georges"}, %{id: 3, value: "Damien"}, %{id: 2, value: "Jacques"}], &(&1.id), :asc)
[
  %{id: 1, value: "Georges"},
  %{id: 2, value: "Jacques"},
  %{id: 3, value: "Damien"}
]

Uniq

Enum.uniq([1, 1, 2, 2, 2, 3])
[1, 2, 3]

Map

Enum.map([1, 2, 3], fn(x) -> x*2 end)
[2, 4, 6]

Reduce

Enum.reduce([1, 2, 3], 0, fn(x, acc) -> x + acc end)
6

With index

/!\ Elixir v1.12 only

Enum.with_index(["a", "b", "c"], fn(x, index) -> {index, x} end)
[{0, "a"}, {1, "b"}, {2, "c"}]

Take

Return first nth elements of list

[1, 2, 3, 4, 5] |> Enum.take(3)
[1, 2, 3]

Works with negative numbers too to return last nth elements

[1, 2, 3, 4, 5] |> Enum.take(-2)
[4, 5]

Drop

Return list except n first elements

[1, 2, 3] |> Enum.drop(1)
[2, 3]
[1, 2, 3] |> Enum.drop(2)
[3]

Join

[1, 2, 3, 4, 5] |> Enum.join(";")
"1;2;3;4;5"

Find

[%{id: 1, name: "a"}, %{id: 2, name: "b"}, %{id: 3, name: "c"}] |> Enum.find(&(&1.id == 3))
%{id: 3, name: "c"}

Maps

Fetch value, returns {:ok, value} or :error if key is not in map:

map = %{:foo => "bar"}
Map.fetch(map, :foo)
{:ok, "bar"}
map = %{:foo => "bar"}
Map.fetch(map, :fooz)
:error

To directly get the value, without error handling:

map = %{:foo => "bar"}
Map.get(map, :foo)
"bar"

Syntactic sugar:

map = %{:foo => "bar"}
map[:foo]
"bar"

To provide a default in case key is not present in map:

map = %{:foo => "bar"}
Map.get(map, :bar, "default value")
"default value"

putin

user = %{name: "John", age: 28}
put_in(user.age, 29)
%{age: 29, name: "John"}

Keys

map = %{"key1" => "value1", "key2" => "value2"}
map |> Map.keys
["key1", "key2"]

Put (add new key/value)

map = %{"key1" => "value1", "key2" => "value2"}
map |> Map.put("key3", "value3")
%{"key1" => "value1", "key2" => "value2", "key3" => "value3"}

Update existing key to new value

map = %{"key1" => "value1", "key2" => "value2"}
%{map | "key2" => "new value 2"}
%{"key1" => "value1", "key2" => "new value 2"}

Delete / Drop

Delete a single key:

map = %{"key1" => "value1", "key2" => "value2"}
map |> Map.delete("key2")
%{"key1" => "value1"}

Drop a list of keys:

map = %{"key1" => "value1", "key2" => "value2", "key3" => "value3"}
map |> Map.drop(["key1", "key2"])
%{"key3" => "value3"}

Functions

Named Functions

Regular

Must be defined with def inside a module

defmodule Greeter do
  def hello(name) do
    "Hello, #{name}!"
  end
end

Greeter.hello("Damien")
"Hello, Damien!"
One-liner
defmodule ShortGreeter do
  def hello(name), do: "Hello, #{name}!"
end

ShortGreeter.hello("Damien")
"Hello, Damien!"
Function Naming and Arity

Functions are defined by name AND arity (number of parameters)

defmodule Greeter2 do
  def hello(), do: "Hello, anonymous!"
  def hello(name), do: "Hello, #{name}!"
  def hello(name1, name2), do: "Hello, #{name1} and #{name2}!"
end
Greeter2.hello()
"Hello, anonymous!"
Greeter2.hello("Damien")
"Hello, Damien!"
Greeter2.hello("Damien", "Jean-Jacques")
"Hello, Damien and Jean-Jacques!"

Private Functions

https://elixirschool.com/en/lessons/basics/functions/#private-functions

Guards

https://elixirschool.com/en/lessons/basics/functions/#guards

Default Arguments

https://elixirschool.com/en/lessons/basics/functions/#default-arguments

Anonymous Functions

sum = fn(a, b) -> a + b end
sum.(2, 3)
5

Using the & shorthand:

sum = &(&1 + &2)
sum.(2, 3)
5

https://blog.lelonek.me/3-tricks-of-anonymous-elixir-functions-a81d1b1e049c

If/else, cond

No elseif!

if

if true do
  "this is true"
else
  "this is false"
end
"this is true"

unless

unless false do
  "this is true"
else
  "this is false"
end
"this is true"

cond

cond do
  1 == 1 -> true
  1 == 2 -> false
  true -> "else"
end
true
cond do
  1 == 0 -> true
  1 == 2 -> false
  true -> "else"
end
"else"

Pipe Operator

[1, 2, 3] |> Enum.map(&(&1*2))
[2, 4, 6]
"Elixir rocks" |> String.upcase() |> String.split()
["ELIXIR", "ROCKS"]
Tap & Then

Starting from Elixir 1.12: tap passes value to fun and returns value Useful for functions that have side-effects but you still want to pass down the value down the pipe (or for logging for example)

then passes value to fun, basically invokes fun with value as an argument, meaning you can place it as 2nd or 3rd argument…

Here in that example, we're able to pipe down a string and log it using tap, then use Regex.scan with the string as the 2nd (not first) argument of the function

"hello world"
|> tap(&IO.puts/1)
|> then(&Regex.scan(~r/\w+/, &1))
hello world
[["hello"], ["world"]]

Debugging

IO.inspect

IO.inspect can be piped!

[1, 2, 3]
|> IO.inspect
|> Enum.map(&(&1*2))
|> IO.inspect
|> Enum.sum
[1, 2, 3]
[2, 4, 6]
12

Outputs can labeled

[1, 2, 3]
|> IO.inspect(label: "initial list")
|> Enum.map(&(&1*2))
|> IO.inspect(label: "after x2")
|> Enum.sum
initial list: [1, 2, 3]
after x2: [2, 4, 6]
12

Pattern Matching

https://elixirschool.com/en/lessons/basics/pattern-matching/

case {:ok, "Hello World"} do
  {:ok, result} -> result
  {:error, _} -> "Uh oh!"
  _ -> "Catch all"
end
"Hello World"

Comprehensions

list = [1, 2, 3, 4, 5]
for x <- list, do: x*x
[1, 4, 9, 16, 25]

Packages

Starting from Elixir 1.12:

IO.puts(Jason.encode!(%{hello: :world}))
** (UndefinedFunctionError) function Jason.encode!/1 is undefined (module Jason is not available)
    Jason.encode!(%{hello: :world})

Can install required packages using Mix.install()

Mix.install([:jason])
IO.puts(Jason.encode!(%{hello: :world}))
{"hello":"world"}
:ok

JSON Parsing

Mix.install([:jason])
"{\"key\": \"value\"}" |> Jason.decode!
%{"key" => "value"}

Mix

Dependencies manager!

Create a new project: mix new example Interactive shell: iex -S mix Compile project: mix compile Fetch dependencies: mix deps.get

Ecto

https://elixirschool.com/en/lessons/ecto/basics/

Query

Preload

user |> Repo.preload(:integrations)

Query Fragment

https://hexdocs.pm/ecto/Ecto.Query.API.html#fragment/1

Migrations

Process migrations: mix ecto.migrate Rollback previous migration: mix ecto.rollback Rollback all migrations: mix ecto.rollback --all

Resources

https://bartoszgorka.com/dynamic-queries-in-ecto

Gettext

https://hexdocs.pm/gettext/Gettext.html Manages translations for i18n

Attributes

https://hexdocs.pm/elixir/master/Module.html

@impl

@impl notes that the function is a callback method https://elixir-lang.org/blog/2017/07/25/elixir-v1-5-0-released/ https://elixircasts.io/%40impl-attribute

Testing

https://elixirschool.com/en/lessons/basics/testing/

ExUnit

https://hexdocs.pm/ex_unit/1.12/ExUnit.html

Doctests

https://inquisitivedeveloper.com/lwm-elixir-66/ https://elixir-lang.org/getting-started/mix-otp/docs-tests-and-with.html

Mox

https://elixirschool.com/en/lessons/testing/mox

Agents

https://elixir-lang.org/getting-started/mix-otp/agent.html

Elixir Script

Elixir can be used for scripting. Script files have a .exs file extension. Recommended for small tasks that don't require a Mix project.

Phoenix

Directory Structure

https://hexdocs.pm/phoenix/directory_structure.html

Mix Commands

Start the Phoenix app: mix phx.server Run the Phoenix app inside IEx: iex -S mix phx.server

Adding a new route

https://hexdocs.pm/phoenix/request_lifecycle.html#content

Views

https://hexdocs.pm/phoenix/views.html

Presence

https://hexdocs.pm/phoenix/Phoenix.Presence.html

Deployment

https://hexdocs.pm/phoenix/deployment.html

Libraries

LiveView

https://github.com/phoenixframework/phoenix_live_view Send HTML over the wire

https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html

Forms

https://hexdocs.pm/phoenix_live_view/form-bindings.html

Surface

https://github.com/surface-ui/surface

JS

JS trick: https://mobile.twitter.com/josevalim/status/1486996785914650625

React in LiveView

https://stephenbussey.com/2022/04/13/react-in-liveview-how-and-why.html

phx.gen.auth

https://github.com/aaronrenner/phx_gen_auth Will be built in Phoenix starting from 1.6

No emails are actually sent on sign up and password reset, have to built that ourselves

Tailwind

https://fly.io/phoenix-files/tailwind-standalone/

Resources

File upload tutorial: https://www.yodiw.com/simple-upload-file-local-phoenix-elixir/

esbuild

https://www.mitchellhanberg.com/how-i-handle-static-assets-in-my-phoenix-apps/ https://cloudless.studio/wrapping-your-head-around-assets-in-phoenix-1-6

Petal

https://petal.build/

Packages

Oban

https://github.com/sorentwo/oban

Oban is a robust job processing library which uses PostgreSQL for storage and coordination.

Nx

https://github.com/elixir-nx/nx/tree/main/nx

Nx is a multi-dimensional tensors library for Elixir with multi-staged compilation to the CPU/GPU.

Mix.install([{:nx, github: "elixir-nx/nx", branch: "main", sparse: "nx"}])
t = Nx.tensor([[1, 2], [3, 4]])
Nx.shape(t)
{2, 2}
Nx.sum(Nx.tensor([1, 2, 3]))
#Nx.Tensor<
  s64
  6
>
Nx.mean(Nx.tensor([1, 2, 3]))
#Nx.Tensor<
  f32
  2.0
>
Nx.add(Nx.tensor([10, 10, 10]), Nx.tensor([100, 200, 300]))
#Nx.Tensor<
  s64[3]
  [110, 210, 310]
>

Axon

https://github.com/elixir-nx/axon

Nx-powered Neural Networks

Resources

https://seanmoriarity.com/2021/04/08/axon-deep-learning-in-elixir/

Livebook

https://github.com/elixir-nx/livebook

Similar to Jupyter notebooks but for Elixir

Explorer

https://github.com/elixir-nx/explorer

Series (one-dimensional) and dataframes (two-dimensional) for fast data exploration in Elixir

GenServers

https://papercups.io/blog/genserver

Bamboo (Email library)

https://github.com/thoughtbot/bamboo

Testable, composable, and adapter based Elixir email library for devs that love piping.

Delta

https://github.com/slab/delta-elixir

Simple yet expressive format to describe documents' contents and changes

Broadway

https://github.com/dashbitco/broadway

Concurrent and multi-stage data ingestion and data processing with Elixir

Lumen

https://getlumen.org/ An alternative BEAM implementation, designed for WebAssembly => Elixir on the front-end?

Burrito

https://github.com/burrito-elixir/burrito

Wrap your application in a BEAM Burrito!

BEAM wrapped in Zig

Witchcraft

https://github.com/witchcrafters/witchcraft

Monads and other dark magic for Elixir

https://blog.appsignal.com/2022/02/08/functional-programming-in-elixir-with-witchcraft.html

Temple

https://github.com/mhanberg/temple

An HTML DSL for Elixir and Phoenix

Wallaby

https://github.com/elixir-wallaby/wallaby

Concurrent browser tests for your Elixir web apps

Ash Framework

https://www.ash-elixir.org/

Build APIs in minutes. Ash is a declarative, resource-oriented application framework for Elixir.

Resources

Elixir School

https://elixirschool.com/en/

Elixir Casts

https://elixircasts.io/

Stripe library

https://github.com/code-corps/stripity_stripe

Blog about Elixir

https://culttt.com/

An Introduction to Metaprogramming in Elixir

https://blog.appsignal.com/2021/09/07/an-introduction-to-metaprogramming-in-elixir.html

Fast Elixir

https://github.com/devonestes/fast-elixir

Tips for Improving Your Elixir Configuration

https://felt.com/blog/elixir-configuration