Domingos Negros – 0x07 – Altair 8800 in 2020

Hi mate, welcome to the next Black Sunday. Welcome to “ALTAIR 8800 IN 2020”

The next session will be the 7th of June at 19:30h (UTC+2) (12:30h PM in Missisippi)

Where?

Online: mumble

Watch: streaming

Who is the speaker?

The speaker will be Jesse (aka jebug29). Directly from Mississippi, United States.

It will be in English the workshop?

Yes, but we will try to translate it in live

I’m invited?

Sure, and it’s free

Domingos Negros – 0x06 – Docker en pequeñas dosis

Seguimos en la brecha. Este domingo una sesión especial, un taller para iniciarse en la secta Docker

El compañero @ale nos trae “Docker en pequeñas dosis” para que sea digerible y no enganche en la primera cita

¿Cuando será la sesión?

El Domingo 31 de Mayo a las 19:30h UTC+2

Siempre es a las 19:30 los domingos 🙂

¿Dónde será la sesión?

En el sitio de siempre: https://mumble.hatthieves.es
El streaming será en: https://p2p.hatthieves.es

¿He de pagar algo?

Son sesiones gratuitas. Eso no quiere decir que no cuesten trabajo y tiempo. Pero tu no tendrás que pagar nada

¿Puedo ir informándome sobre el contenido del taller?

Siempre puedes leer las entradas que tenemos publicadas sobre el tema

Te esperamos el domingo para una sesión más de Domingo Negro


Díselo a tu primo, saludos

Domingos Negros – 0x02 – YUNOHOST Self-Hosting

YunoHost

Hola chiques. La nueva release 3.7 de yunohost está en la calle disponible para que la bajes y la pruebes en tu casa.

En esta tercera sesión 0x02 estará el gran Xaloc a los mandos de la nave espacial.

¿Pero donde he de conectar para disfrutar de un Domingo Negro?

Un poco de calma amigo/a/e . Vamos por partes.

Has de tener instalado en tu equipo un programa llamado: MUMBLE .
Se trata de un programa que permite conectar a un servidor en el que podrás chatear y conversar si gustas.

Descargas desde aquí el software y lo instalas: https://www.mumble.info/downloads/

Una vez lo tienes instalado has de agregar/configurar un servidor nuevo. Ese servidor es:

Hostname: hatthieves.es
Puerto: 64738

¿De que va este Domingo Negro?

Pues será una sesión chula en la que veremos como se instala y como se configura YunoHost. Para ello hemos dispuesto desde HT de una máquina de laboratorio en la que se realizarán las pruebas necesarias.

Xaloc nos explicará de que va todo esto y para que sirve. Si luego existen dudas en la sala mumble se podrán expresar sin problemas.

¿Necesito ser un Juanker para entender de que se habla en esta sesión?

No. Xaloc explica muy bien y es muy cercano.
La idea es que sea sencilla la autogestión de tus servicios y que puedas probar un montón de ellos de forma cómoda y economica.

Vamos que no necesitas venir con muchos conocimientos para entender de que va esto y por el contrario si no conoces YunoHost posiblemente te sorprenderá de la potencia que tiene para determinados ambitos.

Te esperamos el Domingo 3 de Mayo

Domingos Negros – 0x01 – Engañando a la IA de Google

gore_ia_google

Es el momento de una segunda sesión de Domingo Negro.
El maestro Gore andará compartiendo algo de código y nos explicará aproximadamente durante 30 minutos formas de engañar a la IA de Google.

Tras la introducción de Gore tendremos debate en el server mumble de siempre: hatthieves.es (puerto por defecto).

¿Realmente engañamos a Google?, ¿Es posible escapar a sus garras?

Conectar a los domingos negros

Para entrar a los domingos negros se necesita tener instalado mumble.

Mumble es software libre y se puede descargar desde aquí: https://www.mumble.info/downloads/

Independientemente de si utilizas Windows, Linux, Mac, … podrás entrar.  Una vez se instala mumble tienes que agregar un servidor al que conectarte.

Esto es de algún modo como cuando visitas una página web. Necesitas disponer de un navegador web e indicar a que dirección conectar.

En este caso los datos a los que conectar son:

Server mumble:  hatthieves.es
Puerto: 64738

Hora y fecha

