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.
12340b1010 #binary0xcafe #hexadecimal0o765 #octal1_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
string = "hełło" # hełłobyte_size string # 7String.length string # 5
Pueden representarse como lista de caracteres con ' '
str = 'wombat' # 'wombat'is_list str # trueEnum.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""
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"
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).
{1, 2}{:ok, 42, "next"}
[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
states = %{ "AL" => "Alabama", "WI" => "Wisconsin" }states["AL"] # "Alabama"
<<1 , 2 >> # ej leer cabecereas ficheros
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..
Igualdad y desigualdad estricta.
1.0 === 1 # false1 === 1 # true1 !== 1 # false1 !== 1.0 # true
Igualdad y desigualdad por valor.
1.0 == 1 # true1 != 1.0 # false
Mayor, igual o menor.
1 > 2 # false1 <= 2 # true
a || b
a and b
a && b
not a
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
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:
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"
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!"endunless true do"Never and never"end
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 endend
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
se podrían ejecutar así:
$ elixir times.exs $ iex times.exs iex> c "times.exs"
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)
Por ej , Factorial , vamos a definir el contrato recursivo :
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)? :
¿ Y en Factorial.of(0)? :
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" endend
defmodule Math do alias Math.List, as: Listend
require es usado para importar macros.
iex> Integer.is_odd(3) # (CompileError) iex:1: you must require Integer...iex> require Integer #niliex> 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]endimport Integer, only: :macrosimport Integer, only: :functions
Por ej, este código es dificil de leer porque lo lees de abajo a arriba para entenderlo.
people = DB.find_customersorders = 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
Queremos sumar todos los elementos de una lista.
Vamos a diseñar el contrato recursivo:
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.
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) endendMyList.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.
list = [{:a, 1}, {:b, 2}] #[a: 1, b: 2]list == [a: 1, b: 2] #truelist[:a] #1list ++ [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
%{ four: 4, five: 5, six: 6 }
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 endend# Sum a HashDicthd = [ one: 1, two: 2, three: 3 ] |> Enum.into HashDict.newIO.puts Sum.values(hd) # => 6# Sum a Mapmap = %{ four: 4, five: 5, six: 6 }IO.puts Sum.values(map) # => 15
defmodule Subscriber do defstruct name: "", paid: false, over_18: trueends1 = %Subscriber{ name: "Mary", paid: true } # %Subscriber{name: "Mary", over_18: true, paid: true}#Acceder a sus propiedadess1.name # "Mary"%Subscriber{name: a_name} = s1 # %Subscriber{name: "Mary", over_18: true, paid: true}a_name #"Mary"#Actualizarlos2 = %Subscriber{ s1 | name: "Marie"} # %Subscriber{name: "Marie", over_18: true, paid: true}
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 endend
a1 = %Attendee{name: "Dave", over_18: true}# %Attendee{name: "Dave", over_18: true, paid: false}Attendee.may_attend_after_party(a1) #false
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 #trueiex> 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]>
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: falseenddefimpl Blank, for: List do def blank?([]), do: true def blank?(_), do: falseend
iex> Blank.blank?(0) # falseiex> Blank.blank?([]) #trueiex> Blank.blank?([1, 2, 3]) #falseiex> Blank.blank?("hello")# (Protocol.UndefinedError) protocol Blank not implemented for "hello"
es un tipo para los protocolos
defmodule User do defstruct name: "john", age: 27enddefimpl Blank, for: User do def blank?(_), do: falseend
iex> Blank.blank?(%User{}) # false
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) # 13Enum.filter(list, &(&1 > 2)) # [3, 4, 5]Enum.take(list, 3) # [1, 2, 3]Enum.reduce(1..100, &(&1+&2)) # 5050Enum.reduce(1..100, 10, &(&1+&2)) # 5060
[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]
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
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>
defmodule Spawn1 do def greet do receive do {sender, msg} -> send sender, { :ok, "Hello, #{msg}" } end endendpid = spawn(Spawn1, :greet, [])send pid, {self, "World!"}receive do {:ok, message} -> IO.puts messageend
iex> c("spawn1.ex") "Hello, World!" :ok
defmodule Spawn2 do def greet do receive do {sender, msg} -> send sender, { :ok, "Hello, #{msg}" } greet # MIRA JAVA UHAAAAA!!! end endendpid = spawn(Spawn2, :greet, [])send pid, {self, "World!"} receive do {:ok, message} -> IO.puts messageendsend 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]
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
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}
iex> Node.self # :nonode@nohost
se pueden nombrar con el comando --name
$ iex --name test@slides.localiex(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.localiex(node_two@test.local)> Node.list # []iex(node_two@test.local)> Node.connect :"node_one@test.local" # trueiex(node_two@test.local)> Node.list # [:"node_one@test.local"]* terminal 1iex(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
* 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 : : :
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.
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 |