+ - 0:00:00
Notes for current slide
Notes for next slide

ELIXIR

|>

1 / 59

Lenguaje dinámico y funcional de proposito general que corre sobre la Erlang VM, conocida por su funcionamiento de baja latencia y alta disponibilidad en sistemas distribuidos.

2 / 59

Características principales

  • Inmutable Data
  • Pattern Matching
  • Concurrencia - Erlang VM (Actors)
  • Sintaxis (a la Ruby)
  • Protocols (a la Clojure)
  • Macros
  • OTP (herramintas y conjunto de librerias)
    • Genservers - comportamiento cliente-servidor
    • Supervisors - tolerancia fallos y recuperación
    • Application - módulo para aplicaciones
  • REPL (read-eval-print-loop)
  • Hot Code Swapping
3 / 59

Types

4 / 59

Value Types

  • Integers
    1234
    0b1010 #binary
    0xcafe #hexadecimal
    0o765 #octal
    1_000_000
  • Floats

    Se separa con el punto decimal y puede tener cerca de 16 dígitos de precisión.

    1.0 0.2456 0.314159e1 314159.0e-5
5 / 59
  • Atoms

    Constantes que representan un nombre.

    :jhon :is_binary? :var@2 :===
  • Range

    start..end. Para iterar sobre sus valores deben ser Integers.

    1..10
  • Regular Expressions

    Se representan así ~r{regexp} o ~r{regexp}opts.

    Regex.scan ~r{[aeiou]}, “caterpillar” #[[“a”][“e”][“i”][“a”]]
    Regex.replace ~r{[aeiou]}, "caterpillar", "*" # "c*t*rp*ll*r"
6 / 59
  • Strings

    • Pueden tener carácteres en UTF-8
      string = "hełło" # hełło
      byte_size string # 7
      String.length string # 5
    • Pueden representarse como lista de caracteres con ' '

      str = 'wombat' # 'wombat'
      is_list str # true
      Enum.reverse str # 'tabmow'
    • Se puede obtener el codepoint con ?

      ?a # 97
      #322
    • Interpolación de código utilizando la sintaxis #{...}.

      "Hello, #{String.capitalize "world"}!" #"Hello, world""
7 / 59