Comenzará a las 19:30 de modo que es bueno entrar si es posible antes.

Será el Domingo 26 de Abril del 2020.

Gratis y con software libre

Acceder a las sesiones no tiene precio alguno, es decir, que puedes entrar libremente y sin pagar.

Esto no quiere decir que no cueste curro montar estas historias. De modo que es bueno agradecer a la gente que comparte cada domingo y si te ves con ganas compartir tu algo algún domingo.

Mumble es siempre la vía de acceso a los domingos negros pero una vez se entra posiblemente y dependiendo del formato de la sesión se te facilitará una url para ver el taller e incluso para poder participar de este.

Para todos los niveles

Puedes relajarte un poquito amigo si vienes de subidito. Aquí se busca compartir con la gente y agradecer a quienes comparten.

No se viene a reírse uno de quienes no saben algo y si a lo contrario. De modo que como nadie nace sabiendo es buena cosa entender que la gente que conecta es diversa.

Se recomienda por tanto que si vienes de “Mr Robot Juanker” mejor no vengas que esto no es para ti.

Domingos Negros – 0x00 – Calzando LineageOS

En el primer Domingo Negro estará a los mandos @Punk. Nos mostrará como calzar “LineageOS”.

Durante aproximadamente 45 minutos nos explicará el proceso que realiza para sustituir la versión Actual de Android de su smartphone. Y la idea es que lo podamos ver mientras lo realiza.

La hora de los domingos negros

El primer domingo negro será a las 20:30 . No obstante en el futuro será a las 19:30 el resto de sesiones.

Lo necesario para conectar

Necesitas tener mumble instalado. Una vez lo tengas instalado has de configurar el servidor mumble “hatthieves.es”. El puerto el que viene por defecto.

Nos escuchamos por allí.

Taller de Elixir #13 – Structs

Aprendimos sobre mapas:

iex> map = %{a: 1, b: 2}
%{a: 1, b: 2}
iex> map[:a]
1
iex> %{map | a: 3}
%{a: 3, b: 2}

Las estructuras son extensiones construidas sobre mapas que proporcionan comprobaciones en tiempo de compilación y valores predeterminados.

Definiendo structs

Para definir una estructura, se utiliza la construcción defstruct:

iex> defmodule User do
...>   defstruct name: "John", age: 27
...> end

La lista de palabras clave utilizada con defstruct define qué campos tendrá la estructura junto con sus valores predeterminados.

Las estructuras toman el nombre del módulo en el que están definidas. En el ejemplo anterior, definimos una estructura llamada User.

Ahora podemos crear estructuras User utilizando una sintaxis similar a la utilizada para crear mapas (si ha definido la estructura en un archivo separado, puede compilar el archivo dentro de IEx antes de continuar ejecutando c “file.exs”. Ten en cuenta es posible que reciba un error que indique “the struct was not yet defined” si prueba el siguiente ejemplo en un archivo directamente debido a cuando se resuelven las definiciones):

iex> %User{}
%User{age: 27, name: "John"}
iex> %User{name: "Jane"}
%User{age: 27, name: "Jane"}

Las estructuras proporcionan garantías en tiempo de compilación de que solo los campos (y todos ellos) definidos a través de defstruct podrán existir en una estructura:

iex> %User{oops: :field}
** (KeyError) key :oops not found in: %User{age: 27, name: "John"}

Acceder y actualizar structs

Cuando hablamos de mapas, mostramos cómo podemos acceder y actualizar los campos de un mapa. Las mismas técnicas (y la misma sintaxis) también se aplican a las estructuras:

iex> john = %User{}
%User{age: 27, name: "John"}
iex> john.name
"John"
iex> jane = %{john | name: "Jane"}
%User{age: 27, name: "Jane"}
iex> %{jane | oops: :field}
** (KeyError) key :oops not found in: %User{age: 27, name: "Jane"}

Cuando se usa la sintaxis de actualización, la VM es consciente de que no se agregarán nuevas claves a la estructura, lo que permite que los mapas debajo compartan su estructura en la memoria. En el ejemplo anterior, tanto John como Jane comparten la misma estructura clave en la memoria.

Las estructuras también se pueden usar en la coincidencia de patrones, tanto para la coincidencia en el valor de claves específicas como para garantizar que el valor de coincidencia sea una estructura del mismo tipo que el valor coincidente.

