Descubriendo Erlang

y la arquitectura de una aplicación Web

Martín Rodríguez
Erlang Battleground
5 min readJun 12, 2020

--

Tecnologías y lenguajes existen muchos, pero ninguno como Erlang para desarrollar un sistema completo. Como Joe Armstrong dijo, “si Java es ‘hazlo una vez y córrelo donde sea’, Erlang es entonces ‘hazlo una vez y córrelo para siempre’ ”.

El Inicio

Hace mucho tiempo, por azares del destino, me topé con el maravilloso mundo de las computadoras. Inicié programando con Cobol, aunque me hubiera gustado empezar desarrollando en el lenguaje C, por su sintaxis y eficiencia.

Tandem Computers

El camino que me llevaría a encontrarme con Erlang comenzó cuando conocí la tecnología Fault Tolerance in Tandem Computer Systems, tanto en hardware como en software. Sin ahondar en detalles, En Tandem, los programas se desarrollan de manera separada, por un lado, los requesters y por otro, los servers — algo muy similar al frontend y el backend de la tecnología Web — , para poder configurarlos en Pathway de manera distribuida entre todos los CPU’s de la computadora.

Elixir

Fue en 2016 que un compañero de trabajo, al saber que yo había desarrollado para Tandem, me habló de Elixir. Mencionó haber escuchado que, de alguna manera, Elixir tenía gran similitud con la tecnología de Tandem. Por ello decidí indagar al respecto, y, en efecto, como lo expone Joe Armstrong en Fault tolerance 101, la manera de trabajar por procesos y en que cada uno tenga su propio mailbox es muy parecida.

Functional programming paradigm

Cuando empecé a trabajar con Elixir, no sabía del paradigma funcional, y al venir de la programación imperativa y orientada a objetos, no me fue fácil dejar de pensar en dichos esquemas. En aquellos momentos, comentando con un amigo sobre el paradigma funcional, me dijo “es programación write-only”. Pero sé por experiencia que, cuando se aprende algo nuevo, si se hace con gusto y una mente abierta, es posible entenderlo con mayor facilidad; y es así como ahora me queda claro por qué lenguajes como C# van adoptando más y más la sintaxis funcional.

F#

Después de conocer Elixir, se presentó la oportunidad para desarrollar aplicaciones móviles. Una vez conociendo el paradigma funcional, no lo quise dejar, así que aproveché la oportunidad para trabajar con la tecnología cross-platform de Xamarin y el lenguaje F#. Aquí sólo debo agregar que Fabulous es un excelente framework a considerar.

Erlang

A principios de 2019, investigando la tecnología BPM, me encontré con BPE, N2O y Nitro de SYNRC. Al estar estos frameworks desarrollados en Erlang, mi siguiente paso fue aprender este lenguaje, y ¡wow! Ahora, entre más lo uso, más me agrada por ser un lenguaje del paradigma funcional, por su sintaxis, por su tecnología fault-tolerant, etc.

Arquitectura de sistemas y un buen libro

Ahora bien, si algo he aprendido en toda mi trayectoria en el fascinante mundo de la computación es que desarrollar un sistema completo involucra varias tecnologías, que hay que saber cómo y, sobre todo, dónde utilizar cada una para no terminar con un mazacote imposible de mantener.

Ya metido en Erlang, recordé haber guardado hace tiempo el libro Functional Web Development with Elixir, OTP, and Phoenix, de Lance Halvorsen, y decidí retomarlo. Este texto, precisamente explica cómo desarrollar y acomodar las diferentes piezas que componen un sistema, llevando al lector de la mano, paso a paso, para probar de manera independiente cada una de ellas. Y, ya que Elixir es uno de los lenguajes que corren en la BEAM, es posible estructurar una aplicación de Erlang de manera similar. Entonces, me di a la tarea de seguir las instrucciones de Halvorsen en Erlang en lugar de Elixir, y la parte frontend en N2O y Nitro en lugar de Phoenix, aquí un ejemplo:

Código en Erlang

