class: center, middle # ELIXIR # |> --- 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. * [Elixir page](http://elixir-lang.org/) --- # 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 --- class: center, middle # Types --- # 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 ``` --- * **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" ``` --- * **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 *#{...}*. ```ruby "Hello, #{String.capitalize "world"}!" #"Hello, world"" ``` --- **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 '...'*. ```elixir ~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" ``` --- # 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*). --- # 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** ```elixir states = %{ "AL" => "Alabama", "WI" => "Wisconsin" } states["AL"] # "Alabama" ``` * **Binaries** ``` <<1 , 2 >> # ej leer cabecereas ficheros ``` --- # 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.. --- class: center, middle # Operators --- # 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 ``` --- # Boolean operators * **a or b** * **a || b** * **a and b** * **a && b** * **not a ** * **!a** --- class: center, middle # Annonymous Functions --- 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 ``` --- class: center, middle # Pattern Matching --- 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] ``` --- 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" ``` --- class: center, middle # case, cond and if --- * 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 ```elixir if nil do "Noop" else "Yes!" end unless true do "Never and never" end ``` --- class: center, middle # Modules and named functions --- 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* ```bash $ elixirc times.ex ``` Esto generará un fichero llamado *Elixir.Times.beam* con el bytecode. * usando *iex* directamente ```bash $ iex times.ex ``` Entonces dentro del iex ya tendríamos el módulo: ```bash iex> Times.double 4 # 8 ``` --- # Scripting Mode * con extension **.exs** * no son compilados * no generean fichero de bytecode * se podrían ejecutar así: ```bash $ elixir times.exs $ iex times.exs iex> c "times.exs" ``` --- # 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](http://elixir-lang.org/getting_started/mix_otp/1.html)**) --- # 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!! --- # 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 ``` --- # 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. ```bash 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 ``` --- # 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 ``` --- class: center, middle # List and Recursion --- * 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. --- * 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*. --- ### 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. ** --- class: center, middle # Dictionaries: Keywords, Maps, HashDicts, Structs and Sets. --- # 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 ``` --- # Maps y HashDicts * **Maps** ``` %{ four: 4, five: 5, six: 6 } ``` * **HashDict** : clave - valor ```bash 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**. ```ruby 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 ``` --- #Structs I * extension de maps * puden asociarse a los **protocols** para polimorfismo * puede tener parámetros por defecto ```ruby 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} ``` --- #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 ``` ```ruby a1 = %Attendee{name: "Dave", over_18: true} # %Attendee{name: "Dave", over_18: true, paid: false} Attendee.may_attend_after_party(a1) #false ``` --- #Sets Hay una sola implementación para los sets y es con **HashSet** ```ruby 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]> ``` --- class: center, middle # Protocols --- * copiada de los protocols de Clojure * polimorfismo * primero se declara la definición ```elixir 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 ```elixir 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" ``` --- # 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 ``` --- class: center, middle # Enum y Streams --- ##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 ``` --- ##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] ``` --- class: center, middle # Concurrencia --- * 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 --- # Un simple proceso ``` defmodule SpawnBasic do def greet , do: IO.puts "Hello" end ``` Es esto , nada mas que simple código. ```bash iex> SpawnBasic.greet Hello :ok ``` Corrámoslo en un proceso separado con **spawn(<*module*>, <*function*>, <*args*>, <*opts*>(optional))** ```bash iex> spawn(SpawnBasic, :greet, []) # Hello #PID<0.42.0> ``` --- # 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 ``` --- ### 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] ``` --- # 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 ``` --- # 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} ``` --- class: center, middle # Nodes: servicios distribuidos --- * son simples intancias de la Erlang VM ```bash iex> Node.self # :nonode@nohost ``` * se pueden nombrar con el comando *--name* ```bash $ iex --name test@slides.local iex(test@slides.local)1> Node.self # :"test@slides.local" ``` * se puden conectar entre ellos ```bash * 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"] ``` --- 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 ``` --- 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 ``` --- ```bash * 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 : : : ``` --- class: center, middle # Muchas Gracias! ## @manute --- # Fuentes * [Programming Elixir](https://pragprog.com/book/elixir/programming-elixir) * [Elixir - official page](http://elixir-lang.org/)