iex> %User{name: name} = john
%User{age: 27, name: "John"}
iex> name
"John"
iex> %User{} = %{}
** (MatchError) no match of right hand side value: %{}

Structs son mapas desnudos debajo

En el ejemplo anterior, la coincidencia de patrones funciona porque debajo de las estructuras hay mapas desnudos con un conjunto fijo de campos. Como mapas, las estructuras almacenan un campo “especial” llamado __struct__ que contiene el nombre de la estructura:

iex> is_map(john)
true
iex> john.__struct__
User

Tenga en cuenta que nos referimos a las estructuras como mapas desnudos porque ninguno de los protocolos implementados para los mapas están disponibles para estructuras. Por ejemplo, no puede enumerar ni acceder a una estructura:

iex> john = %User{}
%User{age: 27, name: "John"}
iex> john[:name]
** (UndefinedFunctionError) function User.fetch/2 is undefined (User does not implement the Access behaviour)
             User.fetch(%User{age: 27, name: "John"}, :name)
iex> Enum.each john, fn({field, value}) -> IO.puts(value) end
** (Protocol.UndefinedError) protocol Enumerable not implemented for %User{age: 27, name: "John"}

Sin embargo, dado que las estructuras son solo mapas, funcionan con las funciones del módulo Map:

iex> jane = Map.put(%User{}, :name, "Jane")
%User{age: 27, name: "Jane"}
iex> Map.merge(jane, %User{name: "John"})
%User{age: 27, name: "John"}
iex> Map.keys(jane)
[:__struct__, :age, :name]

Valores predeterminados y claves requeridas

Si no especifica un valor de clave predeterminado al definir una estructura, se supondrá nil:

iex> defmodule Product do
...>   defstruct [:name]
...> end
iex> %Product{}
%Product{name: nil}

Puede definir una estructura que combine ambos campos con valores predeterminados explícitos y valores nil implícitos. En este caso, primero debe especificar los campos que están implícitamente predeterminados en nil:

iex> defmodule User do
...>   defstruct [:email, name: "John", age: 27]
...> end
iex> %User{}
%User{age: 27, email: nil, name: "John"}

Hacerlo en orden inverso generará un error de sintaxis:

iex> defmodule User do                          
...>   defstruct [name: "John", age: 27, :email]
...> end
** (SyntaxError) iex:107: syntax error before: email

También puede exigir que ciertas claves tengan que especificarse al crear la estructura:

iex> defmodule Car do
...>   @enforce_keys [:make]
...>   defstruct [:model, :make]
...> end
iex> %Car{}
** (ArgumentError) the following keys must also be given when building struct Car: [:make]
    expanding struct: Car.__struct__/1

Taller de Elixir #12 – Atributos del módulo

Los atributos del módulo en Elixir tienen tres propósitos

  • Sirven para anotar el módulo, a menudo con información para ser utilizada por el usuario o la VM.
  • Trabajan como constantes.
  • Funcionan como un módulo de almacenamiento temporal para ser utilizado durante la compilación.

Veamos cada caso, uno por uno.

Anotaciones

Elixir trae el concepto de atributos de módulo de Erlang. Por ejemplo:

defmodule MyServer do
  @vsn 2
end

En el ejemplo anterior, estamos configurando explícitamente el atributo de versión para ese módulo. @vsn es utilizado por el mecanismo de recarga de código en Erlang VM para verificar si un módulo se ha actualizado o no. Si no se especifica ninguna versión, la versión se establece en la suma de comprobación MD5 de las funciones del módulo.

Elixir tiene un puñado de atributos reservados. Aquí hay algunos de ellos, los más utilizados:

  • @moduledoc: proporciona documentación para el módulo actual.
  • @doc: proporciona documentación para la función o macro que sigue al atributo.
  • @behaviour: (observe la ortografía británica) utilizada para especificar un OTP o un comportamiento definido por el usuario.
  • @before_compile: proporciona un enlace que se invocará antes de compilar el módulo. Esto hace posible inyectar funciones dentro del módulo exactamente antes de la compilación.

@moduledoc y @doc son los atributos más utilizados. Elixir trata la documentación como de primera clase y proporciona muchas funciones para acceder a la documentación. Puede leer más en la documentación en Elixir.

