763 lines
11 KiB
Markdown
763 lines
11 KiB
Markdown
---
|
|
title: Elixir
|
|
category: Elixir
|
|
tags: [New]
|
|
updated: 2018-07-04
|
|
weight: -10
|
|
---
|
|
|
|
## Getting started
|
|
{: .-three-column}
|
|
|
|
### Hello world
|
|
{: .-prime}
|
|
|
|
```elixir
|
|
# hello.exs
|
|
defmodule Greeter do
|
|
def greet(name) do
|
|
message = "Hello, " <> name <> "!"
|
|
IO.puts message
|
|
end
|
|
end
|
|
|
|
Greeter.greet("world")
|
|
```
|
|
|
|
```bash
|
|
elixir hello.exs
|
|
# Hello, world!
|
|
```
|
|
{: .-setup}
|
|
|
|
### Variables
|
|
|
|
```elixir
|
|
age = 23
|
|
```
|
|
|
|
### Maps
|
|
|
|
```elixir
|
|
user = %{
|
|
name: "John",
|
|
city: "Melbourne"
|
|
}
|
|
```
|
|
|
|
```elixir
|
|
IO.puts "Hello, " <> user.name
|
|
```
|
|
{: .-setup}
|
|
|
|
### Lists
|
|
|
|
```elixir
|
|
users = [ "Tom", "Dick", "Harry" ]
|
|
```
|
|
{: data-line="1"}
|
|
|
|
```elixir
|
|
Enum.map(users, fn user ->
|
|
IO.puts "Hello " <> user
|
|
end)
|
|
```
|
|
|
|
### Piping
|
|
|
|
```elixir
|
|
source
|
|
|> transform(:hello)
|
|
|> print()
|
|
```
|
|
{: data-line="2,3"}
|
|
|
|
```elixir
|
|
# Same as:
|
|
print(transform(source, :hello))
|
|
```
|
|
|
|
These two are equivalent.
|
|
|
|
### Pattern matching
|
|
|
|
```elixir
|
|
user = %{name: "Tom", age: 23}
|
|
%{name: username} = user
|
|
```
|
|
{: data-line="2"}
|
|
|
|
This sets `username` to `"Tom"`.
|
|
|
|
### Pattern matching in functions
|
|
|
|
```elixir
|
|
def greet(%{name: username}) do
|
|
IO.puts "Hello, " <> username
|
|
end
|
|
|
|
user = %{name: "Tom", age: 23}
|
|
```
|
|
{: data-line="1"}
|
|
|
|
Pattern matching works in function parameters too.
|
|
|
|
Control flow
|
|
------------
|
|
{: .-three-column}
|
|
|
|
### If
|
|
|
|
```elixir
|
|
if false do
|
|
"This will never be seen"
|
|
else
|
|
"This will"
|
|
end
|
|
```
|
|
### Case
|
|
|
|
```elixir
|
|
case {1, 2, 3} do
|
|
{4, 5, 6} ->
|
|
"This clause won't match"
|
|
{1, x, 3} ->
|
|
"This will match and bind x to 2"
|
|
_ ->
|
|
"This will match any value"
|
|
end
|
|
```
|
|
|
|
### Cond
|
|
|
|
```elixir
|
|
cond do
|
|
1 + 1 == 3 ->
|
|
"I will never be seen"
|
|
2 * 5 == 12 ->
|
|
"Me neither"
|
|
true ->
|
|
"But I will (this is essentially an else)"
|
|
end
|
|
```
|
|
|
|
### With
|
|
|
|
```elixir
|
|
with {:ok, {int, _asdf}} <- Integer.parse("123asdf"),
|
|
{:ok, datetime, _utc_offset} <- DateTime.from_iso8601("2021-10-27T12:00:00Z") do
|
|
DateTime.add(datetime, int, :second)
|
|
# optional else clause. if not provided and an error occurs, the error is returned
|
|
else
|
|
:error -> "couldn't parse integer string"
|
|
{:error, :invalid_format} -> "couldn't parse date string"
|
|
_ -> "this will never get hit because all errors are handled"
|
|
end
|
|
```
|
|
|
|
### Errors
|
|
|
|
```elixir
|
|
try do
|
|
throw(:hello)
|
|
catch
|
|
message -> "Got #{message}."
|
|
after
|
|
IO.puts("I'm the after clause.")
|
|
end
|
|
```
|
|
|
|
## Types
|
|
|
|
### Primitives
|
|
|
|
| Sample | Type |
|
|
| --- | --- |
|
|
| `nil` | Nil/null |
|
|
| `true` _/_ `false` | Boolean |
|
|
| --- | --- |
|
|
| `?a` | Integer (ASCII) |
|
|
| `23` | Integer |
|
|
| `3.14` | Float |
|
|
| --- | --- |
|
|
| `'hello'` | Charlist |
|
|
| `<<2, 3>>` | Binary |
|
|
| `"hello"` | Binary string |
|
|
| `:hello` | Atom |
|
|
| --- | --- |
|
|
| `[a, b]` | List |
|
|
| `{a, b}` | Tuple |
|
|
| --- | --- |
|
|
| `%{a: "hello"}` | Map |
|
|
| `%MyStruct{a: "hello"}` | Struct |
|
|
| `fn -> ... end` | Function |
|
|
|
|
### Type checks
|
|
|
|
```elixir
|
|
is_atom/1
|
|
is_bitstring/1
|
|
is_boolean/1
|
|
is_function/1
|
|
is_function/2
|
|
is_integer/1
|
|
is_float/1
|
|
```
|
|
|
|
```elixir
|
|
is_binary/1
|
|
is_list/1
|
|
is_map/1
|
|
is_tuple/1
|
|
```
|
|
|
|
```elixir
|
|
is_nil/1
|
|
is_number/1
|
|
is_pid/1
|
|
is_port/1
|
|
is_reference/1
|
|
```
|
|
|
|
### Operators
|
|
|
|
```elixir
|
|
left != right # equal
|
|
left !== right # match
|
|
left ++ right # concat lists
|
|
left <> right # concat string/binary
|
|
left =~ right # regexp
|
|
```
|
|
|
|
Modules
|
|
-------
|
|
|
|
### Importing
|
|
|
|
```elixir
|
|
require Redux # compiles a module
|
|
import Redux # compiles, and you can use without the `Redux.` prefix
|
|
|
|
use Redux # compiles, and runs Redux.__using__/1
|
|
use Redux, async: true
|
|
|
|
import Redux, only: [duplicate: 2]
|
|
import Redux, only: :functions
|
|
import Redux, only: :macros
|
|
|
|
import Foo.{Bar, Baz}
|
|
```
|
|
|
|
### Aliases
|
|
|
|
```elixir
|
|
alias Foo.Bar, as: Bar
|
|
alias Foo.Bar # same as above
|
|
|
|
alias Foo.{Bar, Baz}
|
|
```
|
|
|
|
## String
|
|
|
|
### Functions
|
|
|
|
```elixir
|
|
import String
|
|
```
|
|
{: .-setup}
|
|
|
|
```elixir
|
|
str = "hello"
|
|
str |> length() # → 5
|
|
str |> codepoints() # → ["h", "e", "l", "l", "o"]
|
|
str |> slice(2..-1) # → "llo"
|
|
str |> split(" ") # → ["hello"]
|
|
str |> capitalize() # → "Hello"
|
|
str |> match(regex)
|
|
```
|
|
|
|
### Inspecting objects
|
|
|
|
```elixir
|
|
inspect(object, opts \\ [])
|
|
```
|
|
```elixir
|
|
value |> IO.inspect()
|
|
```
|
|
```elixir
|
|
value |> IO.inspect(label: "value")
|
|
```
|
|
|
|
## Numbers
|
|
|
|
### Operations
|
|
|
|
```elixir
|
|
abs(n)
|
|
round(n)
|
|
rem(a, b) # remainder (modulo)
|
|
div(a, b) # integer division
|
|
```
|
|
|
|
### Float
|
|
|
|
```elixir
|
|
import Float
|
|
```
|
|
{: .-setup}
|
|
|
|
```elixir
|
|
n = 10.3
|
|
```
|
|
{: .-setup}
|
|
|
|
```elixir
|
|
n |> ceil() # → 11.0
|
|
n |> ceil(2) # → 11.30
|
|
n |> to_string() # → "1.030000+e01"
|
|
n |> to_string([decimals: 2, compact: true])
|
|
```
|
|
|
|
```elixir
|
|
Float.parse("34") # → { 34.0, "" }
|
|
```
|
|
|
|
### Integer
|
|
|
|
```elixir
|
|
import Integer
|
|
```
|
|
{: .-setup}
|
|
|
|
```elixir
|
|
n = 12
|
|
```
|
|
{: .-setup}
|
|
|
|
```elixir
|
|
n |> digits() # → [1, 2]
|
|
n |> to_charlist() # → '12'
|
|
n |> to_string() # → "12"
|
|
n |> is_even()
|
|
n |> is_odd()
|
|
```
|
|
|
|
```elixir
|
|
# Different base:
|
|
n |> digits(2) # → [1, 1, 0, 0]
|
|
n |> to_charlist(2) # → '1100'
|
|
n |> to_string(2) # → "1100"
|
|
```
|
|
|
|
```elixir
|
|
parse("12") # → {12, ""}
|
|
undigits([1, 2]) # → 12
|
|
```
|
|
|
|
### Type casting
|
|
|
|
```elixir
|
|
Float.parse("34.1") # → {34.1, ""}
|
|
Integer.parse("34") # → {34, ""}
|
|
```
|
|
|
|
```elixir
|
|
Float.to_string(34.1) # → "3.4100e+01"
|
|
Float.to_string(34.1, [decimals: 2, compact: true]) # → "34.1"
|
|
```
|
|
|
|
## Map
|
|
|
|
### Defining
|
|
|
|
```elixir
|
|
m = %{name: "hi"} # atom keys (:name)
|
|
m = %{"name" => "hi"} # string keys ("name")
|
|
```
|
|
|
|
### Updating
|
|
|
|
```elixir
|
|
import Map
|
|
```
|
|
{: .-setup}
|
|
|
|
```elixir
|
|
m = %{m | name: "yo"} # key must exist
|
|
```
|
|
|
|
```elixir
|
|
m |> put(:id, 2) # → %{id: 2, name: "hi"}
|
|
m |> put_new(:id, 2) # only if `id` doesn't exist (`||=`)
|
|
```
|
|
|
|
```elixir
|
|
m |> put(:b, "Banana")
|
|
m |> merge(%{b: "Banana"})
|
|
m |> update(:a, &(&1 + 1))
|
|
m |> update(:a, fun a -> a + 1 end)
|
|
```
|
|
|
|
```elixir
|
|
m |> get_and_update(:a, &(&1 || "default"))
|
|
# → {old, new}
|
|
```
|
|
|
|
### Deleting
|
|
|
|
```elixir
|
|
m |> delete(:name) # → %{}
|
|
m |> pop(:name) # → {"John", %{}}
|
|
```
|
|
|
|
### Reading
|
|
|
|
```elixir
|
|
m |> get(:id) # → 1
|
|
m |> keys() # → [:id, :name]
|
|
m |> values() # → [1, "hi"]
|
|
```
|
|
|
|
```elixir
|
|
m |> to_list() # → [id: 1, name: "hi"]
|
|
# → [{:id, 1}, {:name, "hi"}]
|
|
```
|
|
|
|
### Deep
|
|
|
|
```elixir
|
|
put_in(map, [:b, :c], "Banana")
|
|
put_in(map[:b][:c], "Banana") # via macros
|
|
```
|
|
|
|
```elixir
|
|
get_and_update_in(users, ["john", :age], &{&1, &1 + 1})
|
|
```
|
|
|
|
### Constructing from lists
|
|
|
|
```elixir
|
|
Map.new([{:b, 1}, {:a, 2}])
|
|
Map.new([a: 1, b: 2])
|
|
Map.new([:a, :b], fn x -> {x, x} end) # → %{a: :a, b: :b}
|
|
```
|
|
|
|
### Working with structs
|
|
|
|
#### Struct to map
|
|
|
|
```elixir
|
|
Map.from_struct(%AnyStruct{a: "b"}) # → %{a: "b"}
|
|
```
|
|
|
|
#### Map to struct
|
|
|
|
```elixir
|
|
struct(AnyStruct, %{a: "b"}) # → %AnyStruct{a: "b"}
|
|
```
|
|
|
|
## List
|
|
|
|
```elixir
|
|
import List
|
|
```
|
|
{: .-setup}
|
|
|
|
```elixir
|
|
l = [ 1, 2, 3, 4 ]
|
|
```
|
|
{: .-setup}
|
|
|
|
```elixir
|
|
l = l ++ [5] # push (append)
|
|
l = [ 0 | list ] # unshift (prepend)
|
|
```
|
|
|
|
```elixir
|
|
l |> first()
|
|
l |> last()
|
|
```
|
|
|
|
```elixir
|
|
l |> flatten()
|
|
l |> flatten(tail)
|
|
```
|
|
|
|
Also see [Enum](#enum).
|
|
|
|
|
|
## Enum
|
|
|
|
### Usage
|
|
|
|
```elixir
|
|
import Enum
|
|
```
|
|
{: .-setup}
|
|
|
|
```elixir
|
|
list = [:a, :b, :c]
|
|
```
|
|
{: .-setup}
|
|
|
|
```elixir
|
|
list |> at(0) # → :a
|
|
list |> count() # → 3
|
|
list |> empty?() # → false
|
|
list |> any?() # → true
|
|
```
|
|
|
|
```elixir
|
|
list |> concat([:d]) # → [:a, :b, :c, :d]
|
|
```
|
|
|
|
Also, consider streams instead.
|
|
|
|
### Map/reduce
|
|
|
|
```elixir
|
|
list |> reduce(fn)
|
|
list |> reduce(acc, fn)
|
|
list |> map(fn)
|
|
list |> reject(fn)
|
|
list |> any?(fn)
|
|
list |> empty?(fn)
|
|
```
|
|
|
|
```elixir
|
|
[1, 2, 3, 4]
|
|
|> Enum.reduce(0, fn(x, acc) -> x + acc end)
|
|
```
|
|
|
|
## Tuple
|
|
|
|
### Tuples
|
|
|
|
```elixir
|
|
import Tuple
|
|
```
|
|
{: .-setup}
|
|
|
|
```elixir
|
|
t = { :a, :b }
|
|
```
|
|
|
|
```elixir
|
|
t |> elem(1) # like tuple[1]
|
|
t |> put_elem(index, value)
|
|
t |> tuple_size()
|
|
```
|
|
|
|
### Keyword lists
|
|
|
|
```elixir
|
|
list = [{ :name, "John" }, { :age, 15 }]
|
|
list[:name]
|
|
```
|
|
|
|
```elixir
|
|
# For string-keyed keyword lists
|
|
list = [{"size", 2}, {"type", "shoe"}]
|
|
List.keyfind(list, "size", 0) # → {"size", 2}
|
|
```
|
|
|
|
## Functions
|
|
|
|
### Lambdas
|
|
|
|
```elixir
|
|
square = fn n -> n*n end
|
|
square.(20)
|
|
```
|
|
|
|
### & syntax
|
|
|
|
```elixir
|
|
square = &(&1 * &1)
|
|
square.(20)
|
|
|
|
square = &Math.square/1
|
|
```
|
|
|
|
### Running
|
|
|
|
```elixir
|
|
fun.(args)
|
|
apply(fun, args)
|
|
apply(module, fun, args)
|
|
```
|
|
|
|
### Function heads
|
|
|
|
```elixir
|
|
def join(a, b \\ nil)
|
|
def join(a, b) when is_nil(b) do: a
|
|
def join(a, b) do: a <> b
|
|
```
|
|
|
|
## Structs
|
|
|
|
### Structs
|
|
|
|
```elixir
|
|
defmodule User do
|
|
defstruct name: "", age: nil
|
|
end
|
|
|
|
%User{name: "John", age: 20}
|
|
|
|
%User{}.struct # → User
|
|
```
|
|
|
|
See: [Structs](http://elixir-lang.org/getting-started/structs.html)
|
|
|
|
## Protocols
|
|
|
|
### Defining protocols
|
|
|
|
```elixir
|
|
defprotocol Blank do
|
|
@doc "Returns true if data is considered blank/empty"
|
|
def blank?(data)
|
|
end
|
|
```
|
|
|
|
```elixir
|
|
defimpl Blank, for: List do
|
|
def blank?([]), do: true
|
|
def blank?(_), do: false
|
|
end
|
|
|
|
Blank.blank?([]) # → true
|
|
```
|
|
|
|
### Any
|
|
|
|
```elixir
|
|
defimpl Blank, for: Any do ... end
|
|
|
|
defmodule User do
|
|
@derive Blank # Falls back to Any
|
|
defstruct name: ""
|
|
end
|
|
```
|
|
|
|
### Examples
|
|
|
|
- `Enumerable` and `Enum.map()`
|
|
- `Inspect` and `inspect()`
|
|
|
|
## Comprehensions
|
|
|
|
### For
|
|
|
|
```elixir
|
|
for n <- [1, 2, 3, 4], do: n * n
|
|
for n <- 1..4, do: n * n
|
|
```
|
|
|
|
```elixir
|
|
for {key, val} <- %{a: 10, b: 20}, do: val
|
|
# → [10, 20]
|
|
```
|
|
|
|
```elixir
|
|
for {key, val} <- %{a: 10, b: 20}, into: %{}, do: {key, val*val}
|
|
```
|
|
|
|
### Conditions
|
|
|
|
```elixir
|
|
for n <- 1..10, rem(n, 2) == 0, do: n
|
|
# → [2, 4, 6, 8, 10]
|
|
```
|
|
|
|
### Complex
|
|
|
|
```elixir
|
|
for dir <- dirs,
|
|
file <- File.ls!(dir), # nested comprehension
|
|
path = Path.join(dir, file), # invoked
|
|
File.regular?(path) do # condition
|
|
IO.puts(file)
|
|
end
|
|
```
|
|
|
|
## Misc
|
|
|
|
### Metaprogramming
|
|
|
|
```elixir
|
|
__MODULE__
|
|
__MODULE__.__info__
|
|
|
|
@after_compile __MODULE__
|
|
def __before_compile__(env)
|
|
def __after_compile__(env, _bytecode)
|
|
def __using__(opts) # invoked on `use`
|
|
|
|
@on_definition {__MODULE__, :on_def}
|
|
def on_def(_env, kind, name, args, guards, body)
|
|
|
|
@on_load :load_check
|
|
def load_check
|
|
```
|
|
|
|
### Regexp
|
|
|
|
```elixir
|
|
exp = ~r/hello/
|
|
exp = ~r/hello/i
|
|
"hello world" =~ exp
|
|
```
|
|
|
|
### Sigils
|
|
|
|
```elixir
|
|
~r/regexp/
|
|
~w(list of strings)
|
|
~s|strings with #{interpolation} and \x20 escape codes|
|
|
~S|no interpolation and no escapes|
|
|
~c(charlist)
|
|
```
|
|
|
|
Allowed chars: `/` `|` `"` `'` `(` `[` `{` `<` `"""`.
|
|
See: [Sigils](http://elixir-lang.org/getting-started/sigils.html)
|
|
|
|
### Type specs
|
|
|
|
```elixir
|
|
@spec round(number) :: integer
|
|
|
|
@type number_with_remark :: {number, String.t}
|
|
@spec add(number, number) :: number_with_remark
|
|
```
|
|
|
|
Useful for [dialyzer](http://www.erlang.org/doc/man/dialyzer.html).
|
|
See: [Typespecs](http://elixir-lang.org/getting-started/typespecs-and-behaviours.html)
|
|
|
|
### Behaviours
|
|
|
|
```elixir
|
|
defmodule Parser do
|
|
@callback parse(String.t) :: any
|
|
@callback extensions() :: [String.t]
|
|
end
|
|
```
|
|
|
|
```elixir
|
|
defmodule JSONParser do
|
|
@behaviour Parser
|
|
|
|
def parse(str), do: # ... parse JSON
|
|
def extensions, do: ["json"]
|
|
end
|
|
```
|
|
|
|
See: [Module](http://elixir-lang.org/docs/stable/elixir/Module.html)
|
|
|
|
## References
|
|
{: .-one-column}
|
|
|
|
- [Learn Elixir in Y minutes](https://learnxinyminutes.com/docs/elixir/)
|