Taller de Elixir #9 – Recursividad

Bucles a través de la recursividad

Debido a la inmutabilidad, los bucles en Elixir (como en cualquier lenguaje de programación funcional) se escriben de manera diferente a los lenguajes imperativos. Por ejemplo, en un lenguaje imperativo como C, uno escribiría:

for(i = 0; i < sizeof(array); i++) {
  array[i] = array[i] * 2;
}

En el ejemplo anterior, estamos mutando tanto la matriz como la variable i. La mutación no es posible en Elixir. En cambio, los lenguajes funcionales dependen de la recursividad: una función se llama de forma recursiva hasta que se alcanza una condición que detiene la acción recursiva. No hay datos mutados en este proceso. Considere el siguiente ejemplo que imprime una cadena un número arbitrario de veces:

defmodule Recursion do
  def print_multiple_times(msg, n) when n <= 1 do
    IO.puts msg
  end

  def print_multiple_times(msg, n) do
    IO.puts msg
    print_multiple_times(msg, n - 1)
  end
end

Recursion.print_multiple_times("Hello!", 3)
# Hello!
# Hello!
# Hello!

Similar al case, una función puede tener muchas cláusulas. Una cláusula particular se ejecuta cuando los argumentos pasados a la función coinciden con los patrones de argumento de la cláusula y su guard se evalúa como true.

Cuando print_multiple_times/2 se llama inicialmente en el ejemplo anterior, el argumento n es igual a 3.

La primera cláusula tiene un guard que dice “use esta definición si y solo si n es menor o igual que 1”. Como este no es el caso, Elixir pasa a la siguiente definición de la cláusula.

La segunda definición coincide con el patrón y no tiene guard, por lo que se ejecutará. Primero imprime nuestro mensaje y luego se llama a sí mismo pasando n – 1 como segundo argumento.

Nuestro smg se imprime y print_multiple_times/2 se llama de nuevo, esta vez con el segundo argumento establecido en 1. Debido a que n ahora se establece en 1, el guard en nuestra primera definición de print_multiple_times/2 se evalúa como true, y ejecutamos esta definición particular. El mensaje se imprime y no queda nada para ejecutar.

Definimos print_multiple_times/2 para que, sin importar qué número se pase como segundo argumento, desencadene nuestra primera definición (conocida como caso base) o desencadene nuestra segunda definición, lo que garantizará que estemos exactamente un paso más cerca del caso base.

Reducir y mapear algoritmos

Veamos ahora cómo podemos usar el poder de la recursividad para sumar una lista de números:

defmodule Math do
  def sum_list([head | tail], accumulator) do
    sum_list(tail, head + accumulator)
  end

  def sum_list([], accumulator) do
    accumulator
  end
end

IO.puts Math.sum_list([1, 2, 3], 0) #=> 6

Invocamos sum_list con la lista [1, 2, 3] y el valor inicial 0 como argumentos. Intentaremos cada cláusula hasta encontrar una que coincida de acuerdo con las reglas de coincidencia de patrones. En este caso, la lista [1, 2, 3] coincide con [head | tail] que une head a 1 y tail a [2, 3]. Y accumulator se establece en 0.

Luego, agregamos el encabezado de la lista al head + accumulator y llamamos a sum_list nuevamente, recursivamente, pasando el final de la lista como su primer argumento. La cola volverá a coincidir [head | tail] hasta que la lista esté vacía, como se ve a continuación:

sum_list [1, 2, 3], 0
sum_list [2, 3], 1
sum_list [3], 3
sum_list [], 6

Cuando la lista está vacía, coincidirá con la cláusula final que devuelve el resultado final de 6.

El proceso de tomar una lista y reducirla a un valor se conoce como algoritmo de reducción y es fundamental para la programación funcional.

¿Qué sucede si en cambio queremos duplicar todos los valores de nuestra lista?

defmodule Math do
  def double_each([head | tail]) do
    [head * 2 | double_each(tail)]
  end

  def double_each([]) do
    []
  end
end
$ iex math.exs
iex> Math.double_each([1, 2, 3]) #=> [2, 4, 6]

Aquí hemos utilizado la recursividad para recorrer una lista, duplicar cada elemento y devolver una nueva lista. El proceso de tomar una lista y mapearla se conoce como algoritmo de mapa.

La recursividad y la optimización de la cola son una parte importante de Elixir y se usan comúnmente para crear bucles. Sin embargo, cuando programe en Elixir, rara vez usará la recursividad como se describe arriba para manipular las listas.

El módulo Enum, que veremos en el próximo capítulo, ya ofrece muchas comodidades para trabajar con listas. Por ejemplo, los ejemplos anteriores podrían escribirse como:

iex> Enum.reduce([1, 2, 3], 0, fn(x, acc) -> x + acc end)
6
iex> Enum.map([1, 2, 3], fn(x) -> x * 2 end)
[2, 4, 6]

O usando la sintaxis de captura:

iex> Enum.reduce([1, 2, 3], 0, &+/2)
6
iex> Enum.map([1, 2, 3], &(&1 * 2))
[2, 4, 6]

Taller de Elixir #8 – Módulos y funciones

En Elixir agrupamos varias funciones en módulos. Ya hemos usado muchos módulos diferentes en los capítulos anteriores, como el módulo de cadena:

iex> String.length("hello")
5

Para crear nuestros propios módulos en Elixir, utilizamos la macro defmodule. Usamos la macro def para definir funciones en ese módulo:

iex> defmodule Math do
...>   def sum(a, b) do
...>     a + b
...>   end
...> end

iex> Math.sum(1, 2)
3

Compilación

Ya es hora de que aprendamos cómo compilar el código de Elixir y también cómo ejecutar los scripts de Elixir.

La mayoría de las veces es conveniente escribir módulos en archivos para que puedan compilarse y reutilizarse. Supongamos que tenemos un archivo llamado math.ex con los siguientes contenidos:

defmodule Math do
  def sum(a, b) do
    a + b
  end
end

Este archivo se puede compilar usando elixirc:

$ elixirc math.ex

Esto generará un archivo llamado Elixir.Math.beam que contiene el código de bytes para el módulo definido. Si iniciamos iex nuevamente, la definición de nuestro módulo estará disponible (siempre que iex se inicie en el mismo directorio en el que se encuentra el archivo de código de bytes):