Volvamos al módulo Math definido en los capítulos anteriores, agreguemos documentación y guárdelo en el archivo math.ex:

defmodule Math do
  @moduledoc """
  Provides math-related functions.

  ## Examples

      iex> Math.sum(1, 2)
      3

  """

  @doc """
  Calculates the sum of two numbers.
  """
  def sum(a, b), do: a + b
end

Elixir promueve el uso de Markdown con heredocs para escribir documentación legible. Los heredocs son cadenas de varias líneas, comienzan y terminan con comillas dobles triples, manteniendo el formato del texto interno. Podemos acceder a la documentación de cualquier módulo compilado directamente desde IEx:

$ elixirc math.ex
$ iex
iex> h Math # Access the docs for the module Math
...
iex> h Math.sum # Access the docs for the sum function
...

También tenemos una herramienta llamada ExDoc que se utiliza para generar páginas HTML a partir de la documentación.

Puede consultar los documentos de Módulo para obtener una lista completa de los atributos admitidos. Elixir también usa atributos para definir typepecs.

Esta sección cubre los atributos integrados. Sin embargo, los desarrolladores también pueden usar los atributos o extenderlos las bibliotecas para admitir un comportamiento personalizado.

Constantes

Los desarrolladores de Elixir a menudo usan atributos de módulo cuando desean hacer que un valor sea más visible o reutilizable:

defmodule MyServer do
  @initial_state %{host: "127.0.0.1", port: 3456}
  IO.inspect @initial_state
end

Intentar acceder a un atributo que no se definió imprimirá una advertencia:

defmodule MyServer do
  @unknown
end
warning: undefined module attribute @unknown, please remove access to @unknown or explicitly set it before access

Los atributos también se pueden leer dentro de las funciones:

defmodule MyServer do
  @my_data 14
  def first_data, do: @my_data
  @my_data 13
  def second_data, do: @my_data
end

MyServer.first_data #=> 14
MyServer.second_data #=> 13

Cada vez que se lee un atributo dentro de una función, se toma una instantánea de su valor actual. En otras palabras, el valor se lee en tiempo de compilación y no en tiempo de ejecución. Como veremos, esto también hace que los atributos sean útiles como almacenamiento durante la compilación del módulo.

Normalmente, repetir un atributo del módulo hará que su valor se reasigne, pero hay circunstancias en las que es posible que desee configurar el atributo del módulo para que se acumulen sus valores:

defmodule Foo do
  Module.register_attribute __MODULE__, :param, accumulate: true

  @param :foo
  @param :bar     
  # here @param == [:foo, :bar]
end

Se pueden invocar funciones al definir un atributo del módulo:

defmodule MyApp.Notification do
  @service Application.get_env(:my_app, :email_service)
  @message Application.get_env(:my_app, :welcome_email)
  def welcome(email), do: @service.send_welcome_message(email, @message)
end

Sin embargo, tenga cuidado: las funciones definidas en el mismo módulo que el atributo en sí no se pueden invocar porque aún no se han compilado cuando se está definiendo el atributo.

Al definir un atributo, no deje un salto de línea entre el nombre del atributo y su valor.

Almacenamiento temporal

Uno de los proyectos en la organización Elixir es el proyecto Plug, que pretende ser una base común para construir bibliotecas web y marcos en Elixir.

La biblioteca Plug permite a los desarrolladores definir sus propios plugins que se pueden ejecutar en un servidor web:

defmodule MyPlug do
  use Plug.Builder

  plug :set_header
  plug :send_ok

  def set_header(conn, _opts) do
    put_resp_header(conn, "x-header", "set")
  end

  def send_ok(conn, _opts) do
    send_resp(conn, 200, "ok")
  end
end

IO.puts "Running MyPlug with Cowboy on http://localhost:4000"
Plug.Adapters.Cowboy.http MyPlug, []

En el ejemplo anterior, hemos utilizado la macro plug/1 para conectar funciones que se invocarán cuando haya una solicitud web. Internamente, cada vez que llama a plug/1, la biblioteca Plug almacena el argumento dado en un atributo @plugs. Justo antes de compilar el módulo, Plug ejecuta una devolución de llamada que define una función (call/2) que maneja las solicitudes HTTP. Esta función ejecutará todos los enchufes dentro de @plugs en orden.

