Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/samh7/elixircheatsheet
https://github.com/samh7/elixircheatsheet
cheatsheet elixir
Last synced: about 2 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/samh7/elixircheatsheet
- Owner: samh7
- Created: 2023-11-20T11:48:47.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2023-11-22T18:20:06.000Z (about 1 year ago)
- Last Synced: 2024-09-11T18:17:32.033Z (4 months ago)
- Topics: cheatsheet, elixir
- Language: Elixir
- Homepage:
- Size: 1.68 MB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Elixir Cheat Sheet
---## Introduction
Elixir is a dynamic functional compiled language that runs over an Erlang Virtual Machine called BEAM.
Erlang and its BEAM is well known for running low-lattency, distributed and fault-tolerant applications
Elixir data types are immutable.
In Elixir a function is usually described with its arity (number of arguments), such as `is_boolean/1`
## File Types
---- `.exs` => Elixir Script File
- `.ex` => Elixir Regular File
- `.beam` => Compiled Elixir File## Compilating & Running Elixir Code
- `elixir .exs` => run script code
- `elixirc .ex` => compile a file into an `Elixir..beam`## Elixr Convetions
- `function foo return tuple` => the result of a foo funtion is usually {:ok, result} ot , {:error, reason}.
- `function foo! may raise an error` => the result of foo! is not wrapped in a tuple and may raise an exception.
- **Exceptions/Erros** are not used to controlling flow.
- Elixir uses **fail** **fast** idea and the supervision trees to control process health and if possible, restart processes.## Comments
- `#` => Single line comments
- No Multi-line comments## Code Documentation
- `@moduledoc` => Module documentation
- `@doc` => funtion doc.
- `@spec` => function args/ return specification (types of the args and return of the function)
- `@typedoc` => type doc.
- `@type` => type definition.
- `@typep` => private type defintion### Eg.
```elixir
defmodule Math d0
@moduledoc """
provides math-related functions.
## Examples
iex> Math.sum(1,2)
3
"""""
@spec sum(number, number) :: number
@doc"""
Calculates the sum of two numbers.
"""
def sum(a,b), do: a + b
end```
## Elixir Special Variables
- `_` => unbound variable (discard)
- `^var` => It pins the variable on its value and prevent any assignment to this variable when using pattern matching.
### For Example
```elixir
a = 1
b = 2
a = b # a now has a value of 2
a == b # return true or false
^a = b = #pattern matches the two, if not equal, throws an error
** (SyntaxError) iex:13:10: unexpected expression after keyword list. Keyword lists must always come last in lists and maps. Therefore, this is not allowed:[some: :value, :another]
%{some: :value, another => value}Instead, reorder it to be the last entry:
[:another, some: :value]
%{another => value, some: :value}Syntax error after: ','
|
13 | %{age: 30, :name => "Mary"}
| ^
(iex 1.15.7) lib/iex/evaluator.ex:294: IEx.Evaluator.parse_eval_inspect/4
(iex 1.15.7) lib/iex/evaluator.ex:187: IEx.Evaluator.loop/1
(iex 1.15.7) lib/iex/evaluator.ex:32: IEx.Evaluator.init/5
(stdlib 5.1.1) proc_lib.erl:241: :proc_lib.init_p_do_apply/3
```
- `|` =>
1. used to update a single key in a map or a truct
```elixir#The a key of the same name should already exist otherwise it errors
# iex> map = %{name: Alice}
# iex> map %{map} is not valid syntax you use it with the `|` operator to update a keymap = %{name: "Alice", age: 25}
map = %{map | name: "Bob"}struct = %User{name: "Alice", age: 25}
struct = %{struct | name: "Bob"}
```
2. To prepend an element to a list
```elixir
list = [2,3,4]
list = [1 | list] # list now equals [1,2,3,4]
```## Elixir/Erlang Virtual Machine inspection
- `:observer.start` => Start a gui too for inspection
- `:erlang.memory` => inspect memory
- `:c.memory` => inspect memory
```elixir
:c.memory
# [
# total: 19262624,
# processes: 4932168,
# processes_used: 4931184,
# system: 14330456,
# atom: 256337,
# atom_used: 235038,
# binary: 43592,
# code: 5691514,
# ets: 358016
# ]
```## Interactive Elxir/IEX
- `iex` => open Interactive Elixir
- `iex file` => open iex loading a file
- `c + a` => close iex
- `i ` => information about an object
- `h ` => help for a function
- `h ` => help for an operator
- `s ` => specification for a function
- `s ` => specification for an operator
- `t ` => type for a function
- `c ` => Load and compile a .ex file# Basic Types
---- `1` => Integer
- `1_000` => integer, use `_` to make it easy to read
- `0x1F` => Hexadecimal integer
- `0b1010` => Binary integer, base 10
- `0o777` => Octadecimal Integer
- `1.0` => Float
- `5.7e-2` => Floar exponent notation 0.057
- `:atom` => atom/symbol
- `true` => boolean (it is an atom)
- `<<97::size(2)>>` => bit string
- `<<97,89>>` => Binary
- `"elixir"` => string
- {1,2,3} => tuple
- [1,2,3] => list (actually a linked list)
- `'elixir'` => char list
- `[a: 5, b: 3]` => keyword list (short notation) `[a => 5, b => 3]` is not permited.
- `[{:a, 5},{:b, 3}]` keyword list (long notation)
- `%{name: "Mary", age: 29}` => Map short notation (keys must be atoms).
- `%{:name => "Mary", :age => 29}` => Map long notation.
```elixir
# Keyword list(the short notation) must always come last. So if both short and long notation are used in the same map, the short notation should come last
%{:name => "Mary", age: 30} # Correct, does not error
%{name: "Mary", age: 30} # Correct, Does not error
%{age: 30, :name => "Mary"} # Errors
#** (SyntaxError) iex:13:10: unexpected expression after keyword list. Keyword lists must always come #last in lists and maps. Therefore, this is not allowed:
#[some: :value, :another]
#%{some: :value, another => value}
#Instead, reorder it to be the last entry:
```
- `self()` => Current Process id
- `fn -> :hello end` => Anonymous function
- `make_ref()` => Create a new reference
- `hd Port.list()` => get firt port
- `is_nil/1`
- `is_integer 1`
- `is_float 4.6`
- `is_number 7.8`
- `is_atom :foo`
- `is_boolean false`
- `is_bitstring <<97:2>>`
- `is_binary <<97, 98>>`
- `is_list/1`
- `is_tuple/1`
- `is_map/1`
- `is_pid self()`
- `is_function(fn a, b -> a + b end)` => function
- `is_function(fn a, b -> a + b end, 2)` => function with arity
- `is_port hd Port.list()`
- `is_reference make_ref()`
- `Range.range?(1..3)`## Converting Types
- `to_charlist("hello")`
- `to_string('hello')`
- `Map.to_list(%{:a => 1, 2 => :b})` => map to list of tuples## Number Operators
- `10/2 => 5.0` => always returns a float
- `div(10, 2) => 5` => Integer division
- `rem(10, 3) => 1` => Modulus operation
- `round(3.58) => 4` => float round
- `trunc(3.58) => 3` => float trunc## Operators
`==`, `!=`, `===` => strict equal (integer with float), `!==` => strict different (inetger with float), `<`, `<=`, `>`, `>=`, `&&` => truthy and (t-and), `||` => t-or, `!`, `and` => boolean and (b-and), `or` => b-or, `not` => b-not### NOTE
It's possible to compare different data types and that's the sorting order: `number < atom < reference < functions< port < pod < tuple < list < bit string` (narf, pptlb)
## Pipe Operator
- `|>` => Pipe Operator. Passes a value as the first argument of the function following it
```elixir
1.100
|> Stream.map(&(&1 * 3))
|> Stream.filter(&(rem(&1,2) != 0))
|> Enum.sum
# => 7500
```## Pattern Matching
`=` => is not just an assignment operator, but a Match Operator. It matches variables from the right side to the left based on patterns defined by the left one. If a pattern does not match a `MatchError` is raised.
```elixir
[a, b, 10] = [0,1,10] #does not error instead, assigns values 0 and 1 to variables a and b respectively.
%{a: 10, :b => 20 } => %{:a => 10, b: 20}
```
But, for variables, is will most likely be an assignment operator:```elixir
a = 1
b = 2
a = b # a now is equal to 1
```We therefore use a pin operator `^` to pattern match variables:
```elixir
a = 1
b = 10
^a = b #errors
``````elixir
#To pattern match a single item from a struct or a map do:
map = %{name: "Alice", age: 25}
%{name: name} = map
``````elixir
#empty maps/ structs always match for any map/struct respectively
map = %{name: "Alice", age: 25}
%{} = map # does not error
```### So in other words:
- non variables on the left side will be used to restrict a pattern to match
- variables using the pin operator on the left side will have its value to be used to restrict a pattern to match
- variables on the left side will be assigned with right side values## Match Operator Limitation
You cannot make function calls on the left side of a match.
- `length([1, [2], 3]) = 3 #=> ** (CompileError) illegal pattern`## Custom Operators
---You can customize an Elixir Operator.
example:
```elixir
1 + 2 # => 3
defmodule WrongMath do
def a + b do
{a, b}
end
end
import WrongMath
import Kernel, except: [+: 2]
1 + 2 #=> {1, 2}
```## Sigils
---- `~r` => regular expression (modifires: `i`)
- `~r/hello/i` => `i` modifies to case insensitive
- `~w` => list of a string of words (eg. `~w[foo bar]`)
- `~w[foo bar]c` => `c` modifies list of char lists
- `~w[foo bar]a` => `c` modifies to list ot atoms
```elixir
~w(one two three) #=> ["one", "two", "three"]
~w(one two threee)c #=> ['one', 'two', 'three']
~w(one two three)a #=> [:one, :two, :three]
```
## Bit Strings
---- `<<97::4>>` => short notation with 4 bits
- `<<97::size(4)>>` => long notation with 4 bits
- `byte_size(<<5::4>>)` => bit string byte size
## Binaries
---Binaries are 8 bit multiple Bit Strings. Binaries are 8 bits by default.
- `<<97>>` => Shirt notation with 8 bits.
- `<<97::size(8)>>` => Long notation with 8 bits.
- `<>` => concatenate binaries/strings## Strings
---String is a binary of code points where all elements are valid characters.
Strings are sorrounded by double quotes and are encoded in `UTF-8` by default.
- `"hello"` => string
- `<<97, 98>>` => string "ab"
- `<>` => string "ab"
- `?a` => 97
- `?b` => 98
- `"hello #{:world}"` => string interpolation for "hello world"
- `String.length("hello") #=>5`
- `String.upcase("hello") #=> "HELLO"`
- `"""(begin) multi-line string (end)"""`### Performance for Strings:
cheap functions => `byte_size("hello")`
expensive functions => `String.length("hello")`## Tuples
---Tuple is a list that is stored contiguously in memory.
- `{:ok, "hello"}`
- `tuple_size({:ok, "hello"})` => tuple size (cheap function)
- `elem({:ok, "hello"}, 0)` => get tuple element by index (cheap function)
- `put_elem({:ok, "hello"}, 1, "world")` (expensive function)
## Lists
---Lists implements Enumerables protocol.
List is a linked list structure where each element points to the next one in memory. When subtraction just the first ocurrence will be removed.
- `[:ok, "hello"]`
- `[97 | [1, 6, 9]]` => prepend (expensive function)
- `[1, 5] ++ [3, 9] # [1, 5, 3, 9]` => concatenation (expensive function)
- `[1, 2, 3, 4, 5, 6] -- [3,4,10,13] # [1,2,5,6]` => Keep all elements in the first list execpt for all the commont elements between the two lists and discard the rest in the second list.
- `hd([1, 5, 7]) #=> 1` => head (cheap function)
- `tl([1,5,7]) #=> [5,7]` => tails (cheap function)
- `:foo in [:foo, :bar] #=> true` => in operator (expensive function)
- `length([1,5])` => length of list (expensive function)## Char List
---A Char List is a List of code points where all elements are valid characters. Char Lists are surrounded by single-quote and are ***usually used as arguments to some old Erlang code***.
- `ab` => Char list
- `[97, 98]` => 'ab'
- `[?a,?b]` => 'ab'
- `?a` =? 97
- `'hello' ++ 'world' #helloworld` => Concatenation
- `'hello' -- 'world' #hel` => Keep all elements in the first char list execpt for all the commont elements between the two lists and discard the rest in the second char list.
- `?l in 'hello' #true` => in operator
- `'length('hello')'`
- `[?H | 'ello']`## Keyword Lists
---Keyword list is a list of tuples where first elements are atoms. When fetching by key the first match will return. If a keyword list is the last argument of a function the square brackets [ are optional.
- `[{:a, 6} | [b: 7]] # [a: 6, b: 7]` => prepend
- `[a: 5] ++ [a: 7] # [a: 5, a: 7]`
- `([a: 5, a: 7])[:a] # 5` => fetch by key
- `length([a: 5, b: 7])`
### Note- `[{:a, 6} | [b: 7]]` => `[a: 6, b: 7]`
- `[[a: 6] | [b: 7]]` => `[[a: 6], {:b, 7}]`## Maps
---Maps implements Enumerable Protocol.
Map holds a key value structure.
- `%{name: "Mary", age: 29}[:name] #=> "Mary`
- `%{name: "Mary", age: 29}[:born] #=> nil`
- `%{name: "Mary", age: 29}.name #=> "Mary`
- `%{name: "Mary", age: 29}.name #=> ** (KeyError)`
- `map_size(%{name: "Mary"}) #=> 1`
## Structs
---
Structs are built on top of maps
`defstruct` => define a struct```elixir
defmodule User do
defstruct name: "John", age: 27
end
john = %User{} #=> %User{age: 27, name: "John"}
mary = %User{name: "Mary", age: 25} #=> %User{age: 25, name: "Mary"}
meg = %{john | name: "Meg"} #=> %User{age: 27, name: "Meg"}
bill = Map.merge(john, %User{name: "Bill", age: 23}) #=> %User{name: "Bill", age: 23}john.name #=> John
john[:name] #=> ** (UndefinedFunctionError) undefined function: User.fetch/2
is_map john #=> true
john.__struct__ #=> User
Map.keys(john) #=> [:__struct__, :age, :name]
```## Ranges
---Ranges are `struct`
- `range = 1..10`
- `Enum.reduce(1..3, 0, fn i, acc -> i + acc end) #=> 6`
- `Enum.count(range) #=> 10`
- `Enum.member?(range, 11) #=> false`## Protocols
---- `defprotocol Foo do ... end` => define protocol `Foo`
- `defimpl Blank, for: Integer do ... end` => implement that protocol `Integer`
- Here are all native data types that you can use: `Atom`, `BitString`, `Float`, `Function`, `Integer`, `List`, `Map`, `PID`, `Port`, `Reference`, `Tuple`.
```elixir
defprotocol Blank do
@doc "Returns true if data is considered blank/empty"
def blank?(data)
enddefimpl Blank, for: Integer do
def blank?(_), do: false
enddefimpl Blank, for: List do
def blank?([]), do: true
def blank?(_), do: false
enddefimpl Blank, for: Map do
def blank?(map), do: map_size(map) == 0
end# Structs do not share Protocol implementations with Map.
defimpl Blank, for: User do
def blank?(_), do: false
enddefimpl Blank, for: Atom do
def blank?(false), do: true
def blank?(nil), do: true
def blank?(_), do: false
endBlank.blank?(0) #=> false
Blank.blank?([]) #=> true
Blank.blank?([1, 2, 3]) #=> false
Blank.blank?("hello") #=> ** (Protocol.UndefinedError)
```
```elixir
# You can also implement a Protocol for Any. And in this case you can derive any Structdefimpl Blank, for: Any do
def blank?(_), do: false
enddefmodule DeriveUser do
@derive Blank
defstruct name: "john", age: 27
end
```Elixir built-in most common used protocols: `Enumerable`, `String.Chars`, `Inspect`.
## Nested data Structures
- `put_in/2` => Puts a value in a nested structure via the given path.
- `update_in/2` => Updates a nested structure via the given path.
- `get_and_update_in/2` => Gets a value and updates a nested data structure via the given
path.
```elixir
users = [
john: %{name: "John", age: 27, languages: ["Erlang", "Ruby", "Elixir"]},
mary: %{name: "Mary", age: 29, languages: ["Elixir", "F#", "Clojure"]}
]
users[:john].age #=> 27users = put_in users[:john].age, 31
users = update_in users[:mary].languages, &List.delete(&1, "Clojure")
```## Enums and Streams
---Lists and Maps are Enumerables
`Enum` module perform **eager** operations, meanwhile `Stream` module perform **lazy** operations.
```elixir
# eager Enum
1..100 |> Enum.map(&(&1 * 3)) |> Enum.sum #=> 15150# lazy Stream
1..100 |> Stream.map(&(&1 * 3)) |> Enum.sum #=> 15150
```## do/end Keyword List and Block Syntax
---In Elixir **Keyword List** syntax or do/end **Block** syntax:
```elixir
sky = :grayif sky == :blue do
:sunny
else
:cloudy
endif sky == :blue, do: :sunny, else: :cloudy
if sky == :blue, do: (
:sunny
), else: (
:cloudy
)
```## Conditional Flows (if/else/case/cond)
### if/else
```elixir
sky = :gray
if sky == :blue, do: :sunny, else: :cloudy
```### unless / else
```elixir
sky = :gray
unless sky != :blue, do: :sunny, else: :cloudy
```### case / when
```elixir
sky = {:gray, 75}
case sky, do: (
{:blue, _} -> :sunny
{_, t} when t > 80 -> :hot
_ -> :check_wheather_channel
)
```On **when guards** short-circuiting operators &&, || and ! are not allowed.
### cond
`cond` is equivalent as `if/else-if/else` statements.
```elixir
sky = :gray
cond do: (
sky == :blue -> :sunny
true -> :cloudy
)
```## The `with` macro
---- `with` => macro to combine multiple match clauses
- `<-` => a matching clause, on the left
- `=` => bare expression is allowed
- `else` => if some matching clause fails```elixir
opts = %{width: 10, height: 20}
with {:ok, width} <- Map.fetch(opts, :width),
{:ok, height} <- Map.fetch(opts, :height) do
{:ok, width * height}
else
:error ->
{:error, :wrong_data}
endopts = %{width: 10, height: 20}
with {:ok, width} <- Map.fetch(opts, :width),
{:ok, height} <- Map.fetch(opts, :height) do
{:ok, width * height}
else
:error ->
{:error, :wrong_data}
end#=> {:ok, 200}
```## Recursion
---There is traditional no for loop in Elixir, due to Elixir immutability There is a macro `for` that it's also called as `Comprehension` but it works differently from a traditional for loop. If you want a simple loop iteration you'll need to use `recursion`:
```elixir
defmodule Logger do
def log(msg, n) when n <= 0, do: ()
def log(msg, n) do
IO.puts msg
log(msg, n - 1)
end
end
Logger.log("Hello World!", 3)
# Hello World!
# Hello World!
# Hello World!
```In functional programming languages map and reduce are two major algorithm concepts. They can be implemented with recursion or using the `Enum` module.
`reduce` will reduce the array into a single element.
```elixir
defmodule Math do
def sum_list(list, sum \\ 0)
def sum_list([], sum), do: sum
def sum_list([head | tail], sum) do
sum_list(tail, head + sum)
end
end
Math.sum_list([1, 2, 3]) #=> 6Enum.reduce([1, 2, 3], 0, &+/2) #=> 6
```map modifies an existing array (new array with new modified values):
```elixir
defmodule Math do
def double([]), do: []
def double([head | tail]) do
[head * 2 | double(tail)]
end
end
Math.double([1, 2, 3]) #=> [2, 4, 6]Enum.map([1, 2, 3], &(&1 * 2)) #=> [2, 4, 6]
```## Comprehension -> the for loop
`Comprehension` is a syntax sugar for the very powerful `for special form`. You can have **generators** and **filters** in that.
- `for` => `comprehension`
- `->` => `generators`
- `:into` => Change return to another `Collectable` type.
You can iterate over `Enumrable` whats makes so close to a regular `for` loop on other languages:
```elixir
for n <- [1, 2, 3, 4], do: n * n
#=> [1, 4, 9, 16]
```You can also iterate over multiple `Enumerable` and then create a combination between them:
```elixir
for i <- [:a, :b, :c], j <- [1, 2], do: {i, j}
#=> [a: 1, a: 2, b: 1, b: 2, c: 1, c: 2]
```You can pattern match each element
```elixir
values = [good: 1, good: 2, bad: 3, good: 4]
for {:good, n} <- values, do: n * n
#=> [1, 4, 16]
```Generators use `->` and they have on the right an `Enumerable` and on the left a pattern matchable element variable.
You can have **filters** to filter **truthy elements**:
```elixir
for dir <- [".", "/"],
file <- File.ls!(dir),
path = Path.join(dir, file),
File.regular?(path) do
"dir=#{dir}, file=#{file}, path=#{path}"
end
#=> ["dir=., file=README.md, path=./README.md", "dir=/, file=.DS_Store, path=/.DS_Store"]
```You can `:into` to change the return type:
```elixir
for k <- [:foo, :bar], v <- 1..5, into: %{}, do: {k, v}
#=> %{bar: 5, foo: 5}
for k <- [:foo, :bar], v <- 1..5, into: [], do: {k, v}
#=> [foo: 1, foo: 2, foo: 3, foo: 4, foo: 5, bar: 1, bar: 2, bar: 3, bar: 4, bar: 5]
```## Anonymous Functions
- `fn` => define functions
- `->` => one line function definition
- `.` => call a function
- `when` => guards```elixir
add = fn a, b -> a + b end
add.(4, 5) #=> 9
```We can have multiple clauses and guards inside functions.
```elixir
calc = fn
x, y when x > 0 -> x + y
x, y -> x * y
end
calc.(-1, 6) #=> -6
calc.(4, 5) #=> 9
```## Modules And Named Functions
- `defmodule`
- `def` => functions (inside Modules)
- `defp` => private functions (")
- `when` => guard
- `@` => module attribute
- `__info__(:functions)` => list functions inside a module
- `defdelagate` => delegate functions
```elixir
defmodule Math do
def sum(a, b) when is_integer(a) and is_integer(b), do: a + b
endMath.sum(1, 2) #=> 3
Math.__info__(:functions) #=> [sum: 2]
```Module attribute works as constants, evaluated at compilation time:
```elixir
defmodule Math do
@foo :bar
def print, do: @foo
endMath.print #=> :bar
```Special Module attributes:
`@nsv`, `@moduledoc`,`@doc`,`@behaviour`, `@before_compile`
## Default Argument
```elixir
defmodule Concat do
def join(a, b, sep \\ " ") do
a <> sep <> b
end
endIO.puts Concat.join("Hello", "world") #=> Hello world
IO.puts Concat.join("Hello", "world", "_") #=> Hello_world
```Default values are **evaluated runtime**.
So this is correct syntax:
```elixir
defmodule DefaultTest do
def dowork(x \\ IO.puts "hello") do
x
end
end
DefaultTest.dowork #=> :ok
# hello
DefaultTest.dowork 123 #=> 123
DefaultTest.dowork #=> :ok
# hello
```Function with multiple clauses and a default value requires a function head where default values are set:
```elixir
defmodule Concat do
def join(a, b \\ nil, sep \\ " ") # head functiondef join(a, b, _sep) when is_nil(b) do
a
enddef join(a, b, sep) do
a <> sep <> b
end
endIO.puts Concat.join("Hello") #=> Hello
IO.puts Concat.join("Hello", "world") #=> Hello world
IO.puts Concat.join("Hello", "world", "_") #=> Hello_world
```## Function Capturing
- `&` => Function capturing
- `&1` => first argument
- `&2` => Secong argument and so on.`&` could be used with a module function.
When capturing you can use the function/operator with its arity.
```elixir
&(&1 * &2).(3, 4) #=> 12
(&*/2).(3, 4) #=> 12(&Kernel.is_atom(&1)).(:foo) #=> true
(&Kernel.is_atom/1).(:foo) #=> true
(&{:ok, [&1]}).(:foo) #=> {:ok, [:foo, :bar]}
(&[&1, &2]).(:foo, :bar) #=> [:foo, :bar]
(&[&1 | [&2]]).(:foo, :bar) #=> [:foo, :bar]
#####################
add3 = (&(&1+&2))
add3.(1,2)
```## Behaviours
---- `@callbacks` => defines a function to be implemented by other modules
- `::` => Separates the function defintion to its return type```elixir
defmodule Parser do
@callback parse(String.t) :: any
@callback extensions() :: [String.t]
enddefmodule JSONParser do
@behaviour Parserdef parse(str), do: # ... parse JSON
def extensions, do: ["json"]
end
```## Exceptions/Errors => raise/try/rescue
---Exceptions/Errors in Elixir are `structs`
- `raise "oops" #=> ** (RuntimeError) oops
- `raise ArgumentError #=> ** (ArgumentError) argument error`
- `raise ArgumentError, message: "oops" #=> ** (ArgumentError) oops`
- `defexception` => define an exception
- `try/rescue` => catches an error
- `throw/try/catch` => can be used as circuit breaking, but should be avoided
- `exit("my reason")` => exiting current process
- `after` => ensures some resource is cleaned up even if an exception was raised```elixir
defmodule MyError do
defexception message: "default message"
endis_map %MyError{} #=> true
Map.keys %MyError{} #=> [:__exception__, :__struct__, :message]raise MyError #=> ** (MyError) default message
raise MyError, message: "custom message" #=> ** (MyError) custom message
```You can rescue an error with:
```elixir
defmodule MyError do
defexception message: "default message"
endis_map %MyError{} #=> true
Map.keys %MyError{} #=> [:__exception__, :__struct__, :message]raise MyError #=> ** (MyError) default message
raise MyError, message: "custom message" #=> ** (MyError) custom message
````throw/catch` sometime is used for circuit breaking, but you can usually use another better way:
```elixir
try do
Enum.each -50..50, fn(x) ->
if rem(x, 13) == 0, do: throw(x)
end
"Got nothing"
catch
x -> "Got #{x}"
end
#=> "Got -39"Enum.find -50..50, &(rem(&1, 13) == 0)
#=> -39
````exit` can be caught but this is rare in Elixir:
```elixir
try do
exit "I am exiting"
catch
:exit, _ -> "not really"
end
#=> "not really"
```You can ommit `try` inside functions and use `rescue`, `catch`, `after` directly:
```elixir
def without_even_trying do
raise "oops"
after
IO.puts "cleaning up!"
end
```## IO
---- `IO.puts/1 "Hello"` => prints to stdout
- `IO.puts :stderr, "Hello"` => print to stderr
- `IO.gets "yes/no: "` => reads an user input##StringIO
---- `{:ok, pid} = StringIO.open("my-file.md")` => open a file
- `IO.read(pid, 2) #=> "he"` => read first 2 lines## File
---- `{:ok, file} = File.open "hello", [:write]` => open a file for writing.
- `IO.binwrite(file, "world")` => Writes into file
- `File.close(file)` => close file
- `File.read("my-file.md")` => read a file
- `File.stream!("my-file.md") |> Enum.take(10)` => read the first 10 lines
```elixir
{:ok, file} = File.open "my-file.md", [:write]
IO.binwrite file, "hello world"
File.close file
File.read "my-file.md" #=> {:ok, "hello world"}
```## Path
---- `Path.join` => joins
- `Path.expand("~/hello")` => expand full path## Process, Tasks and Agents
---Process in Elixir has the same concept as threads in a lot of other languages, but extremely lightweight in terms of memory and CPU. They are isolated from each other and communicate via message passing.
- `spawn/1` => fork a process
- `self()` => current process
- `Process.alive?(pid)` => check if process is still alive
- `send/2` => send message to another process
- `receive/1` => receive message from another process
- `after` => receive option to work with after timetout.
- `flush()` => prints out all messages received
- `spawn_link/1` => forks a process and link them , so failures are proagated.
- `Task.start/1`= > Starts a task
- `Task.start_link/1` => starts a task and links them to current process
- `Process.register(pid, :foo)` => register a name for a processThe idea is to have a supervisor that `spawn_link` new processes and when they fail the supervisor will restart them.
This is the basics for **Fail Fast** and Fault **Tolerant** in Elixir.Tasks are used in supervision trees.
```elixir
parent = self()spawn_link(fn -> send(parent, {:hello, self()}) end)
receive do: ({msg, pid} -> "#{inspect pid} => #{msg}"), after: (1_000 -> "nothing after 1s")Task.start_link(fn -> send(parent, {:hello, self()}) end)
receive do: ({msg, pid} -> "#{inspect pid} => #{msg}"), after: (1_000 -> "nothing after 1s")flush()
``````elixir
```
**State** can be stored in processes or using its abstraction: `Agent`.
Manual implementation of a storage using Elixir Processes:
```elixir
defmodule KV do
def start_link do
Task.start_link(fn -> loop(%{}) end)
enddefp loop(map) do
receive do
{:get, key, caller} ->
send caller, Map.get(map, key)
loop(map)
{:put, key, value} ->
loop(Map.put(map, key, value))
end
end
end
{:ok, pid} = KV.start_linksend pid, {:put, :hello, :world}
send pid, {:get, :hello, self()}
flush() #=> :world
```Implementation of a storage using `Agent`:
```elixir
{:ok, pid} = Agent.start_link(fn -> %{} end)
Agent.update(pid, fn map -> Map.put(map, :hello, :world) end)
Agent.get(pid, fn map -> Map.get(map, :hello) end)
```## alias, require, import and use
---In order to facilitate code reuse Elixir has: `alias`, `require`, `import` (directives) and `use` (macro).
- `alias Foo.Bar, as: Bar` => alias module, so Bar can be called instead of Foo.Bar
- `alias Foo.Bar` => `as` is optional on alias
- `require Foo` => requires and import functions from Foo so they can be called without the `Foo.` prefix
- `import List, only: [duplicate: 2]` => only option
- `import List, expect: [duplicate: 2]` => except option
- `import List, only: :macros ` => import only macros
- `import List, only: :functions` => import only functions
- `use Foo` => invokes the custom code defined in Foo as an extension point
- `alias MyApp.{Foo, Bar, Baz} ` => multiple aliases
- `require MyApp.{Foo, Bar, Baz}` => multiple require
- `import MyApp.{Foo, Bar, Baz}` => multiple import
All modules are defines inside `Elixir` namespace but it can be omitted for convenience.
`alias`, `require` and `import` are lexically scoped, which means that it will be valid just inside the scope it was defined. This is not a global scope.`require` is usually used to require Elixir macro code:
```elixir
Integer.is_odd(3) #=> ** (CompileError): you must require Integer before invoking the macro Integer.is_odd/1
require Integer
Integer.is_odd(3) #=> true
```
`use` call `__using__` when the module is being used:```elixir
defmodule Fruit do
defmacro __using__(option: option) do
IO.puts "options=#{inspect option}"
quote do: IO.puts "Using Fruit module"
end
enddefmodule Meal do
use Fruit, option: :hello
end
###Result###
#options=:hello
#Using Fruit module
#:ok
```## Meta Programming
---- `quote` => shows AST (Abstract Syntax Tree)
```elixir
quote do: 2 * 2 == 4
#=> {
#=> :==,
#=> [context: Elixir, import: Kernel],
#=> [
#=> {
#=> :*,
#=> [context: Elixir, import: Kernel],
#=> [2, 2]
#=> },
#=> 4
#=> ]
#=> }
```## Erlang Libraries
---- :crypto => crypto functions like `:crypto.hash/2`
- `:io` => io functions like `:io.format/2`
- `:digraph` => deal with digraphs
- `:ets` => large data structure in memory
- `:dets` => large data structure on disk
- `:math` => math functions like `:math.pi/0`
- `:queue` => first-in first-out structure
- `:rand` => rand functions like `:rand.uniform/0`
- `:zip` => handle zip files
- `:zlib` => handle gzip files