iex> Math.sum(1, 2)
3

Los proyectos de elixir generalmente se organizan en tres directorios:

  • ebin – contiene el bytecode compilado
  • lib: contiene código de elixir (generalmente archivos .ex)
  • prueba: contiene pruebas (generalmente archivos .exs)

Cuando trabaje en proyectos reales, la herramienta de compilación llamada mix será responsable de compilar y configurar las rutas adecuadas para usted. Con fines de aprendizaje, Elixir también es compatible con un modo de script que es más flexible y no genera ningún artefacto compilado.

Modo Scripted

Además de la extensión de archivo Elixir .ex, Elixir también admite archivos .exs para secuencias de comandos. Elixir trata ambos archivos exactamente de la misma manera, la única diferencia está en la intención. Los archivos .ex están destinados a ser compilados mientras que los archivos .exs se utilizan para la creación de scripts. Cuando se ejecutan, ambas extensiones compilan y cargan sus módulos en la memoria, aunque solo los archivos .ex escriben su código de bytes en el disco en el formato de archivos .beam.

Por ejemplo, podemos crear un archivo llamado math.exs:

defmodule Math do
  def sum(a, b) do
    a + b
  end
end

IO.puts Math.sum(1, 2)

Y ejecutarlo como:

$ elixir math.exs

El archivo se compilará en la memoria y se ejecutará, imprimiendo “3” como resultado. No se creará ningún archivo de código de bytes.

Funciones nombradas

Dentro de un módulo, podemos definir funciones con def/2 y funciones privadas con defp/2. Una función definida con def/2 puede invocarse desde otros módulos, mientras que una función privada solo puede invocarse localmente.

defmodule Math do
  def sum(a, b) do
    do_sum(a, b)
  end

  defp do_sum(a, b) do
    a + b
  end
end

IO.puts Math.sum(1, 2)    #=> 3
IO.puts Math.do_sum(1, 2) #=> ** (UndefinedFunctionError)

Las declaraciones de funciones también admiten guards y múltiples cláusulas. Si una función tiene varias cláusulas, Elixir probará cada cláusula hasta que encuentre una que coincida. Aquí tenemos una implementación de una función que verifica si el número dado es cero o no:

defmodule Math do
  def zero?(0) do
    true
  end

  def zero?(x) when is_integer(x) do
    false
  end
endSiiiii......imágenes cuanto menos, impactantes

IO.puts Math.zero?(0)         #=> true
IO.puts Math.zero?(1)         #=> false
IO.puts Math.zero?([1, 2, 3]) #=> ** (FunctionClauseError)
IO.puts Math.zero?(0.0)       #=> ** (FunctionClauseError)

¿El signo de interrogación final en cero? significa que esta función devuelve un valor booleano. Ver convenciones de nomenclatura.

Dar un argumento que no coincide con ninguna de las cláusulas genera un error.

Similar a construcciones como if, las funciones con nombre admiten la sintaxis de bloque do: y do/end, como aprendimos, do/end es una sintaxis conveniente para el formato de lista de palabras clave. Por ejemplo, podemos editar math.exs para que se vea así:

defmodule Math do
  def zero?(0), do: true
  def zero?(x) when is_integer(x), do: false
end

Y proporcionará el mismo comportamiento. Puede usar do: para líneas simples, pero siempre use do/end para funciones que abarcan varias líneas.

Captura de funciones

Hemos estado usando el nombre/aridad de notación para referirnos a funciones. Sucede que esta notación se puede utilizar para recuperar una función con nombre como un tipo de función. Inicie iex, ejecutando el archivo math.exs definido anteriormente:

$ iex math.exs
iex> Math.zero?(0)
true
iex> fun = &Math.zero?/1
&Math.zero?/1
iex> is_function(fun)
true
iex> fun.(0)
true

Recuerde que Elixir hace una distinción entre funciones anónimas y funciones con nombre, donde las primeras deben invocarse con un punto (.) Entre el nombre de la variable y los paréntesis. El operador de captura cierra esta brecha al permitir que las funciones con nombre se asignen a variables y se pasen como argumentos de la misma manera que asignamos, invocamos y pasamos funciones anónimas.

Las funciones locales o importadas, como is_function/1, se pueden capturar sin el módulo:

iex> &is_function/1
&:erlang.is_function/1
iex> (&is_function/1).(fun)
true

hay que tener en cuenta que la sintaxis de captura también se puede utilizar como acceso directo para crear funciones:

iex> fun = &(&1 + 1)
<6.71889879/1 in :erl_eval.expr/5>
iex> fun.(1)
2

iex> fun2 = &"Good #{&1}"
<6.127694169/1 in :erl_eval.expr/5>
iex)> fun2.("morning")
"Good morning"

El &1 representa el primer argumento pasado a la función. &(&1 + 1) arriba es exactamente lo mismo que fn x -> x + 1 final. La sintaxis anterior es útil para definiciones breves de funciones.

Si desea capturar una función desde un módulo, puede hacer &Module.function():

iex> fun = &List.flatten(&1, &2)
&List.flatten/2
iex> fun.([1, [[2], 3]], [4, 5])
[1, 2, 3, 4, 5]

&List.flatten(&1, &2) es lo mismo que escribir fn(list, tail) -> List.flatten (list, tail) final, que en este caso es equivalente a &List.flatten/2. Puede leer más sobre el operador de captura y en la documentación Kernel.SpecialForms.

Argumentos por defecto

Las funciones con nombre en Elixir también admiten argumentos predeterminados:

defmodule Concat do
  def join(a, b, sep \\ " ") do
    a <> sep <> b
  end
end

IO.puts Concat.join("Hello", "world")      #=> Hello world
IO.puts Concat.join("Hello", "world", "_") #=> Hello_world

Se permite que cualquier expresión sirva como valor predeterminado, pero no se evaluará durante la definición de la función. Cada vez que se invoca la función y se debe utilizar cualquiera de sus valores predeterminados, se evaluará la expresión para ese valor predeterminado:

defmodule DefaultTest do
  def dowork(x \\ "hello") do
    x
  end
end
iex> DefaultTest.dowork
"hello"
iex> DefaultTest.dowork 123
123
iex> DefaultTest.dowork
"hello"

