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
= "world"
name "Hello, #{name}!"
"Hello, world!"
Concatenation
"This is a " <> "concatenated string"
"This is a concatenated string"
Split
String.split("header1,header2,header3", ",")
["header1", "header2", "header3"]
Parts
String.split("header1,header2,header3", ",", parts: 2)
["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
= "2021-10-28T08:55:01"
s |> String.replace("T", " ") s
"2021-10-28 08:55:01"
Contains?
String.contains?("key=value", "=")
true
String.contains?("key=value", "x")
false
String.contains?("key=value", ["key", "value"])
true
String.contains?("key=value", ["key", "xyz"])
true
String.contains?("key=value", ["abc", "xyz"])
false
Tuples
Tuples:
- can hold any value
- store elements contiguously in memory
{:ok, "hello"}
{:ok, "hello"} tuple_size
2
0-indexed
({:ok, "hello"}, 1) elem
"hello"
({:ok, "hello"}, 1, "world") put_elem
{:ok, "world"}
Lists / Enums
Access element by index
["a", "b", "c"] |> Enum.at(1)
"b"
Length
[1, 2, 3] |> length
3
Prepending
= [1, 2, 3]
list [0 | list]
[0, 1, 2, 3]
Appending
= [1, 2, 3]
list ++ [4] list
[1, 2, 3, 4]
Concatenation
[1, 2] ++ [3, 4]
[1, 2, 3, 4]
Head / Tail
(["a", "b", "c", "d", "e"]) hd
"a"
(["a", "b", "c", "d", "e"]) tl
["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]
Reject
The "inverse" of Filter
Enum.reject([1, 2, 3, 4], fn(x) -> x > 2 end)
[1, 2]
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
:
= %{:foo => "bar"}
map Map.fetch(map, :foo)
{:ok, "bar"}
= %{:foo => "bar"}
map Map.fetch(map, :fooz)
:error
To directly get the value, without error handling:
= %{:foo => "bar"}
map Map.get(map, :foo)
"bar"
Syntactic sugar:
= %{:foo => "bar"}
map [:foo] map
"bar"
To provide a default in case key
is not present in map
:
= %{:foo => "bar"}
map Map.get(map, :bar, "default value")
"default value"
putin
= %{name: "John", age: 28}
user (user.age, 29) put_in
%{age: 29, name: "John"}
Keys
= %{"key1" => "value1", "key2" => "value2"}
map |> Map.keys map
["key1", "key2"]
Put (add new key/value)
= %{"key1" => "value1", "key2" => "value2"}
map |> Map.put("key3", "value3") map
%{"key1" => "value1", "key2" => "value2", "key3" => "value3"}
Update existing key to new value
= %{"key1" => "value1", "key2" => "value2"}
map {map | "key2" => "new value 2"} %
%{"key1" => "value1", "key2" => "new value 2"}
Delete / Drop
Delete a single key:
= %{"key1" => "value1", "key2" => "value2"}
map |> Map.delete("key2") map
%{"key1" => "value1"}
Drop a list of keys:
= %{"key1" => "value1", "key2" => "value2", "key3" => "value3"}
map |> Map.drop(["key1", "key2"]) map
%{"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
defmodule Guards do
def combine(x, y) when is_number(x) and is_number(y), do: x + y
def combine(x, y), do: x <> y
end
{Guards.combine(4, 5), Guards.combine("abc", "xyz")}
{9, "abcxyz"}
Default Arguments
https://elixirschool.com/en/lessons/basics/functions/#default-arguments
Anonymous Functions
= fn(a, b) -> a + b end
sum .(2, 3) sum
5
Using the & shorthand:
= &(&1 + &2)
sum .(2, 3) sum
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
Kernel.dbg
Starting from Elixir 1.14
elixir -e '[1, 2, 3] |> Enum.map(&(&1*2)) |> dbg() |> Enum.sum |> dbg()'
[nofile:1: (file)]
value #=> [2, 4, 6]
[nofile:1: (file)]
[1, 2, 3] #=> [1, 2, 3]
|> Enum.map(&(&1 * 2)) #=> [2, 4, 6]
|> dbg() #=> [2, 4, 6]
|> Enum.sum() #=> 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"
Using Guards
case 9 do
when n > 5 -> "over than 5"
n -> "anything else"
_ end
"over than 5"
case 4 do
when n > 5 -> "over than 5"
n -> "anything else"
_ end
"anything else"
Comprehensions
= [1, 2, 3, 4, 5]
list 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
Documentation (ExDoc)
https://github.com/elixir-lang/ex_doc
ExDoc is a tool to generate documentation for your Elixir projects.
Cheatsheets
https://hexdocs.pm/ex_doc/cheatsheet.html
Binaries
Strings are binary!
= "hello"
string (string) is_binary
true
Code Points
Reveal a character code point:
?a
97
In hexadecimal:
0x0061
97
String.codepoints("π¨βπ»")
["π¨", "β", "π»"]
String.graphemes("π¨βπ»")
["π¨βπ»"]
Bitstrings
IO.inspect("abc", binaries: :as_binaries)
<<97, 98, 99>>
"abc"
Binaries
A binary is a bitstring where the number of bits is divisible by 8.
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
_build
- everything that's compiled. Exclude from version control.assets
- everything related to front-end (CSS, JSβ¦)config
- holds project configuration, withconfig.exs
being the main entry point anddev.exs
,prod.exs
being for environment specific configdeps
- holds all the Mix dependencies. Exclude from version control.lib
- the juicy part! All the application source code.lib/project_name
holds all the "backend" stuff like interaction with database (the "M" in MVC), whilelib/project_name_web
is for the "frontend" ("V" and "C" in MVC).priv
- stuff that's necessary for prod but not part of the source code, like database scriptstest
- all the tests
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
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"}])
= Nx.tensor([[1, 2], [3, 4]])
t 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
Build APIs in minutes. Ash is a declarative, resource-oriented application framework for Elixir.
Ash Authentication
https://alembic.com.au/blog/announcing-ash-authentication
Ash Authentication is a drop-in authentication solution for users of the Ash framework who want to provide password-based or social sign-in via OAuth 2.0.
Cobblestone
https://github.com/doomspork/cobblestone
A better path to data
Bumblebee
https://github.com/elixir-nx/bumblebee
Bumblebee provides pre-trained and transformer Neural Network models on top of Axon. It includes integration with π€ Models, allowing anyone to download and perform Machine Learning tasks with few lines of code.
Dune
https://github.com/functional-rewire/dune
A sandbox for Elixir to safely evaluate untrusted code from user input.
Enumancer
https://github.com/sabiwara/enumancer
Elixir macros to effortlessly define highly optimized Enum pipelines.
Resources
Elixir School
Elixir Casts
Stripe library
https://github.com/code-corps/stripity_stripe
Blog about Elixir
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