Para comprender el código subyacente, necesitaríamos macros, por lo que volveremos a visitar este patrón en la guía de metaprogramación. Sin embargo, el enfoque aquí está en cómo usar los atributos del módulo como almacenamiento permite a los desarrolladores crear DSL.

Otro ejemplo proviene del marco ExUnit que utiliza los atributos del módulo como anotación y almacenamiento:

defmodule MyTest do
  use ExUnit.Case

  @tag :external
  test "contacts external service" do
    # ...
  end
end

Las etiquetas en ExUnit se utilizan para anotar pruebas. Las etiquetas se pueden usar más tarde para filtrar las pruebas. Por ejemplo, puede evitar ejecutar pruebas externas en su máquina porque son lentas y dependen de otros servicios, mientras que aún pueden habilitarse en su sistema de compilación.

Esperamos que esta sección arroje algo de luz sobre cómo Elixir admite la metaprogramación y cómo los atributos del módulo juegan un papel importante al hacerlo.

Taller de Elixir #11 – alias, require, and import

Para facilitar la reutilización del software, Elixir proporciona tres directivas (alias, requerir e importar) más una macro llamada uso resumida a continuación:

# Alias del módulo para que se pueda llamar como Bar en lugar de Foo.Bar
alias Foo.Bar, as: Bar

# Requiere que el módulo use sus macros
require Foo

# Importa funciones de Foo para poder llamarlas sin el prefijo `Foo.`
import Foo

# Invoca el código personalizado definido en Foo como un punto de extensión
use Foo

alias

Permite configurar alias para cualquier nombre de módulo dado.

Imagine que un módulo utiliza una lista especializada implementada en Math.List. La directiva alias permite hacer referencia a Math.List como List dentro de la definición del módulo:

defmodule Stats do
  alias Math.List, as: List
end

Es igual que:

defmodule Stats do
  alias Math.List
end

Ten en cuenta que el alias tiene un ámbito léxico, lo que le permite establecer alias dentro de funciones específicas:

defmodule Math do
  def plus(a, b) do
    alias Math.List
    # ...
  end

  def minus(a, b) do
    # ...
  end
end

En el ejemplo anterior, estamos invocando el alias dentro de la función plus/2, el alias será válido solo dentro de la función plus/2. minus/2 no se verá afectado en absoluto.

require

Elixir proporciona macros como mecanismo para la metaprogramación (escribir código que genera código). Las macros se expanden en tiempo de compilación.

Las funciones públicas en los módulos están disponibles globalmente, pero para usar macros, debe optar por solicitar el módulo en el que están definidas.

iex> Integer.is_odd(3)
** (CompileError) iex:1: you must require Integer before invoking the macro Integer.is_odd/1
    (elixir) src/elixir_dispatch.erl:97: :elixir_dispatch.dispatch_require/6
iex> require Integer
Integer
iex> Integer.is_odd(3)
true

En Elixir, Integer.is_odd/1 se define como una macro para que pueda usarse como guard. Esto significa que, para invocar Integer.is_odd/1, primero necesitamos el módulo Integer.

Tenga en cuenta que, al igual que la directiva alias, require también tiene un alcance léxico. Hablaremos más sobre macros en un capítulo posterior.

import

Usamos import siempre que deseamos acceder a funciones o macros de otros módulos sin usar el nombre completo. Tenga en cuenta que solo podemos importar funciones públicas, ya que las funciones privadas nunca son accesibles externamente.

Por ejemplo, si queremos usar la función duplicate/2 del módulo List varias veces, podemos importarla:

iex> import List, only: [duplicate: 2]
List
iex> duplicate :ok, 3
[:ok, :ok, :ok]

Importamos solo la función duplicate/2 de List. Aunque: solo es opcional, se recomienda su uso para evitar importar todas las funciones de un módulo dado dentro del alcance actual. :except también podría darse como una opción para importar todo en un módulo, excepto una lista de funciones.

Ten en cuenta que la importación también tiene un ámbito léxico. Esto significa que podemos importar macros o funciones específicas dentro de las definiciones de funciones:

defmodule Math do
  def some_function do
    import List, only: [duplicate: 2]
    duplicate(:ok, 10)
  end
end

En el ejemplo anterior, el List.duplicate/2 importado solo es visible dentro de esa función específica. duplicate/2 no estará disponible en ninguna otra función en ese módulo (o cualquier otro módulo para el caso).

