Lenguaje de programación
Elixir is a dynamic, functional language for building scalable and maintainable applications.
(defmodule tut1
(export all))
(defun double (x)
(* 2 x))
import gleam/string
pub fn greet(name: String) -> String {
string.concat(["Hello ", name, "!"])
}
module Main where
import Prelude
import Effect (Effect)
import Effect.Console (log)
main :: Effect Unit
main = do
log "Hello Erlang!"
defmodule Math do
@moduledoc "Defines useful mathematical functions."
@doc """
Returns the sum of two numbers.
iex> Math.sum(2, 3)
5
"""
def sum(a, b) do
a + b
end
@doc "Returns the absolute value of a number."
def abs(a) when is_integer(a) and a < 0, do: -a
def abs(a) when is_integer(a), do: a
end
math.ex
Una expresión que no sea un literal (constante) o variable corresponde a un llamado a una función.
iex> 2 + 2
4
iex> Kernel.+(2, 2)
4
iex> unless 3**2 + 4**2 == 5**2 do
...> "pythagoras was wrong"
...> end
nil
iex> unless(
...> Kernel.==(
...> Kernel.+(Kernel.**(3, 2), Kernel.**(4, 2)),
...> Kernel.**(5, 2)
...> ),
...> [{:do, "pythagoras was wrong"}]
...> )
nil
Todo código Elixir válido es una expresión, por lo tanto produce un valor.
iex> n = 47
47
iex> parity =
...> if rem(n, 2) == 0 do
...> "even"
...> else
...> "odd"
...> end
"odd"
iex> parity
"odd"
Todo valor es inmutable, en lugar de modificar un objeto se crea otro a partir del objeto previo.
iex> x = %{a: 1}
%{a: 1}
iex> Map.put(x, :b, 2)
%{a: 1, b: 2}
iex> x
%{a: 1}
El operador |>
permite encadenar funciones.
iex> (IO.gets("enter a sentence: ")
...> |> String.split() # la expresión anterior pasa a ser el primer argumento
...> # entonces equivale a: String.split(IO.gets("enter a sentence: "))
...> |> IO.inspect() # imprimir una representación del valor (palabras)
...> |> Enum.map(&String.length/1)
...> |> Enum.sum())
enter a sentence: this is a sentence
["this", "is", "a", "sentence"]
15
[Pattern matching] allows us to assert on the shape or extract values from data structures.
iex> x = 0
0
iex> x = 1 # "reasignar" un mismo identificador
1
iex> x
1
iex> y = {1, 2} # tupla
{1, 2}
iex> {^x, z} = y # se hace match con el valor actual de x e y
{1, 2}
iex> z
2
iex> [x | xs] = [1, 4, 9, 16] # lista, y match con _cabeza_ y _cola_
[1, 4, 9, 16]
iex> {x, xs}
{1, [4, 9, 16]}
iex> [_, 9, _] = xs # omitimos partes con _
[4, 9, 16]
iex> match?([5, _, _], xs) # existe o no match?
false
iex> [4, x] = xs # la estructura no es la misma
... (MatchError) ...
iex> [4, x | _] = xs; x # tenemos 2 o más elementos
9
iex> [4, 8, x] = xs # el elemento 1 no corresponde
... (MatchError) ...
iex> IO.puts("hello")
hello
:ok
iex> :ok = IO.puts("hello") # match con un átomo, la operación fue correcta?
hello
:ok
iex> {:ok, file} = File.open("/etc/passwd", [:read])
{:ok, #PID<...>}
iex> {:ok, user_info} =
...> case IO.read(file, :line) do
...> user_info when is_binary(user_info) -> {:ok, user_info}
...> :eof -> {:ok, nil}
...> {:error, _} = error -> error
...> end
{:ok, "root:x:0:0::/root:/bin/bash\n"}
iex> :ok = File.close(file)
:ok
iex> 1
1
iex> 10_000
10000
iex> 0.1
0.1
iex> :math.pi()
3.141592653589793
iex> is_number(-0.5)
true
iex> is_integer(3.14)
false
iex> is_float(:math.pi())
true
iex> 1 / 2
0.5
iex> div(5, 2)
2
iex> rem(23, 7)
2
iex> 1 == 1.0
true
iex> 1 === 1.0
false
iex> true
true
iex> false
false
iex> 1 !== 1.0 === true
true
iex> is_boolean(:yes) === false
true
iex> IO.inspect(true) || IO.inspect(false)
true
true
iex> IO.inspect(false) && IO.inspect(true)
false
false
iex> "ABC"
"ABC"
iex> """
...> multiline
...> string
...> """
"multiline\nstring\n"
iex> ~s(Perl-like string delimiters)
"Perl-like string delimiters"
iex> String.downcase("Ártico") |> IO.inspect() |> String.length()
"ártico"
6
iex> "AaBbCc" =~ "aBbC"
true
iex> "AaBbCc" =~ ~r/^[abc]+$/i
true
iex> to_string(1.5)
"1.5"
iex> IO.puts("\\n ascii code is #{?\n}. π = #{math.pi()}")
\n ascii code is 10. π = 3.141592653589793
:ok
iex> is_atom(true)
true
iex> is_atom(false)
true
iex> is_atom(nil)
true
iex> :ok
:ok
iex> String.to_atom("error")
:error
iex> :"🤓" |> IO.inspect() |> Atom.to_string()
:"🤓"
"🤓"
iex> is_atom(String)
true
iex> :erlang.display(String)
Elixir.String
true
iex> :erlang.display(Elixir)
Elixir
true
[Tuples are] fixed-size containers for multiple elements.
iex> t1 = {:ok, "result"}
{:ok, "result"}
iex> elem(t1, 0)
:ok
iex> put_elem(t1, 1, "result_updated")
{:ok, "result_updated"}
iex> {}
{}
iex> {:a, :b, {:c, :d}} |> IO.inspect() |> tuple_size()
{:a, :b, {:c, :d}}
3
iex> File.open("/etc/shadow", [:read]) |> IO.inspect() |> elem(1)
{:error, :eacces}
:eacces
iex> {:ok, _} = Task.start(fn ->
...> Process.sleep(1000)
...> IO.puts("hello from process #{inspect(self())}")
...> end)
{:ok, #PID<0.124.0>}
hello from process #PID<0.124.0>
Linked lists hold zero, one, or more elements in the chosen order. [Lists] are internally represented in pairs containing the head and the tail of a list.
iex> []
[]
iex> [1, "dos", :tres, 4.0]
[1, "dos", :tres, 4.0]
iex> [1 | []]
[1]
iex> [1 | [2 | []]]
[1, 2]
iex> [x | xs] = [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
iex> {x, xs}
{1, [2, 3, 4, 5]}
iex> hd(xs)
2
iex> tl(xs)
[3, 4, 5]
iex> numbers = [1, 2, 3, 4]
[1, 2, 3, 4]
iex> [] = numbers
... (MatchError) ...
iex> [_, _] = numbers
... (MatchError) ...
iex> [_ | _] = numbers
[1, 2, 3, 4]
iex> [_, _ | _] = numbers
[1, 2, 3, 4]
iex> xs = []
[]
iex> xs = ["hello" | xs]
["hello"]
iex> xs = xs ++ ["world", "!"]
["hello", "world", "!"]
iex> List.delete(xs, "world")
["hello", "!"]
iex> ys = List.insert_at(xs, 1, "there")
["hello", "there", "world", "!"]
iex> List.zip([List.delete_at(ys, -1), tl(ys)])
{{"hello", "there"}, {"there", "world"}, {"world", "!"}]
iex> odds_r = 1..10//2
1..10//2
iex> evens_r = 2..10//2
2..10//2
iex> odds = Enum.to_list(odds_r)
[1, 3, 5, 7, 9]
iex> Enum.each(odds, fn x -> IO.puts("#{x}^#{x} = #{x ** x}") end)
1^1 = 1
3^3 = 27
...
9^9 = 387420489
:ok
iex> Enum.map(evens_r, &rem(&1, 3))
[2, 1, 0, 2, 1]
iex> randoms = Enum.map(1..10, fn _ -> :rand.uniform(10) end)
[1, 9, 1, 8, 3, 4, 10, 5, 5, 2]
iex> Enum.sort(randoms) |> Enum.reverse()
[10, 9, 8, 5, 5, 4, 3, 2, 1, 1]
iex> Enum.uniq(randoms)
[1, 9, 8, 3, 4, 10, 5, 2]
iex> Enum.at(randoms, 4)
3
Maps are the key-value data structure in Elixir. Key-value pairs in a map do not follow any order.
iex> user = %{first_name: "John", last_name: "Doe"}
%{first_name: "John", last_name: "Doe"}
iex> user.first_name
"John"
iex> user.second_name
... (KeyError) ...
iex> user[:last_name]
"Doe"
iex> user[:second_name]
nil
iex> Map.get(user, :first_name)
"John"
iex> Map.get(user, :second_name)
nil
iex> Map.fetch(user, :last_name)
{:ok, "Doe"}
iex> Map.fetch(user, :second_name)
:error
iex> Map.fetch!(user, :first_name)
"John"
iex> Map.fetch!(user, :second_name)
... (KeyError) ...
iex> user = %{first_name: "Jane", last_name: "Doe"}
%{first_name: "Jane", last_name: "Doe"}
iex> %{:first_name => _, :last_name => _} = user
%{first_name: "Jane", last_name: "Doe"}
iex> m1 = %{"name" => "map", "delimiter" => "%{...}"}
%{"delimiter" => "%{...}", "name" => "map"}
iex> m2 = %{user => {:map, Atom}, m1 => {:map, String}}
%{
%{first_name: "Jane", last_name: "Doe"} => {:map, Atom},
%{"delimiter" => "%{...}", "name" => "map"} => {:map, String}
}
iex> m3 = %{{:k1_1, :k1_2} => :v1, {:k2_1, :k2_2} => :v2}
%{{:k1_1, :k1_2} => :v1, {:k2_1, :k2_2} => :v2}
iex> is_map(m3)
true
iex> is_map_key(m3, {:k1_1, :k1_2})
true
iex> is_map_key(m3, {:k1_1, :k2_1})
false
iex> kz_cities = %{"Almaty" => 1_328_362, "Shymkent" => 454_583, "Karaganda" => 451_800}
%{"Almaty" => 1328362, "Karaganda" => 451800, "Shymkent" => 454583}
iex> %{} = kz_cities
%{"Almaty" => 1328362, "Karaganda" => 451800, "Shymkent" => 454583}
iex> %{"Shymkent" => shymkent} = kz_cities
%{"Almaty" => 1328362, "Karaganda" => 451800, "Shymkent" => 454583}
iex> shymkent
454583
iex> %{"Shymkent" => _, "Taraz" => taraz} = kz_cities
... (MatchError) ...
iex> players_by_country = %{
...> co: [{"Hermes Ibáñez", "coplayer104"}, {"Daniela López", "coplayer042"}],
...> ve: [{"José Martínez", "veplayer291"}, {"Andrés González", "veplayer091"}],
...> es: [{"Rubén Armadillo", "esplayer012"}, {"Rosa Guerrero", "esplayer053"}]
...> }
%{...}
iex> %{es: [{esp0_name, esp0_user} | _], co: [_, {_, cop1_user} | _]} = players_by_country
%{...}
iex> [esp0_name, esp0_user, cop1_user] |> Enum.each(&IO.inspect/1)
"Rubén Armadillo"
"esplayer012"
"coplayer042"
:ok
iex> m1 = %{a: 1, b: 2}
%{a: 1, b: 2}
iex> %{m1 | a: 3}
%{a: 3, b: 2}
iex> %{m1 | c: 4}
... (KeyError) ...
iex> Map.put(m1, :c, 4)
%{a: 1, b: 2, c: 4}
iex> Map.update(m1, :a, 1, fn x -> x + 2 end) # o &(&1 + 2)
%{a: 3, b: 2}
iex> Map.update(m1, :c, 3, &(&1 + 1)) |> Map.delete(:a)
%{b: 2, c: 3}
iex> Map.merge(m1, %{b: 3, c: 4})
%{a: 1, b: 3, c: 4}
proplists
)(…) a list that consists exclusively of two-element tuples. The first element (…) is known as the key, and it must be an atom. The second element, known as the value, can be any term.
iex> [{:a, 1}, {:b, 2}]
[a: 1, b: 2]
iex> [{:b, 2}, {a: 1}]
[b: 2, a: 1]
iex> kw1 = [a: 1, b: 2]
[a: 1, b: 2]
iex> kw2 = [b: 2, a: 1]
[b: 2, a: 1]
iex> [b: b, a: a] = kw1
... (MatchError) ...
iex> [b: b, a: a] = kw2
[b: 2, a: 1]
iex> {a, b}
{1, 2}
proplists
)iex> query = [table: "customers", limit: 10, sort: :customer_id]
[table: "customers", limit: 10, sort: :customer_id]
iex> query.limit
... (KeyError) ...
iex> query[:limit]
10
iex> query[:group_by]
nil
iex> Keyword.get(query, :sort)
:customer_id
iex> Keyword.fetch(query, :table)
{:ok, "customers"}
iex> Keyword.fetch!(query, :where)
... (KeyError) ...
proplists
)iex> grep = [extended: true, ignore_case: false, word: true, pattern: "username", pattern: "address"]
[...]
iex> grep[:pattern]
"username"
iex> Keyword.get_values(grep, :pattern)
["username", "address"]
iex> Enum.filter(grep, fn {k, _} -> k == :pattern end)
[pattern: "username", pattern: "address"]
iex> Enum.filter(grep, fn {_, v} -> is_boolean(v) end)
[extended: true, ignore_case: true, word: true]
iex> Keyword.put(grep, :pattern, "city")
[..., word: true, pattern: "city"]
iex> Keyword.update(grep, :extended, false, &(not IO.inspect(&1)))
true
[extended: false, ignore_case: true, ...]
iex> grep ++ [pattern: "website", extended: false]
[extended: true, ..., pattern: "address", pattern: "website", extended: false]
iex> Keyword.merge(grep, [pattern: "country", extended: false])
[ignore_case: true, word: true, pattern: "country", extended: false]
A struct is a tagged map that allows developers to provide default values for keys.
defmodule User do
defstruct [
:first_name,
:last_name,
:username,
:password,
:birthday,
logged_in: false,
avatar: "file://./priv/img/avatar/default.jpg"
]
end
user.ex
iex> default = %User{}
%User{
first_name: nil,
last_name: nil,
username: nil,
password: nil,
birthday: nil,
logged_in: false,
avatar: "file://./priv/img/avatar/default.jpg"
}
iex> :erlang.display(default)
#{'__struct__'=>'Elixir.User',avatar=><<"file://./priv/img/avatar/default.jpg">>,birthday=>nil,first_name=>nil,last_name=>nil,logged_in=>false,password=>nil,username=>nil}
true
iex> default.avatar
"file://./priv/img/avatar/default.jpg"
iex> default[:avatar]
... (UndefinedFunctionError) ...
iex> %User{avatar: avatar} = default
%User{...}
iex> avatar
"file://./priv/img/avatar/default.jpg"
iex> is_struct(default)
true
iex> john = %User{first_name: "John", last_name: "Doe", username: "johndoe"}
%User{...}
iex> john_map = Map.from_struct(john)
%{
avatar: "file://./priv/img/avatar/default.jpg",
birthday: nil,
...
}
iex> %{} = john
%User{...}
iex> %User{} = john_map
... (MatchError) ...
iex> john_init = %{first_name: "John", second_name: "Edward"}
%{first_name: "John", second_name: "Edward"}
iex> struct(User, john_init)
%User{
first_name: "John",
last_name: nil,
...
}
iex> struct!(User, john_init)
... (KeyError) ...
iex> default = %User{}
%User{...}
iex> %User{default | first_name: "Jane", last_name: "Doe"}
%User{
first_name: "Jane",
last_name: "Doe",
username: nil,
...
}
iex> Map.put(default, :username, "janedoe")
%User{
...,
username: "janedoe",
...
}
iex> Map.delete(default, :first_name) # ❌
%{...}
iex> Map.put(default, :second_name, nil) # ❌
%{...}
defmodule Complex do
defstruct [re: 0, im: 0]
def sum(%__MODULE__{} = z, %__MODULE__{} = w) do
%__MODULE__{re: z.re + w.re, im: z.im + w.im}
end
def product(%__MODULE__{} = z, %__MODULE__{} = w) do
re = z.re * w.re - z.im * w.im
im = z.re * w.im + z.im * w.re
%__MODULE__{re: re, im: im}
end
end
complex.ex
&module.function/arity
iex> complex_sum = &Complex.sum/2
&Complex.sum/2
iex> z = %Complex{re: 0.5, im: -0.5}
%Complex{re: 0.5, im: -0.5}
iex> w = %Complex{re: 2, im: -1.5}
%Complex{re: 2, im: -1.5}
iex> Complex.sum(z, w)
%Complex{re: 2.5, im: -2.0}
iex> complex_sum.(z, w)
%Complex{re: 2.5, im: -2.0}
iex> is_function(complex_sum)
true
iex> is_function(complex_sum, 2)
true
iex> complex_product = &Complex.product/2
#Function<...>
iex> is_function(complex_product, 3)
false
iex> complex_conjugate = &%Complex{re: &1.re, im: -&1.im}
#Function<...>
iex> z = %Complex{re: 0.125, im: -0.875}
%Complex{re: 0.125, im: -0.875}
iex> complex_conjugate.(z)
%Complex{re: 0.125, im: 0.875}
iex> w = %Complex{re: 0.0, im: 1.0}
%Complex{re: 0.0, im: 1.0}
iex> [^z, ^w] = [z, w] |> Enum.map(complex_conjugate) |> Enum.map(complex_conjugate)
[%Complex{re: 0.125, im: -0.875}, %Complex{re: 0.0, im: 1.0}]
fn
lambda o función anónimaiex> import Complex
Complex
iex> complex_dot_product = fn {a0, a1}, {b0, b1} ->
...> sum(product(a0, b0), product(a1, b1))
...> end
#Function<...>
iex> vz = {%Complex{re: 0.5, im: 0.5}, %Complex{re: -0.5, im: -0.5}}
{%Complex{re: 0.5, im: 0.5}, %Complex{re: -0.5, im: -0.5}}
iex> vw = {%Complex{re: 1.0, im: -1.0}, %Complex{re: -1.0, im: 0.0}}
{%Complex{re: 1.0, im: -1.0}, %Complex{re: -1.0, im: 0.0}}
iex> complex_dot_product.(vz, vw)
%Complex{re: 1.5, im: 0.5}
<<0xCA, 0xFE, 0xBA, 0xBE>>
;
<<b2::2, _::6>> = 0xBE
'abc' = [0x61, 0x62, 0x63]
make_ref/0
self/0
; send/2
; Process.list/0
Estructuras sintácticas que al evaluarse producen un valor. En Elixir toda estructura sintáctica válida es una expresión, y toda expresión distinta de un valor literal (constante) o una variable se traduce en un llamado a una función. Repasamos algunas funciones particulares que se asemejan a declaraciones o estructuras de control de otros lenguajes de programación.
Muchas de estas funciones son en realidad macros que se traducen en otras funciones llamadas formas especiales que en realidad no son macros ni funciones Erlang, sino que son directamente interpretadas por el compilador.
Estas funciones y macros predefinidos e importados por defecto se encuentran y
documentan en los módulos Kernel
y Kernel.SpecialForms
, el último
corresponde a todas las formas especiales definidas por Elixir pero solo se ha
definido para documentar estas formas. Los siguientes son algunos macros y
funciones definidos en el módulo Kernel
.
iex> +1
1
iex> -1
-1
iex> 1 + 1
2
iex> 2 - 1
1
iex> 1.5 * 2.0
3.0
iex> 3 / 2
1.5
iex> div(13, 4)
3
iex> rem(13, 4)
1
iex> 4 ** 4
256
iex> "AB" <> "C"
"ABC"
iex> first_name = "John"
"John"
iex> last_name = "Doe"
"Doe"
iex> "Dr. " <> first_name <> " " <> last_name
"Dr. John Doe"
iex> "Dr. #{first_name} #{last_name}"
"Dr. John Doe"
iex> [] ++ [1, 2, 3]
[1, 2, 3]
iex> [0, 1] ++ [2, 3]
[0, 1, 2, 3]
iex> [0, 0, 1, 0, 1, 0, 0, 1] -- [0, 0, 0, 1, 1]
[0, 0, 1]
iex> [1, 2, 3, 4, 5, 6] -- [2, 2, 4, 4]
[1, 3, 5, 6]
iex> 0 in [3, 2, 1]
false
iex> 10 in 0..10
true
iex> {:a, 1} in %{a: 1}
true
iex> {:a, 1} in [a: 2, b: 2]
false
iex> 1..10 |> IO.inspect() |> Enum.to_list()
1..10
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
iex> 1..10 |> Map.to_list()
[__struct__: Range, first: 1, last: 10, step: 1]
iex> String.slice("hello", ..)
"hello"
iex> powers = [1, 4, 9, 16, 25, 36]
[1, 4, 9, 16, 25, 36]
iex> Enum.slice(powers, 1..4)
[4, 9, 16, 25]
iex> Enum.slice(powers, 1..10//2)
[4, 16, 36]
iex> 1 == 1.0
true
iex> 1 === 1.0
false
iex> 1 != 1.0
false
iex> 1 !== 1.0
true
iex> 1 > 2
false
iex> 1 < 2
true
iex> 2 >= 2
true
iex> 3.5 <= 3.0
false
iex> IO.inspect(nil) || IO.inspect(true)
nil
true
true
iex> IO.inspect(nil) && IO.inspect(true)
nil
nil
iex> nil or false
... (BadBooleanError) ...
iex> IO.inspect(true) and IO.inspect(false)
true
false
false
iex> IO.inspect(true) or IO.inspect(false)
true
true
iex> not nil
... (ArgumentError) ...
iex> not true
false
iex> [true, false, nil, "", 0] |> Enum.map(&!/1)
[false, true, true, false, false]
# definir módulo
defmodule Math do
# anidar módulo -> Math.Complex
defmodule Complex do
# define una estructura
defstruct [re: 0.0, im: 0.0]
end
# definir función pública
def abs(x) when sign(x) === -1, do: -x
def abs(x) when sign(x) in [0, 1], do: x
# definir función privada
defp sign(x) when is_number(x) and x < 0, do: -1
defp sign(x) when x == 0, do: 0
defp sign(x) when is_number(x), do: 1
end
defdelegate/2
: Delega una función a otro módulo.defexception/1
: Define un tipo de excepción.defprotocol/2
: Define un protocolo.defimpl/3
: Implementa un protocolo.defmacro/2
: Define un macro público.defmacrop/2
: Define un macro privado.then/2
iex> then(5, &(&1 * 2))
10
iex> (IO.gets("your name: ")
...> |> String.trim_trailing()
...> |> then(&"hello, #{&1}!")
...> |> IO.puts())
your name? Esteban
hello, Esteban
:ok
iex> (1..100
...> |> Enum.split_with(&(rem(&1, 2) === 0))
...> |> IO.inspect()
...> |> then(fn {evens, odds} -> Enum.sum(evens) - Enum.sum(odds) end))
{[2, 4, 6, ...],
[1, 3, 5, ...]}
50
if/2
false
y nil
iex> ({n, ""} = IO.gets("enter a number: ")
...> |> String.trim_trailing()
...> |> Integer.parse())
enter a number: 63
63
iex> if(rem(n, 2) === 0, do: "even", else: "odd")
"odd"
iex> if rem(n, 2) === 0 do
...> "even"
...> else
...> "odd"
...> end
"odd"
iex> nil
nil
iex> |> if do; "truthy"; else; "falsy"; end
"falsy"
iex> |> String.length()
5
iex> |> then(&if(&1 === 0, do: :empty))
nil
if/2
iex> Enum.each(1..30, fn n ->
...> if rem(n, 15) === 0 do
...> "FizzBuzz"
...> else
...> if rem(n, 3) === 0 do
...> "Fizz"
...> else
...> if rem(n, 5) === 0 do
...> "Buzz"
...> else
...> Integer.to_string(n)
...> end
...> end
...> end
...> end)
1
2
Fizz
4
Buzz
...
FizzBuzz
if/2
iex> Enum.each(
...> 1..30,
...> &(if(
...> rem(&1, 15) === 0,
...> do: "FizzBuzz",
...> else:
...> if(
...> rem(&1, 3) === 0,
...> do: "Fizz",
...> else:
...> if(
...> rem(&1, 5) === 0,
...> do: "Buzz",
...> else: Integer.to_string(&1)
...> )
...> )
...> )
...> |> IO.puts())
...> )
1
2
Fizz
4
Buzz
...
FizzBuzz
unless/2
if not ...
iex> people = %{"bob" => "bob@example.com", "alice" => "alice@example.com"}
%{"bob" => "bob@example.com", "alice" => "alice@example.com"}
iex> [nil, "", "alice", "john"] |> Enum.map(fn name ->
...> unless is_nil(name) or name == "" do
...> IO.puts("searching #{name}")
...> people[name]
...> end
...> end)
searching alice
searching john
[nil, nil, "alice@example.com", nil]
iex> "" |> unless(do: "falsy", else: "truthy")
"truthy"
match?/2
iex> match?(a, {:ok, "result"})
warning: ...
true
iex> match?({:ok, result}, {:ok, "result"})
warning: ...
true
iex> result
... (CompileError) ...
iex> match?([_ | _], [])
false
iex> match?(%{a: _, b: _}, %{c: 3, b: 2, a: 1})
true
iex> match?(%{c: _}, %{a: 1, b: 2})
false
iex> match?({:ok, result} when is_integer(result), {:ok, "result"})
false
raise
iex> 1 / 0
... (ArithmeticError) ...
iex> %ArithmeticError{}
%ArithmeticError{message: "bad argument in arithmetic expression"}
iex> %ArithmeticError{} |> Map.to_list()
[
__exception__: true,
__struct__: ArithmeticError,
message: "bad argument in arithmetic expression"
]
iex> raise ArithmeticError
... (ArithmeticError) bad argument in arithmetic expression
...
iex> raise ArithmeticError, message: "my message"
... (ArithmeticError) my message
...
iex> raise %ArithmeticError{message: "my struct message"}
... (ArithmeticError) my struct message
...
iex> ArithmeticError.message(%ArithmeticError{})
"bad argument in arithmetic expression"
raise
iex> a = -1
-1
iex> :math.sqrt(a)
... (ArithmeticError) ...
iex> try do
...> {:ok, :math.sqrt(a)}
...> rescue
...> ArithmeticError ->
...> :error
...> exception ->
...> IO.puts("oops!")
...> reraise exception, __STACKTRACE__
...> end
:error
throw/1
iex> name = "Ae4eA"
"Ae4eA"
iex> try do
...> name = String.trim(name) |> String.capitalize()
...> unless String.length(name) <= 16, do: throw({:error, :name_too_long})
...> unless name =~ ~r/^[A-Z][a-z]*$/, do: throw({:error, :invalid_chars})
...> {:ok, name}
...> catch
...> {:error, error} -> error
...> end
:invalid_chars
use
Introduce el código (AST) generado por el macro __using__/1
del módulo
indicado, opcionalmente un keyword list permite pasar opciones al macro.
defmodule HelloRouter do
use Plug.Router
plug :match
plug :dispatch
get "/hello" do
name = conn.params[:name] || "world"
send_resp(conn, 200, "hello, #{name}!")
end
match _ do
send_resp(conn, 404, "ENOENT")
end
end
hello_router.ex
use
defmodule Plug.Router do
# ...
defmacro __using__(opts) do
quote location: :keep do
import Plug.Router
@plug_router_to %{}
@before_compile Plug.Router
use Plug.Builder, unquote(opts)
# ...
end
end
# ...
end
plug/router.ex
use
use
defmodule HelloRouter do
import Plug.Router
@plug_router_to %{}
@before_compile Plug.Router
use Plug.Builder, []
# ...
plug :match
plug :dispatch
# ...
end
|>/2
defmodule NaivePipe do
defmacro arg |> {name, meta, args} do
{name, meta, [arg | args]}
end
end
naive_pipe.ex
|>/2
iex> import Kernel, except: ["|>": 2]
Kernel
iex> 1..100 |> Enum.length()
... (CompileError) ...: undefined function |>/2 ...
iex> import NaivePipe
NaivePipe
iex> (1..100
...> |> Enum.map(&(&1 ** 2 |> Integer.digits() |> Enum.sum()))
...> |> Enum.sum())
1480
Sigils are useful to encode text with their own escaping rules, such as regular expressions, datetimes, and others.
iex> ~D[2023-06-09] |> IO.inspect() |> Map.to_list()
~D[2023-06-09]
[__struct__: Date, calendar: Calendar.ISO, day: 9, month: 6, year: 2023]
iex> ~T[20:53:38.408] |> IO.inspect() |> Map.to_list()
[
__struct__: Time,
calendar: Calendar.ISO,
hour: 20,
microsecond: {408000, 3},
minute: 53,
second: 38
]
iex> "0xcafebabe" =~ ~r/^0x[0-9a-f]+$/i
true
iex> last_name = "doe"
"doe"
iex> "John Jr. Doe" =~ ~r/\b#{last_name}\b/i
true
iex> "b###c" =~ ~R/^[a-z]#{2,}+[a-z]$/
true
iex> (Kernel.__info__(:macros)
...> |> Keyword.keys()
...> |> Enum.uniq()
...> |> Enum.map(&Atom.to_string/1)
...> |> Enum.filter(&String.starts_with?(&1, "sigil_")))
["sigil_C", "sigil_D", "sigil_N", "sigil_R", "sigil_S", "sigil_T", "sigil_U",
"sigil_W", "sigil_c", "sigil_r", "sigil_s", "sigil_w"]
iex> h(sigil_w)
Handles the sigil ~w for list of words.
It returns a list of "words" split by whitespace. Character unescaping and
interpolation happens for each word.
...
## Examples
iex> ~w(foo #{:bar} baz)
["foo", "bar", "baz"]
...
El concepto special form es frecuente en el contexto de la familia de
lenguajes funcionales LISP. Por ejemplo, en el estándar ANSI de Common Lisp se
define como una forma (unidad de evaluación o ejecución de un programa), que
tiene una sintáxis y/o reglas de evaluación especiales que modifican el entorno
de ejecución y/o el flujo de control. Algunas formas especiales en Common Lisp
serían el condicional if
, let
y let*
para declaración de variables,
progn
para ejecutar otras formas secuencialmente, entre otras.
En Elixir son los building blocks del lenguaje, es decir que todo programa de
Elixir consiste de una secuencia de formas especiales. A diferencia del módulo
Kernel
, no están implementadas en Elixir sino directamente en el compilador,
que actualmente está implementado en Erlang. Por ejemplo, el macro if
está
definido en términos de la forma especial case
, el macro unless
está
definido usando if
, por lo tanto también termina traducido en case
. El
módulo Kernel.SpecialForms
documenta estas estructuras, pero no se debe
importar o usar, puesto que las formas están definidas globalmente.
%{} |
Mapas |
& |
Funciones |
. |
Acceso a módulos, funciones, mapas, estructuras |
= |
Pattern matching |
fn |
Funciones lambda |
try |
Manejo de errores y finalización |
^ |
Variables fijadas en pattern matching |
{} |
Tuplas |
Las expresiones no literales en Elixir se representan internamente con tuplas de
3 elementos, el primero de los cuales es un átomo o tupla que se refiere a una
función, macro o forma especial, el segundo metadatos de compilación y el
tercero una lista de argumentos. Para generar esta representación para un
programa podemos usar la forma especial quote
.
iex> quote do: 1 + 1
{:+, [...], [1, 1]}
iex> quote do: (1 - 1; 2 * 2)
{:__block__, [], [{:-, [...], [1, 1]}, {:*, [...], [2, 2]}]}
iex> quote do: 1 + 2 + 3
{:+, [...],
[{:+, [...], [1, 2]}, 3]}
iex> quote do: x.y(z)
{{:., [], [{:x, [], ...}, :y]}, [], [{:z, [], ...}]}
iex> quote do
...> if true do
...> {:ok, result}
...> else
...> :error
...> end
...> end
{:if, [...],
[true, [do: {:ok, {:result, [], ...}}, else: :error]]}
iex> quote do: fn x -> x + 1 end
{:fn, [],
[
{:->, [],
[
[{:x, [], ...}],
{:+, [...], [{:x, [], ...}, 1]}
]}
]}
cond
iex> cond do
...> false -> IO.inspect(1)
...> true -> IO.inspect(2)
...> end
2
2
iex> cond do
...> false -> IO.inspect(1)
...> false -> IO.inspect(2)
...> end
... (CondClauseError) ...
iex> cond do
...> true -> IO.inspect(1)
...> true -> IO.inspect(2)
...> end
1
1
iex> cond do
...> nil -> IO.inspect(1)
...> false -> IO.inspect(2)
...> 0 -> IO.inspect(3)
...> true -> IO.inspect(4)
...> end
3
3
case
iex> result = {:ok, [1, 2, 3], [1, 4, 9]}
{:ok, [1, 2, 3], [1, 4, 9]}
iex> case result do
...> :ok -> IO.inspect(1)
...> {:ok, _} -> IO.inspect(2)
...> {:ok, _, _} -> IO.inspect(3)
...> end
3
3
iex> case result do
...> {:ok, x, y} when is_binary(x) -> y
...> {:ok, [x | xs], []} -> {x, xs}
...> {:ok, [x | xs], [y | ys]} -> {x + y, xs ++ ys}
...> {:ok, [1 | xs], [1 | ys]} -> xs ++ ys
...> end
{2, [2, 3, 4, 9]}
iex> case result do
...> :ok -> IO.puts("ok")
...> :error -> IO.puts("error")
...> end
... (CaseClauseError) ...
with
with :ok <- MyModule.operation1(),
{:ok, foo} <- MyModule.operation2(),
{:ok, bar} <- MyMoudle.operation3(foo),
%{baz: baz} <- foo[:data] do
MyModule.operation4(baz)
else
:error -> {:error, :unknown}
{:error, error} = result -> result
error -> {:error, :unknown, error}
end
with
iex> calc_length = fn ->
...> with input when is_binary(input) <- IO.gets("enter x: "),
...> input <- String.trim(input),
...> {x, ""} when is_number(x) and x > 0 <- Float.parse(input),
...> input when is_binary(input) <- IO.gets("enter y: "),
...> input <- String.trim(input),
...> {y, ""} when is_number(y) and y > 0 <- Float.parse(input) do
...> {:ok, :math.sqrt(x * x + y * y)}
...> else
...> _ -> :error
...> end
...> end
#Function<...>
iex> calc_length.()
enter x: x
:error
iex> calc_length.()
enter x: 1.5
enter y: -3
:error
iex> calc_length.()
enter x: 3
enter y: 4
{:ok, 5.0}
for
iex> squares = [0, 1, 4, 9, 16, 25, 36, 49, 64]
[0, 1, 4, 9, 16, 25, 36, 49, 64]
iex> for n <- squares, do: n
[0, 1, 4, 9, 16, 25, 36, 49, 64]
iex> for n <- squares, rem(n, 2) == 0, do: n
[0, 4, 16, 36, 64]
iex> squares = for n <- 0..8, do: n * n
[0, 1, 4, 9, 16, 25, 36, 49, 64]
iex> for a <- [1, 2, 3], b <- [4, 5, 6], do: {a, b}
[{1, 4}, {1, 5}, {1, 6}, {2, 4}, {2, 5}, {2, 6}, {3, 4}, {3, 5}, {3, 6}]
iex> for a when rem(a, 2) == 0 <- [1, 2, 3, 4],
...> b when rem(b, 2) == 1 <- [5, 6, 7, 8] do
...> {a, b}
...> end
[{2, 5}, {2, 7}, {4, 5}, {4, 7}]
iex> pairs = fn list ->
...> for {a, i} <- Enum.with_index(list),
...> {b, j} <- Enum.with_index(list),
...> j > i do
...> {a, b}
...> end
...> end
#Function<...>
iex> pairs.(1..5)
[{1, 2}, {1, 3}, {1, 4}, {1, 5}, {2, 3}, {2, 4}, {2, 5}, {3, 4}, {3, 5}, {4, 5}]
for
iex> for line <- IO.stream(:line) |> Stream.take(5),
...> line = String.trim(line),
...> String.starts_with?(line, "#") do
...> String.trim(line, "#")
...> end
abcde
#x
qwerty
##123
16#ffff
["x", "123"]
try
try do
do_something()
rescue
ArgumentError ->
handle_argument_error()
exception in [ArithmeticError, CaseClauseError] ->
handle_arith_or_clause_error(exception)
exception ->
handle_other_error(exception)
catch
x when is_number(x) ->
handle_thrown_number(x)
value ->
handle_thrown_value(value)
after
do_something_under_any_circumstance()
end
try
iex> try_div = fn a, b ->
...> try do
...> {:ok, div(a, b)}
...> rescue
...> ArithmeticError -> :error
...> end
...> end
#Function<...>
iex> try_div.(9, 3)
{:ok, 3}
iex> try_div.(-1, 0)
:error
try
iex> super_optimized_sum = fn a, b ->
...> try do
...> unless is_number(a), do: throw({:error, :a_is_not_a_number})
...> unless is_number(b), do: throw({:error, :b_is_not_a_number})
...> if a == 0, do: throw({:ok, b})
...> if b == 0, do: throw({:ok, a})
...> IO.puts("penalization! (#{a}, #{b})")
...> Process.sleep(1000)
...> {:ok, a + b}
...> catch
...> {:error, _} = result -> result
...> {:ok, _} = result -> result
...> other -> throw(other)
...> end
...> end
#Function<...>
iex> for {a, b} <- [{:a, 1}, {1, :b}, {1, 0}, {0.0, 0.1}, {0.1, 0.3}] do
...> super_optimized_sum.(a, b)
...> end
penalization! {0.1, 0.3}
[error: :a_is_not_a_number, error: :b_is_not_a_number, ok: 1, ok: 0.1, ok: 0.4]
try
case File.open("/etc/passwd", [:read]) do
{:ok, file} ->
try do
do_something_with_users_database(file)
after
File.close(file)
end
_ ->
:error
end
alias
- Define una abreviación de un nombre de módulo.import
- Importa definiciones públicas de un módulo.require
- Permite usar los macros exportados por un módulo.unquote
quote
iex> print_ast = &(IO.inspect(&1) |> Macro.to_string() |> IO.puts())
#Function<...>
iex> {a, b} = {2, 4}
{2, 4}
iex> quote(do: a + b) |> print_ast.()
{:+, [...],
[
{:a, [], ...},
{:b, [], ...}
]}
a + b
:ok
iex> quote(do: unquote(a) + unquote(b)) |> print_ast.()
{:+, [...], [1, 2]}
1 + 2
:ok
unquote
defmodule MyMacros do
defmacro match_bad(pattern, expr) do
quote do
case expr do
pattern -> true
_ -> false
end
end
end
defmacro match(pattern, expr) do
quote do
case unquote(expr) do
unquote(pattern) -> true
_ -> false
end
end
end
end
my_macros.ex
unquote
Erlang/OTP 25 ...
warning: variable "expr" is unused ...
...
iex> require MyMacros
MyMacros
iex> MyMacros.match_bad({:ok, _}, {:ok, "result"})
... (CompileError) ...
iex> MyMacros.match({:ok, _}, {:ok, "result"})
true
iex> MyMacros.match({:ok, _}, :error)
false
iex> print_expansion = &(Macro.expand(&1, __ENV__) |> Macro.to_string() |> IO.puts())
#Function<...>
iex> quote(do: MyMacros.match_bad(1, 2)) |> print_expansion.()
case expr do
pattern -> true
_ -> false
end
:ok
iex> quote(do: MyMacros.match(1, 2)) |> print_expansion.()
case 2 do
1 -> true
_ -> false
end
:ok
iex> defmodule A, do: nil
{:module, A, <<...>>, nil}
iex> A.__info__(:functions)
[]
iex> defmodule B do
...> IO.puts("hello from B!")
...> def hello(), do: IO.puts("hello from B.hello()!")
...> defp hello_priv(), do: IO.puts("hello from B.hello_priv()!")
...> def hello2(name \\ "B.hello2()") do
...> IO.puts("hello, #{name}!")
...> hello_priv()
...> end
...> end
hello from B!
{:module, B, <<...>>, :ok}
iex> B.__info__(:functions)
[hello: 0, hello2: 0, hello2: 1]
iex> B.hello
hello from B.hello()!
:ok
iex> B.hello2("world")
hello, world!
hello from B.hello_priv()!
:ok
defmodule Arithmetic do
defmodule Integers do
def sum(a, 0) when is_integer(a), do: a
def sum(0, b) when is_integer(b), do: b
def sum(a, b) when is_integer(a) and is_integer(b), do: a + b
def sub(a, 0) when is_integer(a), do: a
def sub(0, b) when is_integer(b), do: b
def sub(a, b) when is_integer(a) and is_integer(b), do: a - b
def mul(a, 0) when is_integer(a), do: 0
def mul(a, 1) when is_integer(a), do: a
def mul(a, -1) when is_integer(a), do: -a
def mul(a, b) when is_integer(a) and is_integer(b) and b > 1, do: a + mul(a, b - 1)
def mul(a, b) when is_integer(a) and is_integer(b) and b < 0, do: -a - mul(a, -b - 1)
end
end
arithmetic.ex
$ elixirc arithmetic.ex
$ ls
arithmetic.ex Elixir.Arithmetic.Integers.beam
$ iex
iex> exports(Arithmetic.Integers)
mul/2 sub/2 sum/2
iex> Arithmetic.Integers.sum(2, 3)
5
iex> Arithmetic.Integers.sub(3, 4)
-1
iex> Arithmetic.Integers.mul(20, 30)
600
iex> Arithmetic.Integers.mul(-16, -256)
4096
alias
iex> alias Arithmetic, as: Arith
Arithmetic
iex> Arith
Arithmetic
iex> :erlang.display(Arith)
'Elixir.Arithmetic'
true
iex> Arith.Integers.sub(0, 0)
0
iex> alias Arithmetic.Integers
Arithmetic.Integers
iex> Integers
Arithmetic.Integers
iex> Integers.sum(2, 2)
4
iex> alias Arith.Integers, as: IntOperations
Arithmetic.Integers
iex> IntOperations
Arithmetic.Integers
iex> IntOperations.mul(1, 1)
1
alias
iex> alias A
A
iex> alias A, as: Z
A
iex> Z
A
iex> alias A.B
A.B
iex> B
A.B
iex> alias A.B.C.D
A.B.C.D
iex> D
A.B.C.D
iex> alias A.B.C.D, as: Y
A.B.C.D
iex> Y
A.B.C.D
import
iex> import Arithmetic.Integers
Arithmetic.Integers
iex> mul(-8, 7)
-56
iex> import Arithmetic.Integers, only: [sum: 2]
Arithmetic.Integers
iex> mul(-8, 7)
... (CompileError) ...
iex> sum(-1, -1)
-2
iex> import Arithmetic.Integers, except: [sum: 2]
Arithmetic.Integers
iex> sum(-1, -1)
... (CompileError) ...
iex> sub(3, -4)
7
defmodule MyFunctions do
defp ok(a), do: {:ok, a}
def foo(atom \\ :baz) when is_atom(atom) do
ok(atom)
end
def get_user_name(%{name: name, id: id} = _user) do
name || make_default_name(id)
end
defp make_default_name(id) when is_integer(id) do
"user#{Integer.to_string(id) |> String.pad_leading(8, "0")}"
end
def first_arg(a, _), do: a
def second_arg(_, a), do: a
def make_grep_cmd(opts \\ []) when is_list(opts) do
opts = Keyword.merge([ignore_case: true, extended_regexp: true], opts)
"/usr/bin/grep #{args_to_str(opts)}"
end
defp args_to_str(args) do
OptionParser.to_argv(args) |> Enum.join(" ")
end
end
@
)defmodule MyModule do
@moduledoc "Documentation for the A module."
@on_load :on_load_my_module
def on_load_my_module() do
IO.puts("hello from loaded module #{__MODULE__}!")
end
@doc "Returns its first argument."
@spec identity(term) :: term
def identity(value), do: value
@my_magic_number 0xCAFEBABE
@doc """
Returns this module's magic number.
iex> magic_number
3405691582
"""
def magic_number, do: @my_magic_number
end
my_module.ex
@
)$ elixirc my_module.ex
$ ls
Elixir.MyModule.beam my_module.ex
$ iex
iex> MyModule.magic_number
hello from loaded module Elixir.MyModule!
3405691582
iex> MyModule.my_magic_number
... (UndefinedFunctionError) ...
iex> MyModule.@my_magic_number
... (CompileError) ...
defmodule Cache do
@doc "Retrieves a value by key."
@callback get(key :: any) :: any
@doc "Inserts a value."
@callback put(key :: any, value :: any) :: :ok | {:error, atom}
@doc "Deletes a value by key."
@callback delete(key :: any) :: :ok | {:error, atom}
end
defmodule MapCache do
@behaviour Cache
@dict_key :map_cache
@impl true
def get(key) do
Process.get(@dict_key, %{}) |> Map.fetch(key)
end
@impl true
def put(key, value) do
map = Process.get(@dict_key, %{}) |> Map.put(key, value)
Process.put(@dict_key, map)
:ok
end
@impl true
def delete(key) do
map = Process.get(@dict_key, %{}) |> Map.delete(key)
Process.put(@dict_key, map)
:ok
end
end
defmodule User do
@moduledoc "Defines the User structure."
defstruct [:id, :username, :first_name, :last_name]
@typedoc "An User object."
@type t :: %__MODULE__{
id: pos_integer,
username: String.t(),
first_name: String.t(),
last_name: String.t()
}
@typedoc "An User DTO map."
@type dto :: %{String.t() => any}
@doc "Converts an User object to a DTO map."
def to_dto(%User{} = user) do
%{
"username" => user.username,
"firstName" => user.first_name,
"lastName" => user.last_name
}
end
end
defmodule MyMacros do
defmacro if(condition, opts) do
do_block = opts[:do]
else_block = opts[:else]
quote do
case unquote(condition) do
x when x in [false, nil] -> unquote(else_block)
_ -> unquote(do_block)
end
end
end
defmacro unless(condition, opts) do
quote do
if !unquote(condition), unquote(opts)
end
end
defmacro defdebug(call, opts) do
block = Keyword.fetch!(opts, :do)
block =
quote do
IO.inspect(binding())
unquote(block)
end
opts = Keyword.put(opts, :do, block)
quote do
def unquote(call), unquote(opts)
end
end
end
my_macros.ex
iex> require MyMacros
MyMacros
iex> MyMacros.if 2 < 3, do: :lesser, else: :greater_or_equal
:lesser
iex> fruits = ["apple", "orange", "pineapple"]
["apple", "orange", "pineapple"]
iex> MyMacros.if "guava" not in fruits do
...> :ok
...> else
...> :error
...> end
:ok
iex> errors = ["username is empty", "password is empty"]
["username is empty", "password is empty"]
iex> MyMacros.unless Enum.empty?(errors) do
...> IO.puts("there are errors!")
...> Enum.each(errors, &IO.puts/1)
...> :error
...> end
there are errors!
username is empty
password is empty
:error
iex> defmodule A do
...> import MyMacros
...> defdebug sum(a, b), do: a + b
...> end
{:module, A, <<...>>, ...}
iex> A.sum(19, 23)
[a: 8, b: 7]
15
use
defmodule MyModuleTemplate do
defmacro __using__(opts) do
import_opts = Keyword.get(opts, :import_opts, [])
quote do
import unquote(__MODULE__), unquote(import_opts)
require MyMacros
alias A.B.C
alias Arithmetic.Integers, as: IntOperations
end
end
def first({x, _}), do: x
def second({_, x}), do: x
end
use
defmodule UsingExample do
# becomes MyModuleTemplate.__using__([])
use MyModuleTemplate
def keys(map) when is_map(map) do
Enum.map(map, &first/1)
end
def values(map) when is_map(map) do
Enum.map(map, &second/1)
end
end
use
defmodule UsingExampleExpanded do
import MyModuleTemplate, []
require MyMacros
alias A.B.C
alias Arithmetic.Integers, as: IntOperations
def keys(map) when is_map(map) do
Enum.map(map, &first/1)
end
def values(map) when is_map(map) do
Enum.map(map, &second/1)
end
end
A protocol specifies an API that should be defined by its implementations.
defprotocol Area do
@doc "Calculates the area of a shape."
@spec area(any) :: number
def area(shape)
end
defmodule Rectangle do
defstruct [:width, :height]
defimpl Area do
def area(%Rectangle{width: width, height: height}) do
width * height
end
end
end
defmodule Circle do
defstruct [:radius]
defimpl Area do
def area(%Circle{radius: radius}) do
:math.pi() * radius * radius
end
end
end
defmodule Triangle do
defstruct [:a, :b, :c]
defimpl Area do
def area(%Triangle{a: a, b: b, c: c}) do
s = (a + b + c) / 2
:math.sqrt(s * (s - a) * (s - b) * (s - c))
end
end
end
defimpl Area, for: Map do
def area(%{__shape__: struct} = shape) do
Map.delete(shape, :__shape__)
|> then(&struct!(struct, &1))
|> Area.area()
end
end
defimpl Area, for: Tuple do
def area(tuple) do
struct = elem(tuple, 0)
struct.__info__(:struct)
|> Enum.map(& &1.field)
|> Enum.zip(Tuple.delete_at(tuple, 0) |> Tuple.to_list())
|> then(&struct!(struct, &1))
|> Area.area()
end
end
geometry.ex
iex> Area.area(%Rectangle{width: 5, height: 3})
15
iex> Area.area(%Circle{radius: 1})
3.141592653589793
iex> Area.area(%Triangle{a: 3, b: 4, c: 5})
6.0
iex> Area.area(%{__shape__: Rectangle, width: 1, height: 7})
7
iex> Area.area({Triangle, 3, 4, 5})
6.0
Poison
, librería JSONdefmodule User do
defstruct [:id, :username, :email, :password]
defimpl Poison.Encoder do
# encodes ignoring id and password values
def encode(%User{} = user, options) do
Map.from_struct(user)
|> Map.drop([:id, :password])
|> Poison.Encoder.encode(options)
end
end
end
defprotocol Indexable do
@spec indices(any) :: [any]
def indices(object)
end
defimpl Indexable, for: Tuple do
def indices(tuple) when is_tuple(tuple) do
max_index = tuple_size(tuple) - 1
0..max_index |> Enum.to_list()
end
end
defimpl Indexable, for: List do
def indices(list) when is_list(list) do
indices(list, 0)
end
defp indices([], _), do: []
defp indices([_ | t], i), do: [i | indices(t, i + 1)]
end
defimpl Indexable, for: Map do
def indices(map) when is_map(map) do
Map.keys(map)
end
end
defimpl Indexable, for: Any do
defmacro __deriving__(module, struct, _options) do
fields = Map.from_struct(struct) |> Map.keys()
quote do
defimpl Indexable, for: unquote(module) do
@fields unquote(fields)
def indices(_), do: @fields
end
end
end
def indices(_), do: []
end
defmodule Point do
@derive [Indexable]
defstruct [:x, :y]
end
indexable.ex
iex> Indexable.indices({:a, :b, :c, :d})
[0, 1, 2, 3]
iex> Indexable.indices([1, 8, 27, 64, 125])
[0, 1, 2, 3, 4]
iex> Indexable.indices(%{x: 1, y: 0, z: 0})
[:x, :y, :z]
iex> Indexable.indices(%Point{x: 0, y: 1})
[:x, :y]
El repositorio de Elixir se compone de varias aplicaciones y módulos BEAM, escritos en Erlang y en Elixir, los cuales siempre están disponibles en una instalación del lenguaje.
Compilador y librería estándar, escritos en Erlang y Elixir, respectivamente.
Muchas funciones de la librería estándar hacen uso de BIFs (built-in functions)
de otros módulos estándar de Erlang/OTP. El compilador utiliza yecc
, generador
de parsers estándar de Erlang/OTP.
Framework de pruebas unitarias que nos permite estructurar, definir y ejecutar casos de prueba utilizando un DSL, implementado usando macros. Algunas funcionalidades que implementa el framework son:
use
)
Shell interactivo de Elixir, análogo al módulo shell
de Erlang. Incluye el
módulo IEx.Helpers
que incluye funciones de utilidad y es importado
automáticamente al iniciar el shell. También incluye el módulo IEx.Pry
que
permite detener la ejecución de un programa para depurarlo, iniciando un shell
interactivo en el contexto en que se detiene el programa; puede invocarse con el
macro Kernel.dbg
.
Aplicación para logging implementada internamente usando el módulo logger
de
Erlang, con algunas adiciones, como formatos y mensajes distintos a los
incluidos en Erlang/OTP.
Herramienta de compilación para el manejo de proyectos mediante CLI. Nos permite
realizar tareas de creación, compilación, configuración, ejecución, testing y
manejo de dependencias en proyectos Elixir. Es extensible, por lo que librerías
externas también pueden definir y documentar tareas usando la API de Mix, y que
se pueden invocar y consultar usando la línea de comandos de Mix. La
configuración de proyectos, incluyendo sus dependencias, se define en un módulo
Elixir en el archivo mix.exs
.
Proyecto open source publicado de Bancolombia que permite generar una estructura de proyecto Elixir que sigue los lineamientos de arquitectura limpia de esta organización. Está basado en un proyecto similar de Bancolombia para el lenguaje de programación Java, que es distribuido como un plugin Gradle.
Define un conjunto de tareas Mix que generan módulos Elixir para diferentes componentes de la arquitectura hexagonal, que son: modelos, casos de uso, adaptadores y entry points.
$ mix archive.install hex elixir_structure_manager
$ mix ca.new.structure my_ca_app
$ mix help --search ca.
mix ca.apply.config # Applies project configuration
mix ca.new.da # Creates a new driven adapter
mix ca.new.ep # Creates a new entry point
mix ca.new.model # Creates a new model with empty properties
mix ca.new.structure # Creates a new clean architecture application.
mix ca.new.usecase # Creates a new usecase
$ mix help ca.new.structure
...
Creates a new driven adapter for the clean architecture project mix ca.new.da
--type driven_adapter_name
Type param options:
• generic
• redis
• asynceventbus
...