Si una función con valores predeterminados tiene varias cláusulas, es necesario crear un encabezado de función (sin un cuerpo real) para declarar valores predeterminados:

defmodule Concat do
  def join(a, b \\ nil, sep \\ " ")

  def join(a, b, _sep) when is_nil(b) do
    a
  end

  def join(a, b, sep) do
    a <> sep <> b
  end
end

IO.puts Concat.join("Hello", "world")      #=> Hello world
IO.puts Concat.join("Hello", "world", "_") #=> Hello_world
IO.puts Concat.join("Hello")               #=> Hello

El guión bajo inicial en _sep significa que la variable se ignorará en esta función. Ver convenciones de nomenclatura.

Cuando se usan valores predeterminados, se debe tener cuidado para evitar la superposición de definiciones de funciones. Considere el siguiente ejemplo:

defmodule Concat do
  def join(a, b) do
    IO.puts "***First join"
    a <> b
  end

  def join(a, b, sep \\ " ") do
    IO.puts "***Second join"
    a <> sep <> b
  end
end

Si guardamos el código anterior en un archivo llamado “concat.ex” y lo compilamos, Elixir emitirá la siguiente advertencia:

warning: this clause cannot match because a previous clause at line 2 always matches

El compilador nos dice que invocar la función de combinación con dos argumentos siempre elegirá la primera definición de combinación, mientras que la segunda solo se invocará cuando se pasen tres argumentos:

$ iex concat.ex
iex> Concat.join "Hello", "world"
***First join
"Helloworld"
iex> Concat.join "Hello", "world", "_"
***Second join
"Hello_world"

Taller de Elixir #7 – Lista de palabras claves y mapas

Aún no hemos visto ninguna estructura de datos asociativos, es decir, estructuras de datos que puedan asociar un cierto valor (o valores múltiples) a una clave. Diferentes idiomas llaman a estos diferentes nombres como diccionarios, hashes, matrices asociativas, etc.

Tenemos dos estructuras de datos asociativas principales: listas de palabras clave y mapas.

Lista de palabras claves (Keyword lists)

En Elixir, cuando tenemos una lista de tuplas y el primer elemento de la tupla (es decir, la clave) es un átomo, lo llamamos una lista de palabras clave:

iex> list = [{:a, 1}, {:b, 2}]
[a: 1, b: 2]
iex> list == [a: 1, b: 2]
true

Como puede ver arriba, Elixir admite una sintaxis especial para definir tales listas: [clave: valor]. Debajo se asigna a la misma lista de tuplas que la anterior. Como las listas de palabras clave son listas, podemos usar todas las operaciones disponibles para las listas. Por ejemplo, podemos usar ++ para agregar nuevos valores a una lista de palabras clave:

iex> list ++ [c: 3]
[a: 1, b: 2, c: 3]
iex> [a: 0] ++ list
[a: 0, a: 1, b: 2]

Ten en cuenta que los valores agregados al frente son los obtenidos en la búsqueda:

iex> new_list = [a: 0] ++ list
[a: 0, a: 1, b: 2]
iex> new_list[:a]
0

Las listas de palabras clave son importantes porque tienen tres características especiales:

  • Las llaves deben ser átomos.
  • Las claves están ordenadas, según lo especificado por el desarrollador.
  • Las llaves se pueden dar más de una vez.

Por ejemplo, la biblioteca Ecto hace uso de estas características para proporcionar un DSL elegante para escribir consultas en la base de datos:

query = from w in Weather,
      where: w.prcp > 0,
      where: w.temp < 20,
     select: w

Estas características son las que llevaron a las listas de palabras clave a ser el mecanismo predeterminado para pasar opciones a funciones en Elixir. La macro if/2, mencionamos que la siguiente sintaxis es compatible:

iex> if false, do: :this, else: :that
:that

¡Los pares do: y else: forman una lista de palabras clave! De hecho, la llamada anterior es equivalente a:

iex> if(false, [do: :this, else: :that])
:that

Y tambien es lo mismo que:

iex> if(false, [{:do, :this}, {:else, :that}])
:that

En general, cuando la lista de palabras clave es el último argumento de una función, los corchetes son opcionales.

Aunque podemos hacer coincidir patrones en listas de palabras clave, rara vez se hace en la práctica, ya que la coincidencia de patrones en listas requiere la cantidad de elementos y su orden para que coincidan:

iex> [a: a] = [a: 1]
[a: 1]
iex> a
1
iex> [a: a] = [a: 1, b: 2]
** (MatchError) no match of right hand side value: [a: 1, b: 2]
iex> [b: b, a: a] = [a: 1, b: 2]
** (MatchError) no match of right hand side value: [a: 1, b: 2]

Para manipular las listas de palabras clave, Elixir proporciona el módulo de palabras clave. Sin embargo, recuerde que las listas de palabras clave son simplemente listas y, como tales, proporcionan las mismas características de rendimiento lineal que las listas. Cuanto más larga sea la lista, más tiempo llevará encontrar una clave, contar la cantidad de elementos, etc. Por esta razón, las listas de palabras clave se utilizan en Elixir principalmente para pasar valores opcionales. Si necesita almacenar muchos artículos o garantizar asociados de una clave con un valor máximo, debe usar mapas en su lugar.

Mapas

Recomiendo la lectura de este articulo. Cuando termines este tema. Ya que se explica la sintaxis especial que trae Elixir.

Siempre que necesite un almacén de valores clave, los mapas son la estructura de datos “go to” en Elixir. Se crea un mapa utilizando la sintaxis %{}:

iex> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> map[:a]
1
iex> map[2]
:b
iex> map[:c]
nil

En comparación con las listas de palabras clave, hay dos diferencias:

  • Los mapas permiten cualquier valor como clave.
  • Las claves del mapas no siguen ningún pedido.

A diferencia de las listas de palabras clave, los mapas son muy útiles con la coincidencia de patrones. Cuando se usa un mapa en un patrón, siempre coincidirá en un subconjunto del valor dado:

iex> %{} = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> %{:a => a} = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> a
1
iex> %{:c => c} = %{:a => 1, 2 => :b}
** (MatchError) no match of right hand side value: %{2 => :b, :a => 1}

Como se muestra arriba, un mapa coincide siempre y cuando las claves del patrón existan en el mapa dado. Por lo tanto, un mapa vacío coincide con todos los mapas.