-module(island).-include("coordinate.hrl").
-include("island.hrl").
-export([new/2, types/0, guess/2, forested/1, overlaps/2]).new(Type, UpperLeft) ->
island(offsets(Type), UpperLeft).
types() ->
[atoll, dot, l_shape, s_shape, square].
guess(Island, Coordinate) ->
guessed(lists:member(Coordinate, Island#island.coordinates),
Island,
Coordinate).
forested(Island) ->
lists:sort(Island#island.coordinates) =:=
lists:sort(Island#island.hit_coordinates).
overlaps(ExistingIsland, NewIsland) ->
not sets:is_disjoint(
sets:from_list(ExistingIsland#island.coordinates),
sets:from_list(NewIsland#island.coordinates)).
%% internal functionsoffsets(atoll) ->
{ok, [{0, 0}, {0, 1}, {1, 1}, {2, 0}, {2, 1}]};
offsets(dot) ->
{ok, [{0, 0}]};
offsets(l_shape) ->
{ok, [{0, 0}, {1, 0}, {2, 0}, {2, 1}]};
offsets(s_shape) ->
{ok, [{0, 1}, {0, 2}, {1, 0}, {1, 1}]};
offsets(square) ->
{ok, [{0, 0}, {0, 1}, {1, 0}, {1, 1}]};
offsets(_) ->
{error, invalid_island_type}.
island({ok, Offsets}, UpperLeft) ->
add_coordinates(add_coordinate(Offsets, UpperLeft, []));
island({error, Message}, _UpperLeft) ->
{error, Message}.
add_coordinate([], _UpperLeft, L) ->
{ok, lists:reverse(L)};
add_coordinate([{OffsetRow, OffsetCol} | T], UpperLeft, L) ->
case coordinate:new(OffsetRow + UpperLeft#coordinate.row,
OffsetCol + UpperLeft#coordinate.col) of
{ok, C} ->
add_coordinate(T, UpperLeft, [C | L]);
{error, Message} ->
{error, Message}
end.
add_coordinates({ok, L}) ->
{ok, #island{coordinates = L}};
add_coordinates({error, Message}) ->
{error, Message}.
guessed(true, Island, Coordinate) ->
{hit,
Island#island{
hit_coordinates =
lists:reverse(
[Coordinate | Island#island.hit_coordinates])}};
guessed(false, _Island, _Coordinate) ->
miss.

Código en Elixir

defmodule IslandsEngine.Island do
alias IslandsEngine.{Coordinate, Island}
@enforce_keys [:coordinates, :hit_coordinates]
defstruct [:coordinates, :hit_coordinates]
def new(type, %Coordinate{} = upper_left) do
with [_|_] = offsets <- offsets(type),
%MapSet{} = coordinates <- add_coordinates(
offsets, upper_left)
do
{:ok, %Island{
coordinates: coordinates,
hit_coordinates: MapSet.new()}}
else
error -> error
end
end
def types(), do: [:atoll, :dot, :l_shape, :s_shape, :square]def guess(island, coordinate) do
case MapSet.member?(island.coordinates, coordinate) do
true ->
hit_coordinates =
MapSet.put(island.hit_coordinates, coordinate)
{:hit, %{island | hit_coordinates: hit_coordinates}}
false -> :miss
end
end
def forested?(island), do:
MapSet.equal?(island.coordinates, island.hit_coordinates)
def overlaps?(existing_island, new_island), do:
not MapSet.disjoint?(
existing_island.coordinates, new_island.coordinates)
defp add_coordinates(offsets, upper_left) do
Enum.reduce_while(offsets, MapSet.new(), fn offset, acc ->
add_coordinate(acc, upper_left, offset)
end)
end
defp add_coordinate(coordinates, %Coordinate{row: row, col: col},
{row_offset, col_offset}) do
case Coordinate.new(row + row_offset, col + col_offset) do
{:ok, coordinate} ->
{:cont, MapSet.put(coordinates, coordinate)}
{:error, :invalid_coordinate} ->
{:halt, {:error, :invalid_coordinate}}
end
end
defp offsets(:atoll), do: [{0, 0},{0, 1},{1, 1}, {2, 0}, {2, 1}]
defp offsets(:dot), do: [{0, 0}]
defp offsets(:l_shape), do: [{0, 0}, {1, 0}, {2, 0}, {2, 1}]
defp offsets(:s_shape), do: [{0, 1}, {0, 2}, {1, 0}, {1, 1}]
defp offsets(:square), do: [{0, 0}, {0, 1}, {1, 0}, {1, 1}]
defp offsets(_), do: {:error, :invalid_island_type}
end

Como se puede ver, es muy fácil desarrollar una aplicación Web completamente en Erlang en conjunto con N2O WebSocket Application Server y Nitro Web Framework. Además, la sintaxis de Erlang es muy clara y… ¡muy bonita!, si se tienen en cuenta sus reglas de programación.

Les comparto el ejercicio completo en GitHub:

Conclusión

Inicié con Cobol, y después de pasar por varios lenguajes y tecnologías, con el ímpetu de seguir aprendiendo, llegué a Erlang.

Sin embargo, considero que no basta con el puro conocimiento, sino que también hay que profundizar en la mejor manera de utilizar las tecnologías. Es por eso que encuentro relevante y ampliamente recomendable el libro Functional Web Development with Elixir, OTP, and Phoenix, de Lance Halvorsen, que detalla sobre la arquitectura de un sistema y como demuestro aquí arriba, puede ser implementado tanto en Elixir como en Erlang.

--

--