En la últimas dos décadas hemos podido observar un repunte en el uso de los lenguajes de programación funcional, quizá la predicción de dicho auge la podamos ver claramente en el famoso artículo escrito por Herb Sutter, un prominente experto en C++, en el año 2005, The Free Lunch Is Over, A Fundamental Turn Toward Concurrency in Software. En dicho artículo, Herb Sutter, indicaba algo que ya sabemos hoy, la velocidad de reloj del procesador estaba llegando a sus límites físicos. Las mayores consecuencias de esto eran dos:
Sin embargo, la programación concurrente es compleja si tienes que hacerla desde cero. Por eso en dicho artículo también se menciona que aquellos lenguajes de programación que ya implementan soporte a la programación concurrente, obtendrán una nueva vida. La expectativa será un incremento en la demanda por lenguajes y sistemas que optimicen estas nuevas áreas.
Como veremos más adelante en este artículo, la programación funcional, la inmutabilidad, el pase de mensajes entre procesos, y otros conceptos más, facilitan ese cambio de paradigma que Herb Sutter mencionaba en su famoso artículo.
Elixir, en particular, será el lenguaje funcional del cual hablaremos en detalle en este artículo.
El sitio oficial de Elixir, expone algunos casos de éxito por si quieres saber más acerca de cómo compañías a lo largo de diferentes industrias están usando el poder de Elixir y su ecosistema para crear y hacer crecer sus negocios.
Llegados a este punto, ya vas observando que al hablar de Elixir, también tenemos que hablar de Erlang, así que mejor hagamos una pausa para hablar sobre Erlang y su ecosistema.
En la actualidad existe todo un ecosistema que aprovecha la máquina virtual de Erlang o BEAM (Bogdan/Björn’s Erlang Abstract Machine). Tenemos a Erlang y Elixir por un lado. Hasta lenguajes como LFE que permite la sintaxis de Lisp, o Erlog que permite ejecutar Prolog, Lua, Gleam, entre otros.
Erlang es un lenguaje originalmente desarrollado dentro de Ericsson Computer Science Laboratory alrededor del año 1986, que posteriormente fue liberado open-source en 1998. Ericsson impuso algunos requisitos que vienen a cubrir gran parte de las necesidades en sistemas distribuidos hoy día. Los requisitos fueron:
Erlang llegó a cubrir todos los requisitos impuestos por Ericsson, lo cual ofrece la posibilidad de desarrollar sistemas distribuidos bastante robustos. Y ha sido probado en multiples sistemas en producción por décadas.
En la máquina Virtual de Erlang todo el código se ejecuta en pequeños procesos concurrentes.
Si no has programado en Erlang, seguramente has sido usuario indirecto o directo. Te comparto algunas estadísticas ofrecidas en la charla How Cisco is using Erlang for intent-based networking:
Sin embargo, en la opinión del creador de Elixir, José Valim, a pesar de las ventajas claras que ofrece Erlang, hace más de una década, sintió que habían partes que echaba de menos de otros lenguajes. Algunas de esas partes él solía usarlas en su día a día, como la meta-programación, extensibilidad del software brindando polimorfismo por medio de Protocols, y algunas herramientas que incrementan la productividad de los desarrolladores; algunas otras las descubrió al leer el libro Seven Languages in Seven Weeks. Así que decidió explorar la posibilidad de desarrollar un nuevo lenguaje encima de la máquina virtual de Erlang. Dicho lenguaje fue Elixir. En esencia, puedes ver a Elixir como otro lenguaje de programación funcional que expone todo lo que la máquina virtual de Erlang viene proveyendo por décadas de una manera diferente y a otros desarrolladores que no lo habrían experimentado de otra manera.
defmodule Grapheme do@moduledoc"""
Basic operations over Unicode graphemes
A grapheme is a minimally distinctive unit of writing
in the context of a particular writing system.
"""@doc"Returns a map with the grapheme frequencies"
def frequencies(string) do
string
|> String.graphemes()
|> Enum.frequencies()
endend
iex> Grapheme.frequencies("Elixir")
%{"E" => 1, "i" => 2, "l" => 1, "r" => 1, "x" => 1}
Todo el código en Elixir se ejecuta dentro de hilos de ejecución livianos, llamados procesos, que están aislados unos de otros e intercambian información vía mensajes.
current_process = self()
# Spawn an Elixir process (not an operating system one!)
spawn_link(fn ->
send(current_process, {:msg, "hello world"})
end)
# Block until the message is received
receive do
{:msg, contents} -> IO.puts(contents)
end
Dada la naturaleza liviana de estos procesos, no es extraño tener cientos de miles de procesos ejecutándose simultáneamente en la misma máquina. El aislamiento entre procesos permite que la recolección de elementos no utilizados o basura (garbage collected) sea independiente, lo que reduce las pausas en todo el sistema y utiliza los recursos de la máquina de la manera más eficiente posible (escalabilidad vertical)
Los procesos también pueden comunicarse con otros procesos que se ejecutan en diferentes máquinas en la misma red. Esto proporciona la base para la distribución, lo que permite a los desarrolladores coordinar el trabajo en varios nodos (escalabilidad horizontal).
La verdad inevitable sobre el software que se ejecuta en producción es que las cosas saldrán mal. Más aún cuando tenemos en cuenta la red, los sistemas de archivos y otros recursos de terceros.
Para hacer frente a las fallas, Elixir proporciona supervisores que describen cómo reiniciar partes de su sistema cuando las cosas salen mal, volviendo a un estado inicial conocido que se garantiza que funcionará.
children = [
TCP.Pool,
{TCP.Acceptor, port: 4040}
]
Supervisor.start_link(children, strategy: :one_for_one)
La combinación de tolerancia a fallas y programación basada en eventos a través del pase de mensajes hace que Elixir sea una excelente opción para la programación reactiva y arquitecturas robustas.
La programación funcional promueve un estilo de código que ayuda a los desarrolladores escribir código que es breve, conciso, y mantenible. Por ejemplo, la coincidencia de patrones permite a los desarrolladores descomponer fácilmente las estructuras de datos y acceder a su contenido.
%User{name: name, age: age} = User.get("John Doe")
name#=> "John Doe"
Cuando mezclamos guardas, la coincidencia de patrones nos permite especificar de manera elegante las condiciones específicas en las que un bloque de código se ejecutará.
defdrive(%User{age: age})when age >= 16do# Code that drives a carend
drive(User.get("John Doe"))
#=> Fails if the user is under 16
Elixir depende en gran medida en estas funcionalidades para garantizar que el software funcione en las condiciones esperadas. Y cuando eso no pasa, no te preocupes, los supervisores, mencionados en la sección previa, cuidaran nuestras espaldas.
Elixir ha sido diseñado para ser extensible, dejando que los desarrolladores naturalmente extiendan el lenguaje a dominios particulares con el propósito de incrementar su productividad.
Un ejemplo claro de esto es el framework de pruebas unitarias incluido en Elixir, llamado ExUnit.
defmoduleMathTestdouse ExUnit.Case, async:true
test "can add two numbers"do
assert 1 + 1 == 2endend
La opción async: true
permite que los tests corran concurrentemente, usando tantos CPU cores como sea posible. Mientras que assert
puede hacer introspección del código, proveyendo reportes en caso de fallas. Estas funcionalidades fueron construidas usando macros, haciendo posible agregar nuevos constructos tal como si fueran parte del lenguaje.
Elixir viene con un gran conjunto de herramientas para facilitar el desarrollo. Mix es una herramienta que te permite crear proyectos, administrar tareas, ejecutar pruebas y más.
$ mix new my_app
$ cd my_app
$ mix test
.
Finished in 0.04 seconds (0.04s on load, 0.00s on tests)
1 test, 0 failures
Mix también puede administrar dependencias y se integra con el administrador de paquetes Hex, el cual te permite realizar resolución de dependencias, obtener paquetes remotos y alojar documentación para todo el ecosistema de Erlang.
Herramientas como IEx (la consola interactiva de Elixir) te ofrecen una manera de explorar todas las ventajas del lenguaje. Permite auto-completado, ayudantes para depuración del código, acceso a la documentación con un formato amigable, entre otros.
$ iex
Interactive Elixir - press Ctrl+C to exit (type h() ENTER for help)
iex> h String.trim # Prints the documentation forfunction
iex> i "Hello, World" # Prints information about the given datatype
iex> break! String.trim/1 # Sets a breakpoint in the String.trim/1function
iex> recompile # Recompiles the currentprojecton the fly
Como ya hemos mencionado previamente, Elixir se ejecuta sobre la máquina virtual de Erlang, lo cual permite a los desarrolladores acceder al ecosistema de Erlang. Desde Elixir puedes invocar funciones de Erlang sin ningún costo en tiempo de ejecución.
iex> :crypto.hash(:md5, "Using crypto from Erlang OTP")
<<192, 223, 75, 115, ...>>
Si deseas dominar este lenguaje de programación, sigue estos maravillosos cursos de Elixir:
Este post es una joya 💎 Elixir es una bestialidad de lenguaje, bien por Platzi.
Tengo una gigantesca duda, Exactamente en que casos se puede aplicar elixir y ejemplos? Disculpen es que soy nuevo y no entendí absolutamente nada, pero talvez con ejemplos pueda aclarar las dudas.
Elixir es una maravilla!!! ☺️
Que interesante lenguaje y el artículo esta muy completo. Creo que tomare el curso de introducción para checarlo más a detalle.