Las variables se pueden usar al acceder, hacer coincidir y agregar claves de mapa:

iex> n = 1
1
iex> map = %{n => :one}
%{1 => :one}
iex> map[n]
:one
iex> %{^n => :one} = %{1 => :one, 2 => :two, 3 => :three}
%{1 => :one, 2 => :two, 3 => :three}

El módulo Map proporciona una API muy similar al módulo Keyword con funciones convenientes para manipular mapas:

iex> Map.get(%{:a => 1, 2 => :b}, :a)
1
iex> Map.put(%{:a => 1, 2 => :b}, :c, 3)
%{2 => :b, :a => 1, :c => 3}
iex> Map.to_list(%{:a => 1, 2 => :b})
[{2, :b}, {:a, 1}]

Los mapas tienen la siguiente sintaxis para actualizar el valor de una clave:

iex> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}

iex> %{map | 2 => "two"}
%{2 => "two", :a => 1}
iex> %{map | :c => 3}
** (KeyError) key :c not found in: %{2 => :b, :a => 1}

La sintaxis anterior requiere que exista la clave dada. No se puede usar para agregar nuevas claves. Por ejemplo, al usarlo con la tecla: c falló porque no hay: c en el mapa.

Cuando todas las claves en un mapa son átomos, puede usar la sintaxis de palabras clave por conveniencia:

iex> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}

iex> map.a
1
iex> map.c
** (KeyError) key :c not found in: %{2 => :b, :a => 1}

Los desarrolladores de Elixir generalmente prefieren usar la sintaxis map.field y la coincidencia de patrones en lugar de las funciones en el módulo Map cuando trabajan con mapas porque conducen a un estilo de programación asertivo. Esta publicación de blog proporciona información y ejemplos sobre cómo obtener un software más conciso y rápido escribiendo código asertivo en Elixir.

Estructuras de datos anidados

A menudo tendremos mapas dentro de los mapas, o incluso listas de palabras clave dentro de los mapas, y así sucesivamente. Elixir proporciona comodidades para manipular estructuras de datos anidados a través de put_in/2, update_in/2 y otras macros que brindan las mismas comodidades que encontraría en lenguajes imperativos mientras mantiene las propiedades inmutables del lenguaje.

Imagina que tienes la siguiente estructura:

iex> users = [
  john: %{name: "John", age: 27, languages: ["Erlang", "Ruby", "Elixir"]},
  mary: %{name: "Mary", age: 29, languages: ["Elixir", "F#", "Clojure"]}
]
[john: %{age: 27, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
 mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}]

Tenemos una lista de palabras clave de usuarios donde cada valor es un mapa que contiene el nombre, la edad y una lista de lenguajes de programación que le gusta a cada usuario. Si quisiéramos acceder a la edad de John, podríamos escribir:

iex> users[:john].age
27

Sucede que también podemos usar esta misma sintaxis para actualizar el valor:

users = put_in users[:john].age, 31
[john: %{age: 31, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
 mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}]

La macro update_in/2 es similar pero nos permite pasar una función que controla cómo cambia el valor. Por ejemplo, eliminemos “Clojure” de la lista de idiomas de Mary:

iex> users = update_in users[:mary].languages, fn languages -> List.delete(languages, "Clojure") end
[john: %{age: 31, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
 mary: %{age: 29, languages: ["Elixir", "F#"], name: "Mary"}]

Hay más para aprender sobre put_in/2 y update_in/2, incluido get_and_update_in/2 que nos permite extraer un valor y actualizar la estructura de datos de una vez. También hay put_in/3, update_in/3 y get_and_update_in/3 que permiten el acceso dinámico a la estructura de datos.

Esto concluye nuestra introducción a las estructuras de datos asociativas en Elixir. Descubrirás que, dadas las listas de palabras clave y los mapas, siempre tendrá la herramienta adecuada para abordar los problemas que requieren estructuras de datos asociativas en Elixir.

Taller de Elixir #6 – Case, cond e if

Las estructuras de flujo de control de flujo son case, cond e if.

Case

Case nos permite comparar un valor con muchos patrones hasta que encontremos uno coincidente:

iex> case {1, 2, 3} do
...>   {4, 5, 6} ->
...>     "This clause won't match"
...>   {1, x, 3} ->
...>     "This clause will match and bind x to 2 in this clause"
...>   _ ->
...>     "This clause would match any value"
...> end
"This clause will match and bind x to 2 in this clause"

Si desea comparar patrones con una variable existente, debe usar el operador ^:

iex> x = 1
1
iex> case 10 do
...>   ^x -> "Won't match"
...>   _ -> "Will match"
...> end
"Will match"

Las cláusulas también permiten que se especifiquen condiciones adicionales a través de guards:

iex> case {1, 2, 3} do
...>   {1, x, 3} when x > 0 ->
...>     "Will match"
...>   _ ->
...>     "Would match, if guard condition were not satisfied"
...> end
"Will match"

La primera cláusula anterior solo coincidirá cuando x sea positivo.

Tenga en cuenta que los errores en los guardias no tienen fugas, sino que simplemente hacen que el guardia falle:

iex> hd(1)
** (ArgumentError) argument error
iex> case 1 do
...>   x when hd(x) -> "Won't match"
...>   x -> "Got #{x}"
...> end
"Got 1"

Si ninguna de las cláusulas coincide, se genera un error:

iex> case :ok do
...>   :error -> "Won't match"
...> end
** (CaseClauseError) no case clause matching: :ok

Consulte la documentación completa de los guardias para obtener más información sobre los guardias, cómo se usan y qué expresiones están permitidas en ellos.

Tenga en cuenta que las funciones anónimas también pueden tener múltiples cláusulas y guardias:

iex> f = fn
...>   x, y when x > 0 -> x + y
...>   x, y -> x * y
...> end
<12.71889879/2 in :erl_eval.expr/5>
iex> f.(1, 3)
4
iex> f.(-1, 3)
-3

El número de argumentos en cada cláusula de función anónima debe ser el mismo, de lo contrario se genera un error.

iex> f2 = fn
...>   x, y when x > 0 -> x + y
...>   x, y, z -> x * y + z
...> end
** (CompileError) iex:1: cannot mix clauses with different arities in anonymous functions

Cond

Case es útil cuando necesita hacer coincidir valores diferentes. Sin embargo, en muchas circunstancias, queremos verificar diferentes condiciones y encontrar la primera que no se evalúa como nula o falsa. En tales casos, uno puede usar cond:

iex> cond do
...>   2 + 2 == 5 ->
...>     "This will not be true"
...>   2 * 2 == 3 ->
...>     "Nor this"
...>   1 + 1 == 2 ->
...>     "But this will"
...> end
"But this will"

Esto es equivalente a las cláusulas else if en muchos idiomas imperativos (aunque aquí se usan con menos frecuencia).

Si todas las condiciones devuelven nulo o falso, se genera un error (CondClauseError). Por esta razón, puede ser necesario agregar una condición final, igual a verdadera, que siempre coincidirá con:

iex> cond do
...>   2 + 2 == 5 ->
...>     "This is never true"
...>   2 * 2 == 3 ->
...>     "Nor this"
...>   true ->
...>     "This is always true (equivalent to else)"
...> end
"This is always true (equivalent to else)"

Finalmente, note cond considera que cualquier valor además de nil y false es verdadero:

iex> cond do
...>   hd([1, 2, 3]) ->
...>     "1 is considered as true"
...> end
"1 is considered as true"

if y unless

Además de case y cond, también tenemos las macros if/2 y unless/2, que son útiles cuando necesita verificar una sola condición:

iex> if true do
...>   "This works!"
...> end
"This works!"
iex> unless true do
...>   "This will never be seen"
...> end
nil

Si la condición if/2 devuelve false o nil, el cuerpo dado entre do/end no se ejecuta y en su lugar devuelve nil. Lo contrario sucede con unless/2.

También admiten bloques más:

iex> if nil do
...>   "This won't be seen"
...> else
...>   "This will"
...> end
"This will"

Bloques do/end

Como hemos visto en las cuatro estructuras de control: case, cond, if y unless. En todas estaban envueltas en bloques do/end. Sucede que también podríamos escribir si es así:

iex> if true, do: 1 + 2
3

Los bloques do/end son una conveniencia sintáctica construida sobre las palabras clave. Es por eso que los bloques do/end no requieren una coma entre el argumento anterior y el bloque. Son útiles exactamente porque eliminan la verbosidad al escribir bloques de código. Estos son equivalentes:

iex> if true do
...>   a = 1 + 2
...>   a + 10
...> end
13
iex> if true, do: (
...>   a = 1 + 2
...>   a + 10
...> )
13

Las listas de palabras clave juegan un papel importante en el lenguaje y son bastante comunes en muchas funciones y macros.

Taller de Elixir #5 – Pattern Matching (La coincidencia de patrones)

Vamos a ver cómo el operador = en Elixir es en realidad un operador de coincidencia y cómo usarlo para establecer patrones dentro de las estructuras de datos. También, aprenderemos sobre el operador de pin ^ usado para acceder a valores previamente vinculados.

El operador match

Hemos usado el operador = un par de veces para asignar variables en Elixir:

iex> x = 1
1
iex> x
1

Pero en Elixir, el operador = en realidad se llama operador de coincidencia. Veamos por qué:

iex> x = 1
1
iex> 1 = x
1
iex> 2 = x
** (MatchError) no match of right hand side value: 1

Observe que 1 = x es una expresión válida y coincide porque los lados izquierdo y derecho son iguales a 1. Cuando los lados no coinciden, se genera un MatchError.

Una variable solo se puede asignar en el lado izquierdo del =.

iex> 1 = unknown
** (CompileError) iex:1: undefined function unknown/0

Como no hay una variable desconocida previamente definida, Elixir asumió que estaba intentando llamar a una función llamada unknown/0, pero esa función no existe.

Pattern Matching

El operador de coincidencia no solo se utiliza para comparar valores simples, sino que también es útil para desestructurar tipos de datos más complejos. Por ejemplo, podemos combinar patrones en tuplas:

iex> {a, b, c} = {:hello, "world", 42}
{:hello, "world", 42}
iex> a
:hello
iex> b
"world"

Se producirá un error de coincidencia de patrón si los lados no pueden coincidir, por ejemplo, si las tuplas tienen tamaños diferentes:

iex> {a, b, c} = {:hello, "world"}
** (MatchError) no match of right hand side value: {:hello, "world"}

Y también al comparar diferentes tipos:

iex> {a, b, c} = [:hello, "world", 42]
** (MatchError) no match of right hand side value: [:hello, "world", 42]

Más interesante aún, podemos coincidir en valores específicos. El siguiente ejemplo afirma que el lado izquierdo solo coincidirá con el lado derecho cuando el lado derecho es una tupla que comienza con el átomo :ok:

iex> {:ok, result} = {:ok, 13}
{:ok, 13}
iex> result
13

iex> {:ok, result} = {:error, :oops}
** (MatchError) no match of right hand side value: {:error, :oops}

Podemos emparejar patrones en listas:

iex> [a, b, c] = [1, 2, 3]
[1, 2, 3]
iex> a
1

Una lista también admite coincidencias en su propia cabeza y cola:

iex> [head | tail] = [1, 2, 3]
[1, 2, 3]
iex> head
1
iex> tail
[2, 3]

Similar a las funciones hd/1 y tl/1, no podemos hacer coincidir una lista vacía con un patrón de cabeza y cola:

iex> [head | tail] = []
** (MatchError) no match of right hand side value: []

La [cabeza | el formato tail] no solo se usa en la coincidencia de patrones, sino también para anteponer elementos a una lista:

iex> list = [1, 2, 3]
[1, 2, 3]
iex> [0 | list]
[0, 1, 2, 3]

La coincidencia de patrones permite a los desarrolladores desestructurar fácilmente los tipos de datos, como tuplas y listas.

El operador pin

Utilice el operador de pin ^ cuando desee comparar patrones con el valor de una variable existente en lugar de volver a vincular la variable:

iex> x = 1
1
iex> ^x = 2
** (MatchError) no match of right hand side value: 2
iex> {y, ^x} = {2, 1}
{2, 1}
iex> y
2
iex> {y, ^x} = {2, 2}
** (MatchError) no match of right hand side value: {2, 2}

Como hemos asignado el valor de 1 a la variable x, este último ejemplo también podría haberse escrito como:

iex> {y, 1} = {2, 2}
** (MatchError) no match of right hand side value: {2, 2}

Si una variable se menciona más de una vez en un patrón, todas las referencias deben unirse al mismo patrón:

iex> {x, x} = {1, 1}
{1, 1}
iex> {x, x} = {1, 2}
** (MatchError) no match of right hand side value: {1, 2}

En algunos casos, no le importa un valor particular en un patrón. Es una práctica común vincular esos valores al guión bajo, _. Por ejemplo, si solo nos importa el encabezado de la lista, podemos asignar la cola para subrayar:

iex> [head | _] = [1, 2, 3]
[1, 2, 3]
iex> head
1

La variable _ es especial porque nunca se puede leer. Si intenta leer de él da un error de compilación:

iex> _
** (CompileError) iex:1: invalid use of _. "_" represents a value to be ignored in a pattern and cannot be used in expressions

Aunque la coincidencia de patrones nos permite construir construcciones potentes, su uso es limitado. Por ejemplo, no puede realizar llamadas a funciones en el lado izquierdo de una coincidencia. El siguiente ejemplo no es válido:

iex> length([1, [2], 3]) = 3
** (CompileError) iex:1: cannot invoke remote function :erlang.length/1 inside match

Taller de Elixir #4 – Operadores Básicos

Hemos vistos que los operadores aritméticos +, -, *, / como, además de las funciones div/2 y rem/2 para la división entera y el resto.

También vimos ++ y — para manipular listas:

iex> [1, 2, 3] ++ [4, 5, 6]
[1, 2, 3, 4, 5, 6]
iex> [1, 2, 3] -- [2]
[1, 3]

La concatenación de cadenas se realiza con <>:

iex> "foo" <> "bar"
"foobar"

Aun que también es muy común el uso de:

iex> [foo, bar] = ["foo", "bar"]
["foo", "bar"]
iex> "#{foo}#{bar}"
"foobar"

Hay tres operadores booleanos: or, and y not. Estos operadores son estrictos en el sentido de que esperan algo que se evalúe como booleano (verdadero o falso) como primer argumento:

iex> true and true
true
iex> false or is_atom(:example)
true

Proporcionar un valor no booleano generará una excepción:

iex> 1 and true
** (BadBooleanError) expected a boolean on left-side of "and", got: 1

Or y and son operadores de cortocircuito. Solo ejecutan el lado derecho si el lado izquierdo no es suficiente para determinar el resultado:

iex> false and raise("This error will never be raised")
false
iex> true or raise("This error will never be raised")
true

Además de estos operadores booleanos, Elixir también proporciona ||, && y! que aceptan argumentos de cualquier tipo. Para estos operadores, todos los valores, excepto falso y nulo, se evaluarán como verdaderos:

# or
iex> 1 || true
1
iex> false || 11
11

# and
iex> nil && 13
nil
iex> true && 17
17

# !
iex> !true
false
iex> !1
false
iex> !nil
true

Como regla general, use y, o no y cuando esté esperando booleanos. Si alguno de los argumentos no es booleano, use &&, || y!

También podemos usar ==,! =, ===,! ==, <=,> =, como operadores de comparación:

iex> 1 == 1
true
iex> 1 != 2
true
iex> 1 < 2
true

La diferencia entre == y === es que este último es más estricto:

iex> 1 == 1.0
true
iex> 1 === 1.0
false

También podemos comparar dos tipos de datos diferentes:

iex> 1 < :atom
true

La razón por la que podemos comparar diferentes tipos de datos es el pragmatismo. Los algoritmos de clasificación no necesitan preocuparse por los diferentes tipos de datos para ordenar. El orden de clasificación general se define a continuación:

number < atom < reference < function < port < pid < tuple < map < list < bitstring

En realidad no necesita memorizar este pedido; es suficiente saber que existe este orden.

Taller de Elixir #3 – Tipos de datos

Los tipos básicos en elixir son enteros, flotantes, booleanos, átomos, cadenas, listas y tuplas.

iex> 1          # integer
iex> 0x1F       # integer
iex> 1.0        # float
iex> true       # boolean
iex> :atom      # atom / symbol
iex> "elixir"   # string
iex> [1, 2, 3]  # list
iex> {1, 2, 3}  # tuple

Aritmética básica

Las operaciones básicas son comunes:

iex> 1 + 2
3
iex> 2 - 1
1
iex> 5 * 5
25
iex> 10 / 2
5.0

Para que al dividir devuelva un entero en vez de un flotante se usa la función div/1 y para el modulo usamos rem/1.

iex> div(10, 2)
5
iex> div 10, 2
5
iex> rem 10, 3
1

Elixir le permite quitar los paréntesis al invocar funciones con nombre. Esta característica proporciona una sintaxis más limpia.

También admite anotaciones de acceso directo para ingresar números binarios, octales y hexadecimales:

iex> 0b1010
10
iex> 0o777
511
iex> 0x1F
31

Los números flotantes requieren un punto seguido de al menos un dígito y también admiten e para notación científica:

iex> 1.0
1.0
iex> 1.0e-10
1.0e-10

Las flotantes son de doble precisión de 64 bits.

La función “round” devuelve el número entero más cercano a un flotante y la función “trunc” para obtener la parte entera de un flotante.

iex> round(3.58)
4
iex> trunc(3.58)
3

Booleanos

Tenemos las palas claves true y false:

iex> true
true
iex> true == false
false

También tenemos la función “is_boolean”:

iex> is_boolean(true)
true
iex> is_boolean(1)
false

Atoms

Un átomo es una constante cuyo valor es su propio nombre. Algunos lenguajes llaman a estas símbolos. A menudo son útiles para enumerar valores distintos, como:

iex> :apple
:apple
iex> :orange
:orange
iex> :watermelon
:watermelon

Los átomos son iguales si sus nombres son iguales:

iex> :apple == :apple
true
iex> :apple == :orange
false

A menudo se utilizan para expresar el estado de una operación, mediante el uso de valores como :ok y :error.

Los booleanos verdadero y falso también son átomos:

iex> true == :true
true
iex> is_atom(false)
true
iex> is_boolean(:false)
true

Elixir te permite omitir el líder: para los átomos falso, verdadero y nulo.

Finalmente, Elixir tiene una construcción llamada alias que exploraremos más adelante. Los alias comienzan en mayúsculas y también son átomos:

iex> is_atom(Hello)
true

Strings

Las cadenas están delimitadas por comillas dobles, y están codificadas en UTF-8:

iex> "hellö #{:world}"
"hellö world"

Las cadenas pueden tener saltos de línea en ellas. Puedes presentarlos usando secuencias de escape:

ex> "hello
...> world"
"hello\nworld"
iex> "hello\nworld"
"hello\nworld"

Se puede imprimir una cadena usando la función IO.puts/1 desde el módulo IO:

iex> IO.puts "hello\nworld"
hello
world
:ok

Podemos ver que la función IO.puts/1 devuelve el átomo :ok después de imprimir.

Las cadenas están representadas internamente por secuencias contiguas de bytes conocidas como binarios:

iex> is_binary("hellö")
true

Para saber el número de bytes en una cadena:

iex> byte_size("hellö")
6

Observamos que el número de bytes en esa cadena es 6, a pesar de que tiene 5 caracteres. Esto se debe a que el carácter “ö” toma 2 bytes para ser representado en UTF-8. Podemos obtener la longitud real de la cadena, en función del número de caracteres, utilizando la función String.length/1:

iex> String.length("hellö")
5

El String module  contiene un montón de funciones para operar:

iex> String.upcase("hellö")
"HELLÖ"

Funciones anónimas

Estas funciones nos permiten almacenar y pasar código ejecutable como si fuera un entero o una cadena. Están delimitados por las palabras clave fn y end:

iex> add = fn a, b -> a + b end
<12.71889879/2 in :erl_eval.expr/5>
iex> add.(1, 2)
3
iex> is_function(add)
true

Las funciones anónimas también se identifican por la cantidad de argumentos que reciben. Podemos verificar si una función usando is_function / 2:

iex> is_function(add, 2)
true

iex> is_function(add, 1)
false

Definamos una nueva función anónima que usa la función agregar anónimo que hemos definido previamente:

iex> double = fn a -> add.(a, a) end
<6.71889879/1 in :erl_eval.expr/5>
iex> double.(2)
4

Una variable asignada dentro de una función no afecta a su entorno:

iex> x = 42
42
iex> (fn -> x = 0 end).()
0
iex> x
42

Listas

Se usan corchetes para especificar una lista de valores. Los valores pueden ser de cualquier tipo:

iex> [1, 2, true, 3]
[1, 2, true, 3]
iex> length [1, 2, 3]
3

Se pueden concatenar o restar dos listas utilizando los operadores ++ / 2 y – / 2 respectivamente:

iex> [1, 2, 3] ++ [4, 5, 6]
[1, 2, 3, 4, 5, 6]
iex> [1, true, 2, false, 3, true] -- [true, false]
[1, 2, 3, true]

Los operadores de listas nunca modifican la lista existente. Concatenar o eliminar elementos de una lista devuelve una nueva lista. Decimos que las estructuras de datos de Elixir son inmutables. Una ventaja de la inmutabilidad es que conduce a un código más claro. Puede pasar libremente los datos con la garantía de que nadie los mutará en la memoria, solo los transformará.

La cabeza es el primer elemento de una lista y la cola es el resto de la lista. Se pueden recuperar con las funciones hd/1 y tl/1. Asignemos una lista a una variable y recuperemos su cabeza y cola:

iex> list = [1, 2, 3]
iex> hd(list)
1
iex> tl(list)
[2, 3]

Obtener la cabeza o la cola de una lista vacía arroja un error:

iex> hd []
** (ArgumentError) argument error

A veces creará una lista y devolverá un valor entre comillas simples. Por ejemplo:

iex> [11, 12, 13]
'\v\f\r'
iex> [104, 101, 108, 108, 111]
'hello'

Cuando Elixir ve una lista de números ASCII imprimibles, Elixir lo imprimirá como una lista de caracteres (literalmente, una lista de caracteres). Las listas de caracteres son bastante comunes al interactuar con el código Erlang existente. Siempre que vea un valor en IEx y no esté seguro de cuál es, puede usar el i/1 para recuperar información al respecto:

iex> i 'hello'
Term
  'hello'
Data type
  List
Description
  ...
Raw representation
  [104, 101, 108, 108, 111]
Reference modules
  List
Implemented protocols
  ...

Las representaciones con comillas simples y dobles no son equivalentes en Elixir, ya que están representadas por diferentes tipos:

iex> 'hello' == "hello"
false

Las comillas simples son charlists, las comillas dobles son cadenas.

Tuplas

Elixir usa llaves para definir tuplas. Al igual que las listas, las tuplas pueden contener cualquier valor:

iex> {:ok, "hello"}
{:ok, "hello"}
iex> tuple_size {:ok, "hello"}
2

Las tuplas almacenan elementos contiguos en la memoria. Esto significa que acceder a un elemento de tupla por índice u obtener el tamaño de la tupla es una operación rápida. Los índices comienzan desde cero:

iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> elem(tuple, 1)
"hello"
iex> tuple_size(tuple)
2

También es posible poner un elemento en un índice particular en una tupla con put_elem/3:

iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> put_elem(tuple, 1, "world")
{:ok, "world"}
iex> tuple
{:ok, "hello"}

Observamos que put_elem/3 devolve una nueva tupla. La tupla original almacenada en la variable tupla no se modificó. Al igual que las listas, las tuplas también son inmutables. Cada operación en una tupla devuelve una nueva tupla, nunca cambia la dada.

¿Listas o tuplas?

Las listas se almacenan en la memoria como listas vinculadas, lo que significa que cada elemento de una lista mantiene su valor y apunta al siguiente elemento hasta llegar al final de la lista. Esto significa que acceder a la longitud de una lista es una operación lineal: debemos recorrer toda la lista para determinar su tamaño.

Del mismo modo, el rendimiento de la concatenación de listas depende de la longitud de la lista de la izquierda:

iex> list = [1, 2, 3]

iex> [0] ++ list
[0, 1, 2, 3]

iex> list ++ [4]
[1, 2, 3, 4]

Las tuplas, por otro lado, se almacenan contiguamente en la memoria. Esto significa que obtener el tamaño de tupla o acceder a un elemento por índice es rápido. Sin embargo, actualizar o agregar elementos a las tuplas es costoso porque requiere crear una nueva tupla en la memoria:

iex> tuple = {:a, :b, :c, :d}
iex> put_elem(tuple, 2, :e)
{:a, :b, :e, :d}

Hay que tener en cuenta que esto solo se aplica a la tupla en sí, no a su contenido. Por ejemplo, cuando actualiza una tupla, todas las entradas se comparten entre la tupla antigua y la nueva, excepto la entrada que ha sido reemplazada. En otras palabras, las tuplas y las listas en Elixir son capaces de compartir sus contenidos. Esto reduce la cantidad de asignación de memoria que necesita realizar el idioma y solo es posible gracias a la semántica inmutable del idioma.

Esas características de rendimiento dictan el uso de esas estructuras de datos. Un caso de uso muy común para las tuplas es usarlas para devolver información adicional de una función. Por ejemplo, File.read/1 es una función que se puede usar para leer el contenido del archivo. Devuelve una tupla:

iex> File.read("path/to/existing/file")
{:ok, "... contents ..."}
iex> File.read("path/to/unknown/file")
{:error, :enoent}

Si la ruta dada a File.read/1 existe, devuelve una tupla con el átomo :ok como el primer elemento y el contenido del archivo como el segundo. De lo contrario, devuelve una tupla con :error y la descripción del error.

La mayoría de las veces, Elixir te guiará para hacer lo correcto. Por ejemplo, hay una función elem/2 para acceder a un elemento de tupla, pero no hay un equivalente incorporado para las listas:

iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> elem(tuple, 1)
"hello"

Hasta ahora hemos utilizado 4 funciones de conteo: byte_size/1 (para el número de bytes en una cadena), tuple_size/1 (para el tamaño de la tupla), length/1 (para la longitud de la lista) y String.length/1 ( para el número de grafemas en una cadena). Usamos byte_size para obtener el número de bytes en una cadena, una operación barata. Recuperar el número de caracteres Unicode, por otro lado, usa String.length, y puede ser costoso ya que depende de un recorrido de toda la cadena.

Elixir también proporciona puertos, referencias y PID como tipos de datos (generalmente utilizados en la comunicación de procesos).

Taller de Elixir #2 – Primeros pasos

Documentación

Modo Interactivo

Cuando tenemos instalado Elixir. Tendremos 3 ejecutables: iex, elixir, mix.

El primero que vamos a ver es iex o iex.bat en windows. Que significa Interactive Elixir. En este shell, podremos escribir cualquier expresión de Elixir y obtener su resultado.

Un ejemplo sencillo sería.

Erlang/OTP 21.0 [64-bit] [smp:2:2] [...]

Interactive Elixir (1.10.2) - press Ctrl+C to exit
iex(1)> 40 + 2
42
iex(2)> "hello" <> " world"
"hello world"

Para salir de iex, presione Ctrl + C dos veces.

Ejecutar scripts

Creamos un fichero llamado “simple.exs” y le añadimos la siguiente linea.

IO.puts "Hello world from Elixir"

Salvamos el fichero y ejecutamos en la consola.

$ elixir simple.exs
Hello world from Elixir

Crear un proyecto

Creemos nuestro primer proyecto con “mix new” desde la línea de comandos. Pasaremos el nombre del proyecto como argumento (kv, en este caso), y le diremos a Mix que nuestro módulo principal debería ser el KV en mayúsculas, en lugar del predeterminado, que habría sido Kv:

$ mix new kv --module KV

Lo que nos devolverá algo parecido a esto:

* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/kv.ex
* creating test
* creating test/test_helper.exs
* creating test/kv_test.exs

El fichero responsable de la compilación del proyecto (Opciones, dependencias.. etc) es mix.exs y se genera automáticamente.

defmodule KV.MixProject do
  use Mix.Project

  def project do
    [
      app: :kv,
      version: "0.1.0",
      elixir: "~> 1.9",
      start_permanent: Mix.env == :prod,
      deps: deps()
    ]
  end

  # Run "mix help compile.app" to learn about applications
  def application do
    [
      extra_applications: [:logger]
    ]
  end

  # Run "mix help deps" to learn about dependencies
  defp deps do
    [
      # {:dep_from_hexpm, "~> 0.3.0"},
      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
    ]
  end
end

Ahora ya tenemos un proyecto. Se podría crea módulos y funciones. Para usarlo como librería de otra app. Pero queremos que se ejecute. Para ello modificamos el archivo mix.exs el apartado application. Tiene que ser algo parecido a esto.

  def application do
    [
      mod: {KV, []},
      extra_applications: [:logger]
    ]
  end

Eso hará que cuando ejecutemos la app nos llame a la función “start” del método “KV”. Nos vamos a fichero “lib/kv.ex” y cambiamos su contenido que es de ejemplo por:

defmodule KV do
  @moduledoc """
  Documentation for `KV`.
  """

  @doc """
    Run to start app.
  """
  def start(_type, _args) do
    IO.puts "starting"
    :timer.sleep(1000)
    Task.start(fn -> :timer.sleep(1000); IO.puts("done sleeping") end)
  end
end

Una vez realizado esto. Ejecutamos en consola:

$ mix run

Lo que nos devolverá el siguiente texto con un pequeño delay.

Compiling 1 file (.ex)
starting

Taller de Elixir #1 – Conceptos e instalación

Este taller estará basado en Elixir guides. Es básicamente una traducción del mismo con algunos cambios y apuntes.

¿Qué es?

Elixir es un lenguaje de programación funcionalconcurrente, de propósito general que se ejecuta sobre la máquina virtual de Erlang (BEAM). Elixir está escrito sobre Erlang y comparte las mismas abstracciones para desarrollar aplicaciones distribuidas y tolerantes a fallos. Elixir también proporciona un diseño extensible con herramientas productivas. Incluye soporte para metaprogramación en tiempo de compilación con macros y polimorfismo mediante protocolos.

Características

Instalación en Linux (Ubuntu)

wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb 
sudo dpkg -i erlang-solutions_1.0_all.deb
sudo apt-get update
sudo apt-get install esl-erlang
sudo apt-get install elixir

Instalación en Mac

brew install elixir (utilizando Homebrew)

Instalación en Windows

Descarga el instalador y sigue los pasos