Strings II

  • Sigils

    Mecanismo para trabajar con representaciones de texto.

    Empiezan con ~ seguido de una letra mayúscula o minúscula , un delimitador y unas opciones. Los delimitadores pueden ser <...>, {...}, [...], (...), |...|, /.../, "...", y '...'.

    ~w[the cat sat on the mat] # ["the", "cat", "sat", "on", "the", "mat"]
  • Soportan Heredocs

    La heredoc notation """ arregla que no se queden espacios o saltos entre strings.Es muy útil para la documentación de modulos y funciones.

    IO.puts "start"
    IO.write """
    my
    string
    """
    IO.puts "end"
8 / 59

Sistem Types

  • PIDS

    Referencias a procesos locales o remotos, self nos dará el actual pid.

    self #PID<0.53.0>
  • Ports

    Referencias a recursos, normalmente externos a la aplicación (leer o escribir).

    Port.list() #[#Port<0.0>, #Port<0.239>, #Port<0.667>]
  • References

    Referencias globales únicas (make_ref).

9 / 59

Collection Types

  • Tuples
    {1, 2}
    {:ok, 42, "next"}
  • Lists
    [1, 2 ,3 ]
    [name: "David" , age: 32] # [{:name , "David"}, {:age , 32}]
    [1, 2] ++ [3 , 4] # [1, 2 , 3 , 4] ;
    [1, 2, 3, 4] -- [3 , 4] # [1, 2 ] ;
    1 in [1, 3] # true
  • Maps
    states = %{ "AL" => "Alabama", "WI" => "Wisconsin" }
    states["AL"] # "Alabama"
  • Binaries
    <<1 , 2 >> # ej leer cabecereas ficheros
10 / 59

Boolean

  • true, false y nil

  • nil es tratado como false.

  • Tienen alias

    :true # true
    :false # false
    :nil # nil

    Muy útil para operaciones en tuples, maps , pattern matching..

11 / 59

Operators

12 / 59

Comparision

  • Igualdad y desigualdad estricta.

    1.0 === 1 # false
    1 === 1 # true
    1 !== 1 # false
    1 !== 1.0 # true
  • Igualdad y desigualdad por valor.

    1.0 == 1 # true
    1 != 1.0 # false
  • Mayor, igual o menor.

    1 > 2 # false
    1 <= 2 # true
13 / 59

Boolean operators

  • a or b
  • a || b

  • a and b

  • a && b

  • not a

  • !a
14 / 59

Annonymous Functions

15 / 59

Se crean con el keyword fn :

fn
parameter-list -> body
parameter-list -> body
end
sum = fn (a, b) -> a + b end #Function<12.90072148/2 in :erl_eval.expr/5>

Para ejecutarlas es necesario un '.' aunque vayan sin parámetros.

sum.(1,2) #3
greet = fn -> IO.puts "Hello" end
greet.() # "Hello"

Se pueden omitir los paréntesis en la declaración.

f1 = fn a, b -> a * b end
f1.(3, 2) # 6
16 / 59

Pattern Matching

17 / 59

En Elixir a = 1 no es un asignación es como una aserción, como un contrato.

Tiene éxito si puede encontrar una manera de hacer que el lado izquierdo sea igual al lado derecho.

a = 1 # 1
1 = a # 1
2 = a # ** (MatchError) no match of right hand side value: 1
# En listas:
list = [1, 2 , 3] # [1 , 2, 3]
[a , b , c ] = list # a → 1 ; b → 2 ; c → 3
# Ignorando valores con *_* :
[1 , _ , _ ] = [1 , “cat , “dog”] # [1, “cat”, “dog”]
# head y tail :
[ h | t ] = [1, 2, 3] # h 1 ; t [2, 3]
18 / 59

En funciones anónimas :

handle_open = fn
{:ok, file} -> "Read data : #{IO.read(file, :line)}"
{ _, error} -> "Error : #{:file.format_error(error)}"
end

A handle_open le asignamos dos contratos:

  • una tupla de {:ok, fichero} => leer fichero
  • una tupla de { _, error} => print error formateado

Usamos File.open(string ruta fichero) para abrir ficheros:

good_file = File.open("/home/exist.txt") # {:ok, #PID<0.132.0>}
no_exist = File.open("no exist") # {:error, :enoent}
handle_open.(good_file) #"Read data: you are awsome!!
handle_open.(no_exist) #"Error: no such file or directory"
#Interoperabilidad con Erlang
:file.format_error(:enoent) -> "no such file or directory"
19 / 59

case, cond and if

20 / 59
  • case
    case {1, 2, 3} do
    {4, 5, 6} -> "Noop"
    {1, x, 3} -> "Yes!"
    _ -> "Entra si fallan los demas"
    end
  • cond

    cond do
    2 + 2 == 5 -> "Noop"
    2 * 2 == 3 -> "Noop"
    1 + 1 == 2 -> "Yes!"
    end
  • if , unless

    if nil do
    "Noop"
    else
    "Yes!"
    end
    unless true do
    "Never and never"
    end
21 / 59

Modules and named functions

22 / 59

Los módulos sirven para estructurar el código y poder reutilizarlos.

Primero , creamos un fichero , por ej: times.ex

defmodule Times do
def double(n) do
n*2
end
end

Este puede ser compilado :

  • usando elixirc

    $ elixirc times.ex

    Esto generará un fichero llamado Elixir.Times.beam con el bytecode.

  • usando iex directamente

    $ iex times.ex

Entonces dentro del iex ya tendríamos el módulo:

iex> Times.double 4 # 8
23 / 59

Scripting Mode

  • con extension .exs
  • no son compilados
  • no generean fichero de bytecode
  • se podrían ejecutar así:

    $ elixir times.exs
    $ iex times.exs
    iex> c "times.exs"
24 / 59

Project structure

  • ebin

    Contiene el bytecode compilado

  • lib

    código elixir, normalente ficheros .ex

  • test

    los tests del proyecto, normalmente .exs

(Como herramienta de construcción, compilación y tests se usa mix)

25 / 59

Named Functions y PatternMatching

Por ej , Factorial , vamos a definir el contrato recursivo :

  • factorial(0) → 1
  • factorial(n) → n * factorial(n - 1)
defmodule Factorial do
def of(0), do: 1
def of(n), do: n * of(n-1)
end

¿Cómo funciona llamando a Factorial.of(2)? :

  1. lo intenta con la fn de argumento 0 => FALLA
  2. lo intenta con la fn de argumento n => PASA

¿ Y en Factorial.of(0)? :

  1. lo intenta con la fn de argumento 0 => PASA

EL ORDEN DE LAS FUNCIONES IMPORTA MUCHO!!

26 / 59

Guard Clauses

Distinguir que función es invocada dependiento del tipo del parametro o del valor de este.

En el caso anterior de Factorial , si pasamos un número negativo , haría un loop infinito:

defmodule Factorial do
def of(0), do: 1
def of(n) when n > 0 do
n * of(n-1)
end
end

Por tipo de parámetro:

defmodule Guard do
def what_is(x) when is_number(x) do
IO.puts "#{x} is a number"
end
def what_is(x) when is_list(x) do
IO.puts "#{inspect(x)} is a list"
end
end
27 / 59

Alias, require and import

  • alias sirve para acortar nombres a cualquier modulo.
    defmodule Math do
    alias Math.List, as: List
    end
  • require es usado para importar macros.

    iex> Integer.is_odd(3) # (CompileError) iex:1: you must require Integer...
    iex> require Integer #nil
    iex> Integer.is_odd(3) #true
  • import para acceder fácilmente a las funciones o macros de otros módulos.

    defmodule MyList do
    import Integer, only: [is_odd: 1]
    end
    import Integer, only: :macros
    import Integer, only: :functions
28 / 59

The pipe operator |>

  • Coge el resultado de la operación de la izquierda y lo inserta como 1º parámetro en la operación de la derecha.
  • Filosofía Unix (y copiado de F#)

Por ej, este código es dificil de leer porque lo lees de abajo a arriba para entenderlo.

people = DB.find_customers
orders = Orders.for_customers(people)
tax = sales_tax(orders, 2013)
filing = prepare_filing(tax)

En cambio si usamos el pipe operator el código fluye en orden lógico:

filing = DB.find_customers
|> Orders.for_customers
|> sales_tax(2013)
|> prepare_filing
29 / 59

List and Recursion

30 / 59
  • Se suele usar el patrón [head | tail]

Queremos sumar todos los elementos de una lista.

Vamos a diseñar el contrato recursivo:

  • sum([]) → 0
  • sum([ head |tail ]) → «total» + sum(tail)
defmodule MyList do
def sum([], total), do: total
def sum([ head | tail ], total), do: sum(tail, head+total)
end
MyList.sum([], 0) #0
MyList.sum([11,12,13,14,15], 0) #65

Tener que pasar el parámetro total es un poco tedioso e innecesario.

Vamos a ver como encapsularlo.

31 / 59
  • Sabemos que el total inicialmente siempre es 0

  • declaremos metodos privados que acepten el total como argumento

  • y uno público que llame a los privados con total = 0

defmodule MyList do
def sum(list), do: _sum(list, 0)
# private methods
defp _sum([], total), do: total
defp _sum([ head | tail ], total), do: _sum(tail, head+total)
end
MyList.sum([]) #0
MyList.sum([11,12,13,14,15]) #65

¿Y si generalizamos esta función?

Vamos a hacer nuestra propia higher order function, ya que esto es un reduce.

32 / 59

Higher Order Function - nuestro Reduce

  • coge una o más funciones como args
  • devuelve otra función

Args para nuestro reduce : (collection, init_value, function)

Vamos a pensar el diseño recursivo :

  • reduce([], value, _) → value

  • reduce([ head |tail ], value, fn) → reduce(tail, fn.(head, value), fn)

defmodule MyList do
def reduce([], value, _) do
value
end
def reduce([head | tail], value, fn) do
reduce(tail, fn.(head, value), fn)
end
end
MyList.reduce([1,2,3,4,5], 0, &(&1 + &2)) # 15 # fn anónimas con **&()**
MyList.reduce([1,2,3,4,5], 1, &(&1 * &2)) # 120

El reduce ya existe en Elixir, en el modulo Enum.

33 / 59

Dictionaries: Keywords, Maps, HashDicts, Structs and Sets.

34 / 59

Keywords List

  • Las claves deben ser atoms.
  • Las claves están ordenadas, segun el desarrollador.
  • Las claves se pueden repetir.
list = [{:a, 1}, {:b, 2}] #[a: 1, b: 2]
list == [a: 1, b: 2] #true
list[:a] #1
list ++ [c: 3] #[a: 1, b: 2, c: 3]
[a: 0] ++ list #[a: 0, a: 1, b: 2]
new_list = [a: 0] ++ list #[a: 0, a: 1, b: 2]
new_list[:a] #0
35 / 59

Maps y HashDicts

  • Maps
    %{ four: 4, five: 5, six: 6 }
  • HashDict : clave - valor
    iex> hashdict = Enum.into [name: "Manu", likes: "Programming"], HashDict.new
    # #HashDict<[name: "Dave", where: "Dallas", likes: "Programming"]>

Para operar con Maps y HashDicts se utiliza el módulo Dict.

defmodule Sum do
def values(dict) do
dict |> Dict.values |> Enum.sum
end
end
# Sum a HashDict
hd = [ one: 1, two: 2, three: 3 ] |> Enum.into HashDict.new
IO.puts Sum.values(hd) # => 6
# Sum a Map
map = %{ four: 4, five: 5, six: 6 }
IO.puts Sum.values(map) # => 15
36 / 59

Structs I

  • extension de maps
  • puden asociarse a los protocols para polimorfismo
  • puede tener parámetros por defecto
defmodule Subscriber do
defstruct name: "", paid: false, over_18: true
end
s1 = %Subscriber{ name: "Mary", paid: true } # %Subscriber{name: "Mary", over_18: true, paid: true}
#Acceder a sus propiedades
s1.name # "Mary"
%Subscriber{name: a_name} = s1 # %Subscriber{name: "Mary", over_18: true, paid: true}
a_name #"Mary"
#Actualizarlo
s2 = %Subscriber{ s1 | name: "Marie"} # %Subscriber{name: "Marie", over_18: true, paid: true}
37 / 59

Structs II

Los structs se definen dentro de los modulos porque normalmente se les añede funcionalidad:

defmodule Attendee do
defstruct name: "", paid: false, over_18: true
def may_attend_after_party(attendee = %Attendee{}) do
attendee.paid && attendee.over_18
end
end
a1 = %Attendee{name: "Dave", over_18: true}
# %Attendee{name: "Dave", over_18: true, paid: false}
Attendee.may_attend_after_party(a1) #false
38 / 59

Sets

Hay una sola implementación para los sets y es con HashSet

iex> set1 = Enum.into 1..5, HashSet.new #HashSet<[1, 2, 3, 4, 5]>
iex> Set.member? set1, 3 #true
iex> set2 = Enum.into 3..8, HashSet.new #HashSet<[3, 4, 5, 6, 7, 8]>
iex> Set.union set1, set2 #HashSet<[7, 6, 4, 1, 8, 2, 3, 5]>
iex> Set.difference set1, set2 #HashSet<[1, 2]>
iex> Set.difference set2, set1 #HashSet<[6, 7, 8]>
iex> Set.intersection set1, set2 #HashSet<[3, 4, 5]>
39 / 59

Protocols

40 / 59
  • copiada de los protocols de Clojure

  • polimorfismo

  • primero se declara la definición

    defprotocol Blank do
    @doc "Returns true if data is considered blank/empty"
    def blank?(data)
    end
  • y luego se implementa para los tipos que necesitemos

    defimpl Blank, for: Integer do
    def blank?(_), do: false
    end
    defimpl Blank, for: List do
    def blank?([]), do: true
    def blank?(_), do: false
    end
    iex> Blank.blank?(0) # false
    iex> Blank.blank?([]) #true
    iex> Blank.blank?([1, 2, 3]) #false
    iex> Blank.blank?("hello")
    # (Protocol.UndefinedError) protocol Blank not implemented for "hello"
41 / 59

Con Structs

  • es un tipo para los protocolos

    defmodule User do
    defstruct name: "john", age: 27
    end
    defimpl Blank, for: User do
    def blank?(_), do: false
    end
    iex> Blank.blank?(%User{}) # false
42 / 59

Enum y Streams

43 / 59

Enum

  • Es el módulo más usado
  • manipula colección : filter, map, reduce , etc
  • consume toda la colección

    list = Enum.to_list 1..5 # [1, 2, 3, 4, 5]
    Enum.map(list, &(&1 * 10)) # [10, 20, 30, 40, 50]
    Enum.at(10..20, 3) # 13
    Enum.filter(list, &(&1 > 2)) # [3, 4, 5]
    Enum.take(list, 3) # [1, 2, 3]
    Enum.reduce(1..100, &(&1+&2)) # 5050
    Enum.reduce(1..100, 10, &(&1+&2)) # 5060
44 / 59

Streams

  • NO consumen toda la colección - lazy
  • son para componer Enumerators - composables
  • NO crean listas intermedias
[1,2,3,4]
|> Stream.map(&(&1*&1))
|> Stream.map(&(&1+1))
|> Stream.filter(fn x -> rem(x,2) == 1 end)
|> Enum.to_list
# [5, 17]
45 / 59

Concurrencia

46 / 59
  • sistema basado en actores - gracias a Erlang VM

  • los procesos se pasan mensajes entre ellos

  • son independientes, no comparten nada entre ellos

  • desacoplamiento

  • procesos pertenecientes a Erlang VM y no al sistema

  • bajo consumo de memoria - muchos, muchos procesos a la vez

  • tolerancia a fallos

  • escalabilidad

47 / 59

Un simple proceso

defmodule SpawnBasic do
def greet , do: IO.puts "Hello"
end

Es esto , nada mas que simple código.

iex> SpawnBasic.greet Hello
:ok

Corrámoslo en un proceso separado con

spawn(<module>, <function>, <args>, <opts>(optional))

iex> spawn(SpawnBasic, :greet, [])
# Hello
#PID<0.42.0>
48 / 59

Mensaje entre procesos

  • un proceso que escucha (receive)
  • otro que manda un mensaje (send) y escucha la respuesta (receive)
defmodule Spawn1 do
def greet do
receive do
{sender, msg} ->
send sender, { :ok, "Hello, #{msg}" }
end
end
end
pid = spawn(Spawn1, :greet, [])
send pid, {self, "World!"}
receive do
{:ok, message} ->
IO.puts message
end
iex> c("spawn1.ex")
"Hello, World!"
:ok
49 / 59

Mensajes entre procesos

defmodule Spawn2 do
def greet do
receive do
{sender, msg} ->
send sender, { :ok, "Hello, #{msg}" }
greet # MIRA JAVA UHAAAAA!!!
end
end
end
pid = spawn(Spawn2, :greet, [])
send pid, {self, "World!"}
receive do
{:ok, message} -> IO.puts message
end
send pid, {self, "Kermit!"}
receive do
{:ok, message} -> IO.puts message
after 500 -> IO.puts "The greeter has gone away"
end
iex> c("spawn2.ex")
Hello World!
Hello Kermit!
[Spawn2]
50 / 59

Linkar Procesos

Para compartir el comportamiento de dos procesos. Si uno termina de alguna forma , el otro muere. Con OTP Supervisors se podrían relanzar.

defmodule Link2 do
import :timer, only: [ sleep: 1 ]
# termina el proceso al medio seg.
def sad_function do
sleep 500
exit(:boom)
end
def run do
spawn_link(Link2, :sad_function, [])
receive do
msg ->
IO.puts "MESSAGE RECEIVED: #{inspect msg}"
after 1000 ->
IO.puts "Nothing happened as far as I am concerned"
end
end
end
Link.run # ** (EXIT from #PID<0.35.0>) :boom
51 / 59

Monitorizar Procesos

Notifica la terminación de un proceso.

defmodule Monitor1 do
import :timer, only: [ sleep: 1 ]
# termina el proceso al medio seg.
def sad_function do
sleep 500
exit(:boom)
end
def run do
res = spawn_monitor(Monitor1, :sad_method, [])
IO.puts inspect res
receive do
msg ->
IO.puts "MESSAGE RECEIVED: #{inspect msg}"
after 1000 ->
IO.puts "Nothing happened as far as I am concerned"
end
end
end
Monitor1.run
#{PID<0.37.0>,#Reference<0.0.0.53>}
#MESSAGE RECEIVED: {:DOWN,#Reference<0.0.0.53>,:process,#PID<0.37.0>,:boom}
52 / 59

Nodes: servicios distribuidos

53 / 59
  • son simples intancias de la Erlang VM
    iex> Node.self # :nonode@nohost
  • se pueden nombrar con el comando --name

    $ iex --name test@slides.local
    iex(test@slides.local)1> Node.self # :"test@slides.local"
  • se puden conectar entre ellos

    * terminal 1
    $ iex --name node_one@test.local
    * terminal 2
    $ iex --name node_two@test.local
    iex(node_two@test.local)> Node.list # []
    iex(node_two@test.local)> Node.connect :"node_one@test.local" # true
    iex(node_two@test.local)> Node.list # [:"node_one@test.local"]
    * terminal 1
    iex(node_one@test.local)> Node.list # [:"node_two@test.local"]
54 / 59

Ejemplo, de un servidor que manda una notificacion cada 2 s.

defmodule Ticker do
@interval 2000 # 2 seconds
@name :ticker
# Lanzamos el proceso y lo registramos globalmente
def start do
pid = spawn(__MODULE__, :generator, [[]])
:global.register_name(@name, pid)
end
# registramos al cliente con su pid asociándolo al servidor
def register(client_pid) do
send :global.whereis_name(@name), { :register, client_pid }
end
# escuchamos 2 eventos continuamente:
# 1 - muestra los clientes conectados
# 2 - manda un mensaje a todos los clientes
def generator(clients) do
receive do
{ :register, pid } ->
IO.puts "registering #{inspect pid}"
generator([pid|clients])
after
@interval ->
IO.puts "tick"
Enum.each clients, fn(client) ->
send client, { :tick }
end
generator(clients)
end
end
end
55 / 59

Cliente, para recibir la notif. crearemos un cliente registrado con el servidor. Suponemos que está en el mismo fichero que el servidor Ticker

defmodule Client do
# lanzamos proceso y lo asociamos con el servidor
def start do
pid = spawn(__MODULE__, :receiver, [])
Ticker.register(pid)
end
# evento que escucha continuamente y cuando recibimos { :tick}
def receiver do
receive do
{ :tick } ->
IO.puts "tock in client"
receiver
end
end
end
56 / 59
* terminal 1
$ iex --name one@test.local
iex(one@test.local)> c("ticker.ex")
* terminal 2
$ iex --name two@test.local
* terminal 1
iex(one@test.local)> Node.connect :"two@test.local"
true
iex(one@test.local)> Ticker.start
:yes
tick
tick
iex(one@test.local)> Client.start
registering #PID<0.59.0>
{:register,#PID<0.59.0>}
tick
tock in client
tick
tock in client
: : :
* terminal 2
$ iex --name two@test.local
iex(two@test.local)> c("ticker.ex")
iex(two@test.local)> Client.start
{:register,#PID<0.53.0>}
tock in client
tock in client
tock in client
: : :
57 / 59

Muchas Gracias!

@manute

58 / 59

Lenguaje dinámico y funcional de proposito general que corre sobre la Erlang VM, conocida por su funcionamiento de baja latencia y alta disponibilidad en sistemas distribuidos.

2 / 59
Paused

Help

Keyboard shortcuts

, , Pg Up, k Go to previous slide
, , Pg Dn, Space, j Go to next slide
Home Go to first slide
End Go to last slide
Number + Return Go to specific slide
b / m / f Toggle blackout / mirrored / fullscreen mode
c Clone slideshow
p Toggle presenter mode
t Restart the presentation timer
?, h Toggle this help
Esc Back to slideshow