Tenga en cuenta que la importación de un módulo lo requiere automáticamente.

use

La macro de use con frecuencia es un punto de extensión. Esto significa que, cuando usa un módulo FooBar, permite que ese módulo inyecte cualquier código en el módulo actual, como importarse a sí mismo u otros módulos, definir nuevas funciones, establecer un estado del módulo, etc.

Por ejemplo, para escribir pruebas usando el marco ExUnit, un desarrollador debe usar el módulo ExUnit.Case:

defmodule AssertionTest do
  use ExUnit.Case, async: true

  test "always pass" do
    assert true
  end
end

Detrás de escena, el uso requiere el módulo dado y luego llama a la devolución de llamada __using__/1, lo que permite que el módulo inyecte algo de código en el contexto actual. Algunos módulos (por ejemplo, el ExUnit.Case anterior, pero también Supervisor y GenServer) usan este mecanismo para llenar su módulo con un comportamiento básico, que su módulo está destinado a anular o completar.

En general, el siguiente módulo:

se compila en

defmodule Example do
  require Feature
  Feature.__using__(option: :value)
end

Como el uso permite que se ejecute cualquier código, no podemos conocer realmente los efectos secundarios del uso de un módulo sin leer su documentación. Por esta razón, a menudo se prefiere la importación y el alias, ya que su semántica está definida por el lenguaje.

Entendiendo los alias

Un alias en Elixir es un identificador en mayúscula (como String, Keyword, etc.) que se convierte en un átomo durante la compilación. Por ejemplo, el alias String se traduce por defecto al átomo: “Elixir.String”:

iex> is_atom(String)
true
iex> to_string(String)
"Elixir.String"
iex> :"Elixir.String" == String
true

Al usar la directiva alias/2, estamos cambiando el átomo al que se expande el alias.

Los alias se expanden a átomos porque en los módulos Erlang VM (y, en consecuencia, Elixir) siempre están representados por átomos. Por ejemplo, ese es el mecanismo que usamos para llamar a los módulos de Erlang:

iex> :lists.flatten([1, [2], 3])
[1, 2, 3]

Anidamiento de módulos

Fíjate en el siguiente ejemplo:

defmodule Foo do
  defmodule Bar do
  end
end

El ejemplo anterior definirá dos módulos: Foo y Foo.Bar. Se puede accedeFrom Elixir v1.2, it is possible to alias, import or require multiple modules at once. This is particularly useful once we start nesting modules, which is very common when building Elixir applications. For example, imagine you have an application where all modules are nested under MyApp, you can alias the modules MyApp.Foo, MyApp.Bar and MyApp.Baz at once as follows:r al segundo como Bar dentro de Foo siempre que estén en el mismo ámbito léxico. El código anterior es exactamente el mismo que:

defmodule Elixir.Foo do
  defmodule Elixir.Foo.Bar do
  end
  alias Elixir.Foo.Bar, as: Bar
end

O que:

defmodule Elixir.Foo do
  alias Elixir.Foo.Bar, as: Bar
end

defmodule Elixir.Foo.Bar do
end

Multi alias/import/require/use

Desde Elixir v1.2, es posible crear alias, importar o requerir múltiples módulos a la vez. Esto es particularmente útil una vez que comenzamos a anidar módulos, lo cual es muy común al construir aplicaciones Elixir. Por ejemplo, imagine que tiene una aplicación en la que todos los módulos están anidados en MyApp, puede crear un alias de los módulos MyApp.Foo, MyApp.Bar y MyApp.Baz de la siguiente manera:

alias MyApp.{Foo, Bar, Baz}

Taller de Elixir #10 – Enumerables y Streams

Enumerables

Elixir proporciona el concepto de enumerables y el módulo Enum para trabajar con ellos. Ya hemos aprendido dos enumerables: listas y mapas.

iex> Enum.map([1, 2, 3], fn x -> x * 2 end)
[2, 4, 6]
iex> Enum.map(%{1 => 2, 3 => 4}, fn {k, v} -> k * v end)
[2, 12]

El módulo Enum proporciona una amplia gama de funciones para transformar, ordenar, agrupar, filtrar y recuperar elementos de enumerables. Es uno de los módulos que los desarrolladores usan con frecuencia en su código Elixir.

Elixir también proporciona rangos:

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

Las funciones en el módulo Enum se limitan a, como su nombre lo indica, enumerar valores en estructuras de datos. Para operaciones específicas, como insertar y actualizar elementos particulares, es posible que deba buscar módulos específicos para el tipo de datos. Por ejemplo, si desea insertar un elemento en una posición dada en una lista, debe usar la función List.insert_at/3 del módulo List, ya que tendría poco sentido insertar un valor en, por ejemplo, un rango.

Decimos que las funciones en el módulo Enum son polimórficas porque pueden trabajar con diversos tipos de datos. En particular, las funciones en el módulo Enum pueden funcionar con cualquier tipo de datos que implemente el protocolo Enumerable.

El operador pipe

En el ejemplo siguiente tiene una pipeline de operaciones. Comenzamos con un rango y luego multiplicamos cada elemento en el rango por 3. Esta primera operación ahora creará y devolverá una lista con 100_000 elementos. Luego mantenemos todos los elementos impares de la lista, generando una nueva lista, ahora con 50_000 elementos, y luego sumamos todas las entradas.

iex> total_sum = 1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum
7500000000

El símbolo |> utilizado en el fragmento de arriba es el operador de tubería: toma la salida de la expresión en su lado izquierdo y la pasa como el primer argumento para la llamada a la función en su lado derecho. Es similar al Unix | operador. Su propósito es resaltar los datos que están siendo transformados por una serie de funciones. Para ver cómo puede hacer que el código sea más limpio, eche un vistazo al ejemplo anterior reescrito sin usar el operador |>:

iex> Enum.sum(Enum.filter(Enum.map(1..100_000, &(&1 * 3)), odd?))
7500000000

Puedes encontrar más sobre el operador de tubería en su documentación.

Streams

Como alternativa a Enum, Elixir proporciona el módulo Stream que admite operaciones diferidas:

iex> 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?) |> Enum.sum
7500000000

Los flujos son enumerables perezosos y componibles.

En el ejemplo anterior, 1..100_000 |> Stream.map (& (& 1 * 3)) devuelve un tipo de datos, un flujo real, que representa el cálculo del mapa en el rango 1..100_000:

iex> 1..100_000 |> Stream.map(&(&1 * 3))
<[enum: 1..100000, funs: [#Function<34.16982430/1 in Stream.map/2>]]>

Además, son componibles porque podemos canalizar muchas operaciones de flujo:

1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?)
<[enum: 1..100000, funs: [...]]>

En lugar de generar listas intermedias, las secuencias crean una serie de cálculos que se invocan solo cuando pasamos la secuencia subyacente al módulo Enum. Las secuencias son útiles cuando se trabaja con colecciones grandes, posiblemente infinitas.

iex> stream = Stream.cycle([1, 2, 3])
<15.16982430/2 in Stream.unfold/2>
iex> Enum.take(stream, 10)
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1]

Por otro lado, Stream.unfold/2 puede usarse para generar valores a partir de un valor inicial dado:

iex> stream = Stream.unfold("hełło", &String.next_codepoint/1)
<39.75994740/2 in Stream.unfold/2>
iex> Enum.take(stream, 3)
["h", "e", "ł"]

Otra función interesante es Stream.resource/3, que se puede utilizar para envolver los recursos, garantizando que se abran justo antes de la enumeración y se cierren después, incluso en caso de fallas. Por ejemplo, File.stream!/1 se basa en Stream.resource/3 para transmitir archivos:

iex> stream = File.stream!("path/to/file")
%File.Stream{
  line_or_bytes: :line,
  modes: [:raw, :read_ahead, :binary],
  path: "path/to/file",
  raw: true
}
iex> Enum.take(stream, 10)

El ejemplo anterior buscará las primeras 10 líneas del archivo que ha seleccionado. Esto significa que las transmisiones pueden ser muy útiles para manejar archivos grandes o incluso recursos lentos como los recursos de red.

La cantidad de funcionalidad en los módulos Enum y Stream puede ser desalentadora al principio, pero se familiarizará con ellos caso por caso. En particular, enfóquese primero en el módulo Enum y solo muévase a Stream para los escenarios particulares donde se requiere pereza, ya sea para manejar recursos lentos o grandes colecciones, posiblemente infinitas.

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]