Translate

Mostrando las entradas para la consulta elixir ordenadas por fecha. Ordenar por relevancia Mostrar todas las entradas
Mostrando las entradas para la consulta elixir ordenadas por fecha. Ordenar por relevancia Mostrar todas las entradas

domingo, 19 de enero de 2025

Diferencias entre Hilos y Procesos en Elixir


Elixir, gracias a la BEAM VM, utiliza procesos livianos que se diferencian fundamentalmente de los hilos tradicionales:  

1. Aislamiento Completo  

  • Los procesos en Elixir no comparten memoria. Esto evita condiciones de carrera, simplificando el manejo de la concurrencia.  
  • En contraste, los hilos suelen compartir memoria, requiriendo mecanismos como locks y semáforos para sincronización.  


2. Ligereza y Escalabilidad  

  • Cada proceso en Elixir consume muy pocos recursos, permitiendo que millones coexistan.  
  • Los hilos son más pesados y su número está limitado por el sistema operativo.  


3. Comunicación por Mensajes  

  • Los procesos en Elixir se comunican mediante mensajes asíncronos, usando send y receive.  
  • Los hilos comparten datos directamente, lo que puede complicar la concurrencia.  

4. Tolerancia a Fallos  

  • Elixir sigue la filosofía "Let it crash", donde los supervisores reinician procesos fallidos.  
  • Los hilos carecen de un sistema equivalente nativo de supervisión.  


Veamos un ejemplo rápido: 


spawn(fn -> receive do

  msg -> IO.puts("Mensaje recibido: #{msg}")

end end)

|> send("¡Hola, proceso!")


Elixir muestra cómo un enfoque basado en procesos simplifica y fortalece la concurrencia, ideal para sistemas distribuidos y resilientes.  


sábado, 18 de enero de 2025

El Operador |> de Elixir y sus equivalentes en otros lenguajes


En Elixir, el operador |> pasa el resultado de una expresión como el primer argumento de la siguiente función. Ya lo explicamos en el post anterior. 


" hello "

|> String.trim()

|> String.upcase()

Resultado: "HELLO"


Este diseño promueve una lectura fluida del código, eliminando la necesidad de paréntesis anidados.


F#, un lenguaje funcional inspirado en ML, también tiene un operador pipe |> con un propósito similar al de Elixir.


" hello "

|> String.trim

|> String.uppercase


El operador en F# permite que el flujo de datos sea explícito, facilitando la composición de funciones.


Python no tiene un operador pipe nativo, pero existen bibliotecas que lo emulan, como `pipe` o `toolz`. Sin embargo, sin bibliotecas adicionales, puedes lograr algo similar con reduce:


from functools import reduce


data = " hello "

result = reduce(lambda acc, fn: fn(acc), [str.strip, str.upper], data)

print(result)  # HELLO


Con una biblioteca como pipe:


from pipe import Pipe


result = " hello " | Pipe(str.strip) | Pipe(str.upper)

print(result)  # HELLO


JavaScript aún no tiene un operador pipe oficial, pero hay una propuesta en desarrollo en el comité TC39 (etapa 2 al momento de escribir). Con esta propuesta, el pipe se usa de la siguiente manera:


" hello "

  |> (x => x.trim())

  |> (x => x.toUpperCase());


Por ahora, puedes emularlo con funciones:


const pipeline = (...fns) => x => fns.reduce((v, f) => f(v), x);


const result = pipeline(

  x => x.trim(),

  x => x.toUpperCase()

)(" hello ");

console.log(result); // HELLO


Scala no tiene un operador pipe nativo, pero es posible definir uno:


implicit class PipeOps[T](val value: T) extends AnyVal {

  def |>[R](f: T => R): R = f(value)

}


val result = " hello "

  |> (_.trim)

  |> (_.toUpperCase)

println(result) // HELLO


En C#, aunque no existe un operador pipe, los métodos de extensión de LINQ se comportan de manera similar:


string result = " hello "

    .Trim()

    .ToUpper();

Console.WriteLine(result); // HELLO


El concepto detrás del operador pipe (`|>`) es universal: facilita la composición de funciones y mejora la legibilidad. Aunque su implementación varía entre lenguajes, su propósito sigue siendo el mismo: transformar datos paso a paso de manera clara y concisa.


martes, 14 de enero de 2025

El Poder del Operador |> en Elixir: Elegancia y Legibilidad


La programación funcional se centra en la composición de funciones para resolver problemas de manera clara y concisa. Uno de los operadores más representativos de este paradigma es el operador pipe |>, que permite encadenar llamadas a funciones de forma fluida y natural.

El operador |> (pipe) se utiliza para pasar el resultado de una expresión como el primer argumento de la siguiente función en la cadena.


value |> function1() |> function2()


Esto es equivalente a:


function2(function1(value))


Como ventajas podemos nombrar: 

  1. Legibilidad Mejorada: El flujo de datos se representa de forma secuencial, como si leyeras un proceso paso a paso.
  2. Eliminación de Paréntesis Anidados: Reduce la complejidad visual de funciones anidadas.
  3. Facilita el Refactoring: Reordenar o agregar pasos en el flujo es más sencillo.


Esto :

String.upcase(String.trim(" hola "))


Se puede escribir así:

" hola "

|> String.trim()

|> String.upcase()


Ambos códigos producen el mismo resultado: `"HOLA"`, pero la versión con |> es más legible.

El operador |> no se limita a funciones de la librería estándar; también puedes usarlo con tus propias funciones.


defmodule Math do

  def square(x), do: x * x

  def double(x), do: x * 2

end


5

|> Math.square()

|> Math.double()

Resultado: 50


Veamos ejemplos de uso : 


[1, 2, 3, 4]

|> Enum.map(&(&1 * 2))

|> Enum.filter(&(&1 > 4))

Resultado: [6, 8]


Otro ejemplo con estructuras más complejas: 


%{name: "John", age: 30}

|> Map.put(:country, "USA")

|> Map.update!(:age, &(&1 + 1))

Resultado: %{name: "John", age: 31, country: "USA"}


El operador `|>` es una herramienta fundamental en Elixir que no solo mejora la legibilidad del código, sino que también alienta un diseño funcional y modular. Al adoptarlo, puedes construir pipelines claros y efectivos que hagan que tu código sea más expresivo y fácil de mantener.


sábado, 4 de enero de 2025

Actores en Elixir


Elixir, construido sobre la Máquina Virtual de Erlang (BEAM), es conocido por su capacidad para manejar concurrencia y tolerancia a fallos de manera elegante. Una de las piezas clave detrás de esta potencia es el modelo de actores.

El modelo de actores es un paradigma de concurrencia donde las entidades llamadas actores:

  • Reciben mensajes.
  • Procesan esos mensajes.
  • Pueden responder, enviar mensajes a otros actores o crear nuevos actores.

En Elixir, los actores se implementan como procesos ligeros gestionados por la BEAM, lo que permite manejar miles o incluso millones de ellos simultáneamente. Las caracteristicas más importantes son: 

  1. Aislamiento completo: Cada actor tiene su propio estado y no comparte memoria con otros actores.
  2. Comunicación mediante mensajes: Los mensajes entre a ctores son asíncronos y pasan a través de colas de mensajes.
  3. Tolerancia a fallos: Si un actor falla, su supervisor puede reiniciarlo, manteniendo la estabilidad del sistema.

En Elixir, los procesos se crean con spawn, y se comunican usando send para enviar mensajes y receive para manejarlos. Veamos un ejemplo: 


defmodule ActorExample do

  def start do

    spawn(fn -> listen() end)

  end


  defp listen do

    receive do

      {:greet, name} ->

        IO.puts("¡Hola, #{name}!")

        listen()

      :stop ->

        IO.puts("Proceso detenido.")

      _ ->

        IO.puts("Mensaje no reconocido.")

        listen()

    end

  end

end


# Crear el actor

pid = ActorExample.start()


# Enviar mensajes al actor

send(pid, {:greet, "Mundo"})

send(pid, :stop)


Elixir hereda de Erlang una rica tradición de más de tres décadas en sistemas concurrentes y distribuidos. Esto lo convierte en una elección ideal para aplicaciones modernas como:

  • Sistemas distribuidos.
  • Aplicaciones web con alta concurrencia.
  • Sistemas en tiempo real.


lunes, 9 de diciembre de 2024

¿Cómo Crear una Librería en Elixir?


Elixir es un lenguaje poderoso y flexible, ideal para crear librerías reutilizables. En esta guía, aprenderás cómo iniciar y publicar tu propia librería.

1. Inicializando el Proyecto


Para empezar, crea un nuevo proyecto con mix:

mix new mi_libreria --module MiLibreria


Esto generará una estructura básica con carpetas como `lib` (para el código) y `mix.exs` (configuración del proyecto).  

En el archivo `mix.exs`, ajusta la metadata básica, como nombre y descripción, para que sea más descriptiva:


def project do

  [

    app: :mi_libreria,

    version: "0.1.0",

    elixir: "~> 1.15",

    description: "Una librería simple para manipular cadenas",

    start_permanent: Mix.env() == :prod

  ]

end


2. Implementando la Funcionalidad


Agregamos lógica en lib/mi_libreria.ex. Por ejemplo, una función para convertir cadenas a mayúsculas:


defmodule MiLibreria do

  @moduledoc """

  MiLibreria es una colección de funciones útiles para cadenas.

  """


  @doc """

  Convierte una cadena en mayúsculas.


  ## Ejemplo

      iex> MiLibreria.convertir_mayusculas("hola")

      "HOLA"

  """

  def convertir_mayusculas(cadena) when is_binary(cadena) do

    String.upcase(cadena)

  end

end


La documentación (`@moduledoc` y `@doc`) ayuda a otros desarrolladores a entender y usar tu librería.


3. Generando Documentación con ExDoc


Añade `ExDoc` al archivo `mix.exs` para generar documentación en HTML:


{:ex_doc, "~> 0.29", only: :dev, runtime: false}


Instala las dependencias:


mix deps.get


Genera la documentación:


mix docs


Esto crea una carpeta `doc` con una versión navegable de tu documentación.


4. Publicando en Hex


Crea una cuenta en Hex.pm:

   Tenemos que registrarnos en Hex.pm y genera una clave de autenticación con:  


   mix hex.user register


Completa la Metadata en mix.exs:


   def project do

     [

       app: :mi_libreria,

       version: "0.1.0",

       description: "Librería para manipulación de cadenas",

       package: package_info()

     ]

   end


   defp package_info do

     [

       maintainers: ["Tu Nombre"],

       licenses: ["MIT"],

       links: %{"GitHub" => "https://github.com/tu_usuario/mi_libreria"}

     ]

   end


  Publica la librería:


   mix hex.publish


Crear una librería en Elixir es sencillo y gratificante. Con estas herramientas y pasos, puedes compartir tu trabajo con la comunidad y contribuir al crecimiento del ecosistema. 

martes, 3 de diciembre de 2024

Ecto = bases de datos + Elixir



Ecto es la herramienta principal para interactuar con bases de datos en el ecosistema de Elixir. Más que un simple ORM, Ecto combina la construcción de consultas, validaciones y migraciones, brindando una experiencia robusta y funcional.

Ecto es un toolkit para bases de datos en Elixir que se compone de tres partes principales:

  • Ecto.Schema: Define la estructura de los datos.
  • Ecto.Changeset: Maneja validaciones y transformaciones.
  • Ecto.Query: Facilita la construcción de consultas.


Para empezar debemos añadir las dependencias en mix.exs:


defp deps do

  [

    {:ecto_sql, "~> 3.10"},

    {:postgrex, ">= 0.0.0"} # Adaptador para PostgreSQL

  ]

end


Luego debemos configurar el repositorio en config/config.exs:


config :mi_app, MiApp.Repo,

  database: "mi_app_db",

  username: "usuario",

  password: "contraseña",

  hostname: "localhost"


config :mi_app, ecto_repos: [MiApp.Repo]


Crear el repositorio:


mix ecto.gen.repo -r MiApp.Repo


Para luego ejecutar migraciones iniciales:


mix ecto.create


Creamos un módulo para tu esquema, que representa una tabla en la base de datos:


defmodule MiApp.Usuario do

  use Ecto.Schema

  schema "usuarios" do

    field :nombre, :string

    field :email, :string

    field :edad, :integer

    timestamps()

  end

end


Y ahora hagamos consultas básicas con Ecto.Query. Ecto ofrece una sintaxis declarativa para construir consultas SQL. Ejemplo:


import Ecto.Query


# Obtener todos los usuarios

query = from u in "usuarios", select: u

MiApp.Repo.all(query)


# Filtrar usuarios mayores de 18 años

query = from u in MiApp.Usuario, where: u.edad > 18, select: u.nombre

MiApp.Repo.all(query)


Los cambios en los datos se gestionan con Ecto.Changeset, lo que facilita aplicar validaciones.


defmodule MiApp.Usuario do

  use Ecto.Schema

  import Ecto.Changeset


  schema "usuarios" do

    field :nombre, :string

    field :email, :string

    field :edad, :integer

    timestamps()

  end


  def cambios_usuario(usuario, attrs) do

    usuario

    |> cast(attrs, [:nombre, :email, :edad])

    |> validate_required([:nombre, :email])

    |> validate_format(:email, ~r/@/)

    |> validate_number(:edad, greater_than_or_equal_to: 0)

  end

end


Ecto permite definir relaciones como has_many, belongs_to y many_to_many. Veamos un ejemplo: 


defmodule MiApp.Usuario do

  use Ecto.Schema


  schema "usuarios" do

    field :nombre, :string

    has_many :posts, MiApp.Post

    timestamps()

  end

end


defmodule MiApp.Post do

  use Ecto.Schema


  schema "posts" do

    field :titulo, :string

    belongs_to :usuario, MiApp.Usuario

    timestamps()

  end

end


Consultas relacionales:


query = from u in MiApp.Usuario, preload: [:posts]

usuarios = MiApp.Repo.all(query)


Ahora actualización de registros:


usuario = MiApp.Repo.get(MiApp.Usuario, 1)

cambioset = MiApp.Usuario.cambios_usuario(usuario, %{nombre: "Nuevo Nombre"})

MiApp.Repo.update(cambioset)


Y Borramos:


usuario = MiApp.Repo.get(MiApp.Usuario, 1)

MiApp.Repo.delete(usuario)


Las migraciones permiten gestionar cambios en el esquema de la base de datos.


Crear una migración:


mix ecto.gen.migration crea_usuarios


Editar la migración:


defmodule MiApp.Repo.Migrations.CreaUsuarios do

  use Ecto.Migration


  def change do

    create table(:usuarios) do

      add :nombre, :string

      add :email, :string

      add :edad, :integer

      timestamps()

    end

  end

end


Ejecutar migración:

mix ecto.migrate


Ecto transforma la interacción con bases de datos en una experiencia declarativa, segura y extensible. Ya sea que estés manejando datos simples o esquemas complejos, Ecto te da las herramientas para hacerlo de manera eficiente.


domingo, 1 de diciembre de 2024

Creación de pruebas en Elixir con ExUnit


ExUnit es el framework de pruebas integrado en Elixir, diseñado para ayudarte a escribir pruebas claras y efectivas. Desde pruebas unitarias hasta pruebas más complejas, ExUnit ofrece las herramientas necesarias para asegurar que tu código funcione como esperas.

ExUnit viene incluido con Elixir, por lo que no necesitas instalar dependencias adicionales. Solo asegúrate de que tu entorno de desarrollo esté configurado para ejecutarlas.


ExUnit.start()


Los archivos de pruebas suelen estar en el directorio test/ y deben tener el sufijo _test.exs.


Veamos un ejemplo: 


defmodule MiApp.MiModuloTest do

  use ExUnit.Case


  test "una prueba simple" do

    assert 1 + 1 == 2

  end

end


Para ejecutar las pruebas, utiliza:


mix test


Las aserciones son fundamentales para comprobar el comportamiento esperado. ExUnit ofrece varias:


- assert: Verifica que una condición sea verdadera.

- refute: Verifica que una condición sea falsa.

- assert_raise: Verifica que se lance una excepción específica.


Veamos algunos ejemplos: 


assert String.length("Hola") == 4

refute String.contains?("Hola", "mundo")

assert_raise ArgumentError, fn -> String.to_integer("no_numero") end


ExUnit permite convertir ejemplos en la documentación en pruebas automáticas.


defmodule MiModulo do

  @doc """

  Duplica un número.


  ## Ejemplo


      iex> MiModulo.duplicar(2)

      4


  """

  def duplicar(n), do: n * 2

end


defmodule MiModuloTest do

  use ExUnit.Case

  doctest MiModulo

end


Para organizar tus pruebas, podemos usar describe:


defmodule MiApp.MiModuloTest do

  use ExUnit.Case


  describe "función suma/2" do

    test "suma números positivos" do

      assert MiApp.MiModulo.suma(2, 3) == 5

    end


    test "suma números negativos" do

      assert MiApp.MiModulo.suma(-2, -3) == -5

    end

  end

end


Y podemos usar setup para definir configuraciones comunes:


defmodule MiApp.MiModuloTest do

  use ExUnit.Case


  setup do

    {:ok, numero: 42}

  end


  test "usa datos de setup", %{numero: numero} do

    assert numero == 42

  end

end


Elixir soporta pruebas concurrentes por defecto. Si necesitas pruebas asincrónicas, podemos indícarlo:


defmodule MiApp.AsyncTest do

  use ExUnit.Case, async: true


  test "prueba concurrente" do

    Task.async(fn -> :ok end)

    |> Task.await()

    |> assert == :ok

  end

end


ExUnit facilita capturar salidas a consola y logs:


import ExUnit.CaptureIO


test "captura salida de IO.puts" do

  salida = capture_io(fn -> IO.puts("Hola, mundo!") end)

  assert salida == "Hola, mundo!\n"

end


import ExUnit.CaptureLog


test "captura logs" do

  salida = capture_log(fn -> Logger.info("Esto es un log") end)

  assert salida =~ "Esto es un log"

end


Para ejecutar pruebas individuales, podemos usar:


mix test test/mi_modulo_test.exs:10


ExUnit es una herramienta flexible y poderosa para escribir pruebas en Elixir. Desde las pruebas más básicas hasta configuraciones avanzadas, te ayuda a mantener un código confiable y fácil de mantener. 


domingo, 24 de noviembre de 2024

Macros en Elixir


Elixir no solo es conocido por su modelo de concurrencia y eficiencia, sino también por su poderosa capacidad de metaprogramación, gracias a sus macros. Con ellas, puedes escribir código que genera más código, permitiendo crear herramientas, DSLs (lenguajes específicos de dominio) y extender el lenguaje.

En Elixir, las macros son funciones especiales que reciben expresiones y devuelven otras expresiones transformadas. Estas transformaciones ocurren en tiempo de compilación, lo que las diferencia de las funciones normales.


defmodule EjemploMacro do

  defmacro saludo do

    quote do

      IO.puts("¡Hola, mundo!")

    end

  end

end


require EjemploMacro

EjemploMacro.saludo()

# Salida: ¡Hola, mundo!


¿Cómo funciona esto?

  • quote/2: Captura código como datos. Convierte un bloque en su representación AST (Abstract Syntax Tree).
  • unquote/1: Inserta valores dinámicos en el código generado.
  • defmacro: Declara macros.


Imagina que necesitas loguear todas las llamadas a funciones en un módulo. Con macros, puedes automatizar este proceso:


defmodule LoggerMacro do

  defmacro log_call(fun_name, do: block) do

    quote do

      IO.puts("Llamando a #{unquote(fun_name)}")

      result = unquote(block)

      IO.puts("Resultado: #{result}")

      result

    end

  end

end


defmodule Ejemplo do

  require LoggerMacro


  def calcular do

    LoggerMacro.log_call(:calcular) do

      1 + 2

    end

  end

end


Ejemplo.calcular()

# Salida:

# Llamando a calcular

# Resultado: 3


Los macros son herramientas poderosas para crear DSLs. Por ejemplo, un DSL para rutas web (similar a Plug.Router):


defmodule MiniRouter do

  defmacro route(method, path, do: block) do

    quote do

      def handle_request(unquote(method), unquote(path)) do

        unquote(block)

      end

    end

  end

end


defmodule MyRouter do

  require MiniRouter


  MiniRouter.route(:get, "/") do

    IO.puts("¡Bienvenido!")

  end


  MiniRouter.route(:post, "/create") do

    IO.puts("¡Creando recurso!")

  end

end


MyRouter.handle_request(:get, "/")

# Salida: ¡Bienvenido!


Aunque las macros son útiles, pueden complicar el código:

  • Usa macros solo si la lógica no puede resolverse con funciones.
  • Opta por composición funcional siempre que sea posible.


Las macros en Elixir son una herramienta increíblemente poderosa para extender el lenguaje y crear soluciones elegantes. Sin embargo, requieren un buen entendimiento del AST y un uso responsable. 

miércoles, 20 de noviembre de 2024

Introducción a Plug de Elixir


Elixir, con su enfoque funcional y capacidad para manejar concurrencia de manera eficiente, es un lenguaje ideal para aplicaciones web. Si bien Phoenix es el framework más conocido, Plug es una alternativa minimalista perfecta para quienes buscan simplicidad y flexibilidad, similar a lo que Sinatra ofrece en Ruby.

Plug es un conjunto de especificaciones y módulos para construir aplicaciones web en Elixir. Se centra en el manejo de conexiones HTTP y proporciona herramientas básicas para construir rutas y middlewares.

  • Minimalista: Perfecto para aplicaciones simples o como base para proyectos más grandes.
  • Rápido: Aprovecha Cowboy, un servidor HTTP eficiente.
  • Flexible: Puedes usarlo directamente o integrarlo en frameworks más grandes como Phoenix.

Para comenzar, necesitas agregar Plug y Cowboy a tu proyecto. En el archivo `mix.exs`:

Antes creas el proyecto : 

mix new hello_world

Y Luego editamos mix.exs


defp deps do

  [

    {:plug, "~> 1.13"},

    {:plug_cowboy, "~> 2.6"}

  ]

end


Ejecuta mix deps.get para instalar las dependencias.

Para comenzar a crear Plugs, necesitamos conocer, y adherirse a la especificación Plug. Afortunadamente para nosotros, sólo hay dos funciones necesarias: init/1 y call/2.


Aquí hay un Plug simple que devuelve “Hello World!”:


defmodule Example.HelloWorldPlug do

  import Plug.Conn


  def init(options), do: options


  def call(conn, _opts) do

    conn

    |> put_resp_content_type("text/plain")

    |> send_resp(200, "Hello World!")

  end

end

Guarda el archivo en lib/example/hello_world_plug.ex.

La función init/1 se utiliza para inicializar las opciones de nuestros Plugs. Esta es llamada por el árbol de supervisión. De momento, está será una lista vacía que es ignorada.

El valor retornado por la función init/1 eventualmente será pasado a call/2 como su segundo argumento.

La función call/2 es ejecutada por cada petición que viene desde el servidor web, Cowboy. Esta recibe una estructura de conexión %Plug.Conn{} como su primer argumento y se espera que retorne una estructura de conexión %Plug.Conn{}.

Debido a que estamos iniciando nuestra aplicación plug desde cero, necesitamos definir el módulo de la aplicación. Actualiza lib/example.ex para iniciar y supervisar Cowboy:


defmodule Example do

  use Application

  require Logger


  def start(_type, _args) do

    children = [

      Plug.Adapters.Cowboy.child_spec(:http, Example.HelloWorldPlug, [], port: 8080)

    ]


    Logger.info("Started application")


    Supervisor.start_link(children, strategy: :one_for_one)

  end

end


Esto supervisa Cowboy, y a su vez, supervisa nuestro HelloWorldPlug.

En la petición a Plug.Adapters.Cowboy.child_spec/4, el tercer argumento será pasado a Example.HelloWorldPlug.init/1.

Aún no hemos terminado. Abre mix.exs de nuevo, y busca la función applications. De momento la parte de aplication en mix.exs necesita dos cosas:

  • Una lista de aplicaciones de dependencia (cowboy, logger, and plug) que necesintan iniciar, y
  • Configuración para nuestra aplicación, la cual también deberá iniciar automáticamente. Vamos a actualizarla para hacerlo:

def application do

  [

    extra_applications: [:cowboy, :logger, :plug],

    mod: {Example, []}

  ]

end

Estamos listos para probar este servidor web, minimalístico basado en Plug. En la línea de comando ejecuta:

mix run --no-halt

Cuando todo termine de compilar, y el mensaje [info] Started app aparece, abre el explorador web en 127.0.0.1:8080. Este debera de desplegar:

Hello World!

Plug es ideal para quienes buscan simplicidad y control en sus aplicaciones web. Su integración con Cowboy y su flexibilidad lo hacen una excelente opción para proyectos ligeros, APIs o como base para aprender Elixir.

Dejo link: https://elixirschool.com/es/lessons/misc/plug

lunes, 18 de noviembre de 2024

Elixir: Concurrencia Hecha Sencilla


Elixir, basado en la máquina virtual de Erlang (BEAM), utiliza el modelo de actores como su paradigma de concurrencia. Este modelo permite manejar múltiples procesos de manera eficiente y segura, lo que lo hace ideal para sistemas distribuidos y concurrentes.

El modelo de actores es un paradigma de concurrencia en el que las entidades llamadas actores:

  • Son unidades independientes de ejecución.
  • Tienen su propio estado y no comparten memoria con otros actores.
  • Se comunican mediante el envío de mensajes.

En Elixir, los procesos son implementaciones del modelo de actores y son extremadamente ligeros gracias a la eficiencia de la VM de Erlang.

En Elixir, los actores se implementan utilizando módulos como GenServer. Vamos a crear un ejemplo básico de un contador que incrementa su valor en respuesta a mensajes.

   defmodule Counter do

     use GenServer


     # Inicio del actor con un estado inicial

     def start_link(initial_value) do

       GenServer.start_link(__MODULE__, initial_value, name: __MODULE__)

     end


     # Callbacks

     def init(initial_value) do

       {:ok, initial_value}

     end


     # Manejar el mensaje para incrementar el contador

     def handle_call(:increment, _from, state) do

       {:reply, state + 1, state + 1}

     end


     def handle_call(:get, _from, state) do

       {:reply, state, state}

     end

   end


   # Iniciar el actor con un valor inicial de 0

   {:ok, _pid} = Counter.start_link(0)


   # Incrementar el contador

   Counter.call(:increment) # Devuelve 1

   Counter.call(:increment) # Devuelve 2


   # Obtener el valor actual

   Counter.call(:get) # Devuelve 2


Ahora crearemos múltiples actores que se comuniquen entre sí. Supongamos que tenemos un sistema donde un actor recopila datos y otro los procesa.


   defmodule DataCollector do

     use GenServer


     def start_link(processor_pid) do

       GenServer.start_link(__MODULE__, processor_pid, name: __MODULE__)

     end


     def init(processor_pid) do

       {:ok, processor_pid}

     end


     def handle_cast({:collect, data}, processor_pid) do

       send(processor_pid, {:process, data})

       {:noreply, processor_pid}

     end

   end

 

   defmodule DataProcessor do

     use GenServer


     def start_link(_) do

       GenServer.start_link(__MODULE__, [], name: __MODULE__)

     end


     def init(_) do

       {:ok, []}

     end


     def handle_info({:process, data}, state) do

       IO.puts("Procesando: #{data}")

       {:noreply, [data | state]}

     end

   end


   {:ok, processor_pid} = DataProcessor.start_link([])

   {:ok, collector_pid} = DataCollector.start_link(processor_pid)


   GenServer.cast(collector_pid, {:collect, "dato1"})

   GenServer.cast(collector_pid, {:collect, "dato2"})


DataCollector recopila datos y los envía a DataProcessor. DataProcessor procesa los datos recibidos y los guarda en su estado.


Como ventaja del modelo de actores tenemos:

Aislamiento Total: Los actores no comparten memoria, eliminando condiciones de carrera.

Escalabilidad: Los procesos son livianos y se ejecutan de manera concurrente.

Resiliencia: Si un actor falla, el sistema no se detiene; los supervisores pueden reiniciarlo.


El modelo de actores de Elixir proporciona una forma poderosa, segura y eficiente de manejar concurrencia. Al entender cómo implementar actores y supervisarlos, puedes construir sistemas robustos que escalen sin problemas.

sábado, 16 de noviembre de 2024

Primeros pasos en Pony


Pony es un lenguaje de programación orientado a objetos, actor-modelo, capacidades seguras. Es orientado a objetos porque tiene clases y objetos, como Python, Java, C++ y muchos otros lenguajes. Es actor-modelo porque tiene actores (similares a Erlang, Elixir o Akka). Estos se comportan como objetos, pero también pueden ejecutar código de forma asincrónica. Los actores hacen que Pony sea increíble.

Cuando decimos que Pony es seguro en cuanto a capacidades, nos referimos a algunas cosas:

  • Es seguro en cuanto a tipos. Realmente seguro en cuanto a tipos. 
  • Es seguro en cuanto a memoria. Esto viene con seguridad en cuanto a tipos, pero sigue siendo interesante. No hay punteros, ni desbordamientos de búfer, ni siquiera tiene el concepto de nulo.
  • Es seguro en cuanto a excepciones. No hay excepciones en tiempo de ejecución. Todas las “situaciones excepcionales” tienen una semántica definida y siempre se manejan.
  • No tiene carreras de datos. Pony no tiene bloqueos ni operaciones atómicas ni nada parecido. En cambio, el sistema de tipos garantiza en tiempo de compilación que su programa concurrente nunca pueda tener carreras de datos. Así que puede escribir código altamente concurrente y nunca equivocarse.
  • No tiene interbloqueos. Esto es fácil, Pony no tiene bloqueos en absoluto. Así que definitivamente no se bloquean, porque no existen.

Pony no puede impedir que se escriba errores lógicos, pero puede evitar que clases enteras de errores sean posibles. El compilador Pony evita que accedas a la memoria de forma insegura de forma concurrente. Si alguna vez has hecho programación concurrente, sabes lo difícil que puede ser rastrear este tipo de cosas. 

domingo, 10 de noviembre de 2024

Pony: Un Lenguaje de Programación basado en actores

 


En un mundo donde la concurrencia y el rendimiento son esenciales, el lenguaje de programación Pony emerge como una solución innovadora que permite escribir aplicaciones concurrentes de manera segura y eficiente. Pony combina paradigmas de actor y tipos de referencia para manejar múltiples tareas sin necesidad de bloqueos ni condiciones de carrera. 

Pony es un lenguaje de programación orientado a actores, diseñado para ofrecer concurrencia segura sin caer en los problemas típicos de bloqueos y condiciones de carrera. Fue creado por Sylvan Clebsch y otros investigadores de la Universidad de Cambridge y se enfoca en aplicaciones que requieren alta concurrencia y bajo tiempo de respuesta.

Algunas características clave de Pony incluyen:

- Concurrencia sin bloqueos: Pony utiliza un sistema de tipos de referencia que garantiza que el código concurrente no genere condiciones de carrera.

- Recolección de basura libre de pausas: Su recolección de basura está diseñada para ser concurrente y no requiere detener la ejecución de los programas.

- Orientación a actores: Al igual que Elixir y Erlang, Pony usa actores como unidades de concurrencia.

El sistema de tipos de referencia en Pony es lo que permite su concurrencia segura. Existen cinco tipos de referencia principales en Pony:

  • iso: Garantiza que solo hay una referencia a un valor.
  • rn: Representa una referencia transitoria que puede transferirse entre actores.
  • val: Un valor inmutable que puede ser compartido de manera segura.
  • ref: Una referencia mutable exclusiva de un actor.
  • box: Una referencia de solo lectura.

Al definir el tipo de referencia de cada variable, Pony asegura que los datos compartidos entre actores no puedan ser modificados de manera insegura.

Aquí tienes un ejemplo básico que demuestra el uso de actores en Pony. En este ejemplo, crearemos un actor llamado `Counter` que cuenta hasta un valor especificado y luego envía un mensaje de notificación.


actor Counter

  var count: U32

  var target: U32

  let notify: Notifier


  new create(target: U32, notify: Notifier) =>

    count = 0

    this.target = target

    this.notify = notify


  be increment() =>

    count = count + 1

    if count >= target then

      notify.done()

    end

end


actor Notifier

  be done() =>

    @println[I32]("Contador alcanzó el objetivo!".cstring())


En este código:

  1. El actor Counter tiene una función increment que aumenta el contador.
  2. Cuando el contador llega al objetivo, se llama al método done del actor Notifier.


Pony es un lenguaje único en el ecosistema de lenguajes concurrentes. Con su sistema de tipos de referencia y su enfoque en actores, Pony proporciona un marco robusto para aplicaciones concurrentes y distribuidas. Aunque es relativamente nuevo, su propuesta de concurrencia segura y recolección de basura eficiente lo convierten en una opción interesante para desarrolladores que buscan maximizar el rendimiento sin comprometer la seguridad.

Dejo link: https://www.ponylang.io/

miércoles, 6 de noviembre de 2024

Supervisores y Árboles de Supervisión en Elixir


La concurrencia es uno de los puntos fuertes de Elixir, y el modelo de supervisión es fundamental para construir aplicaciones resilientes. 

El modelo de concurrencia en Elixir está basado en procesos ligeros y aislados que se comunican entre sí enviándose mensajes, siguiendo el paradigma del modelo de actor. Estos procesos no comparten memoria, lo que reduce los riesgos de condiciones de carrera y hace que el sistema sea más seguro.

Supervisores son procesos especiales en Elixir que gestionan y supervisan otros procesos, reiniciándolos si fallan. Esto asegura que la aplicación siga funcionando incluso cuando ocurren errores.

Elixir provee módulos como Supervisor y Task.Supervisor para gestionar procesos de manera eficiente. Vamos a crear un simple supervisor que inicie y supervise un proceso Worker.


Primero, definimos el proceso Worker usando GenServer:


defmodule Worker do

  use GenServer


  def start_link(initial_state) do

    GenServer.start_link(__MODULE__, initial_state, name: __MODULE__)

  end


  def init(state), do: {:ok, state}


  def handle_cast(:do_work, state) do

    # Simulamos un trabajo que puede fallar

    if :rand.uniform() > 0.5 do

      {:stop, :error, state}

    else

      IO.puts("Trabajo completado exitosamente")

      {:noreply, state}

    end

  end

end


Ahora, creamos un supervisor que inicie y supervise nuestro proceso Worker:


defmodule MySupervisor do

  use Supervisor


  def start_link(_opts) do

    Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)

  end


  def init(:ok) do

    children = [

      {Worker, :some_initial_state}

    ]


    Supervisor.init(children, strategy: :one_for_one)

  end

end


Elixir ofrece varias estrategias de supervisión que permiten diferentes enfoques de recuperación en caso de fallo:


  • :one_for_one: Solo reinicia el proceso que falló.
  • :one_for_all: Reinicia todos los procesos supervisados si uno falla.
  • :rest_for_one: Reinicia el proceso que falló y todos los procesos que fueron iniciados después de este.
  • :simple_one_for_one: Útil para supervisar un número dinámico de procesos que comparten el mismo tipo de inicialización.


En el ejemplo anterior, usamos :one_for_one, que reiniciará únicamente el proceso Worker si falla.

Los árboles de supervisión permiten organizar supervisores en una jerarquía. Esto es ideal para sistemas complejos que necesitan distintos niveles de supervisión.

Por ejemplo, podríamos tener un supervisor principal que supervise varios supervisores secundarios, cada uno a cargo de procesos específicos. Aquí hay una estructura básica:


defmodule MainSupervisor do

  use Supervisor


  def start_link(_opts) do

    Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)

  end


  def init(:ok) do

    children = [

      MySupervisor, # Supervisor de workers

      AnotherSupervisor # Otro supervisor

    ]


    Supervisor.init(children, strategy: :one_for_all)

  end

end


En esta configuración:

  • MainSupervisor es el supervisor principal.
  • MySupervisor y AnotherSupervisor son supervisores secundarios.
  • Si cualquiera de los supervisores secundarios falla, MainSupervisor los reiniciará junto con sus respectivos procesos.


Los supervisores y árboles de supervisión en Elixir son una herramienta poderosa para construir sistemas concurrentes y resilientes. Siguiendo estos patrones, puedes crear aplicaciones que se recuperen automáticamente de fallos sin necesidad de intervención manual. Esto convierte a Elixir en una excelente opción para aplicaciones que requieren alta disponibilidad.

domingo, 3 de noviembre de 2024

Monadex en Elixir


Elixir, aunque no tiene una implementación nativa de mónadas como Haskell, permite patrones funcionales que se pueden potenciar con bibliotecas como Monadex. Monadex nos brinda las clásicas Maybe y Either, permitiéndonos manejar valores opcionales y errores de forma compositiva, y aprovechando el poder de la programación funcional.

Primero, agrega Monadex a tu archivo mix.exs:

defp deps do

  [

    {:monadex, "~> 1.1"}

  ]

end


Ejecuta mix deps.get para instalar la dependencia.


La mónada Maybe es útil cuando deseas trabajar con valores que podrían ser nil. Monadex provee funciones para encapsular operaciones en un flujo que se detiene automáticamente si algún valor es nil.

Supongamos que estamos buscando datos de un usuario y aplicando transformaciones a su información.


alias Monadex.Maybe


def get_user_info(user) do

  Maybe.return(user)

  |> Maybe.and_then(&get_name/1)

  |> Maybe.and_then(&upcase_name/1)

end


defp get_name(%{name: name}), do: Maybe.return(name)

defp get_name(_), do: Maybe.nothing()


defp upcase_name(name), do: Maybe.return(String.upcase(name))


user = %{name: "Juan"}

get_user_info(user) # Retorna: {:ok, "JUAN"}


Either es útil para operaciones que pueden tener éxito o devolver un error. Similar a Maybe, Either permite encadenar operaciones pero distingue entre :ok y :error.


Procesemos datos que pueden fallar en algún paso.


alias Monadex.Either


def process_data(data) do

  Either.return(data)

  |> Either.and_then(&validate/1)

  |> Either.and_then(&transform/1)

end


defp validate(data) do

  if valid?(data), do: Either.ok(data), else: Either.error("Invalid data")

end


defp transform(data), do: Either.ok("Processed: #{data}")


process_data("valid_data")  # Retorna: {:ok, "Processed: valid_data"}

process_data("invalid")      # Retorna: {:error, "Invalid data"}


Monadex facilita el manejo de errores y valores opcionales en Elixir, siguiendo patrones funcionales.

Dejo link: https://github.com/rob-brown/MonadEx


lunes, 28 de octubre de 2024

Quicksort en Elixir


Un Algoritmo que me gusta mucho es el quicksort, porque es un algoritmo por demás claro. Ya he escrito lo fácil que es implementarlo en scalaerlangrusthaskell y lisp.

Ahora le toca a Elixir. Básicamente el algoritmo toma un pivot y agrupa los menores del pivot al principio y los mayores al final y aplica quicksort a estos 2 grupos. Y si la lista es vacia o tiene un elemento, ya esta ordenada. 

Vamos al código: 


defmodule QuickSort do

  def sort([]), do: []

  def sort([pivot | tail]) do

    lower = Enum.filter(tail, &(&1 <= pivot))

    higher = Enum.filter(tail, &(&1 > pivot))


    sort(lower) ++ [pivot] ++ sort(higher)

  end

end


martes, 30 de abril de 2024

Queres probar código y no queres instalar nada? paiza.io


Queres probar código y no queres instalar nada? 

Bueno podes utilizar paiza. Que es una pagina donde podemos correr código en diferentes lenguajes: 

Bash, C, C#, C++, Clojure, Cobol, CoffeeScript, D, Elixir, Erlang, F#, Go, Haskell, Java, JavaScript, Kotlin, MySQL, Nadesiko, Objective-C, Perl, PHP, Python2, Python3, R, Ruby, Rust, Scala, Scheme, Swift, TypeScript, VB


Dejo link: https://paiza.io/

lunes, 5 de junio de 2023

Mi experiencia con la programación funcional en Scala


Creo que si leen este blog saben que me encanta la programación funcional y investigar y aprender. Y en eso estoy...


Me gustaría contar un poco mi evolución y mis altibajos para que la gente que sabe me aconseje y los que no saben puedan aprender de mis exitos y derrotas. 


Bueno, todo empezó con un curso de scala de coursera y quede encantado con el lenguaje y la programación funcional. De a poco tambien me interiorice en otros lenguajes como erlang, haskell, Clojure, Elixir, F#, etc ... pero siempre en la volvía a Scala por gusto nomas. Y me iba bastante bien...


Hasta que empece a quedarme corto en scala, quería progresar y decidí ver algunos framework funcionales, leí un libro de Cats y la verdad es que si bien esta bueno, me frustre mucho... porque no sabia porque se hacían ciertas cosas, no entendía porque tan complejo o si bien entendía desconfiaba ( no hay otra forma de hacerlo más fácil) y bueno ... 


Cambie a Akka, y me pareció bueno y me sentí más en mi mundo, pero justo justo cuando volvia el entuciasmo, cambio de licencia y bueno... Se fue el entusiasmo, ahora sé que existe un fork open source llamado pekko (https://github.com/apache/incubator-pekko) y es de apache. Pero lo veo verde, y no sé cuanto va a crecer ...


Y ahora estoy con ZIO y por ahora todo va bien, por ende la conclusión es si quieren progresar en su conocimiento en Scala, y no vienen del mundo funcional como Haskell, ZIO esta bueno. 

Si son nuevos en el mundo funcional y scala, yo haría lo siguiente: 

1. Estudiar Scala (curso de scala de coursera, el libro rojo de programación funcional en scala, etc ...)

2. Leer y estudiar ZIO 

3. Leer y estudiar Cats


Pero no perder de vista Pekko, para ver como progresa. 


Que opinan? 


jueves, 6 de abril de 2023

Primeros pasos con Phoenix parte 7

 


Agreguemos un poco de complejidad a nuestra aplicación. Vamos a agregar una nueva página que reconocerá una parte de la URL, la etiquetará como "mensajero" y la pasará a través del controlador a la plantilla para que nuestro mensajero pueda saludar.

Como hicimos la última vez, lo primero que haremos será crear una nueva ruta.

Vamos a reutilizar HelloController creado en el post anterior y agregaremos una nueva acción de mostrar. Agregaremos una línea justo debajo de nuestra última ruta, así:


scope "/", HelloWeb do

  pipe_through :browser

  get "/", PageController, :home

  get "/hello", HelloController, :index

  get "/hello/:messenger", HelloController, :show

end


Usamos la sintaxis :messenger en la ruta. Phoenix tomará cualquier valor que aparezca en esa posición en la URL y lo convertirá en un parámetro. Por ejemplo, si apuntamos el navegador a: http://localhost:4000/hello/Frank, el valor de "messenger" será "Frank".

Las solicitudes a nuestra nueva ruta serán manejadas por la acción show HelloWeb.HelloController. Ya tenemos el controlador en lib/hello_web/controllers/hello_controller.ex, así que todo lo que tenemos que hacer es editar ese controlador y agregarle una acción show. Esta vez, necesitaremos extraer el messenger de los parámetros para que podamos pasarlo a la plantilla. Para hacer eso, agregamos esta función show al controlador:

def show(conn, %{"messenger" => messenger}) do

  render(conn, :show, messenger: messenger)

end


Dentro del cuerpo de la acción show, también pasamos un tercer argumento a la función render, un par clave-valor donde :messenger es la clave, y la variable messenger se pasa como el valor.

Si el cuerpo de la acción necesita acceso al mapa completo de parámetros enlazados a la variable params, además de la variable messenger enlazada, podríamos definir show/2 así:


def show(conn, %{"messenger" => messenger} = params) do

  ...

end


Es bueno recordar que las claves del mapa de parámetros siempre serán cadenas, y que el signo igual no representa una asignación, sino una afirmación de coincidencia de patrón.

Ahora necesitaremos una nueva plantilla. Dado que es para la acción de mostrar de HelloController, irá al directorio lib/hello_web/controllers/hello_html y se llamará show.html.heex. Se parecerá sorprendentemente a nuestra plantilla index.html.heex, excepto que necesitaremos mostrar el nombre de nuestro mensajero.

Para hacer eso, usaremos las etiquetas HEEx especiales para ejecutar expresiones Elixir: <%= %>. Observe que la etiqueta inicial tiene un signo igual como este: <%= . Eso significa que se ejecutará cualquier código de Elixir que se encuentre entre esas etiquetas, y el valor resultante reemplazará la etiqueta en la salida HTML. Si faltara el signo igual, el código aún se ejecutaría, pero el valor no aparecería en la página.

Nuestras plantillas están escritas en HEEx (HTML+EEx). HEEx es un superconjunto de EEx, por lo que comparte la sintaxis <%= %>.


Y así es como debería verse la plantilla:


<section>

  <h2>Hello World, from <%= @messenger %>!</h2>

</section>


Nuestro messenger aparece como @messenger.

Los valores que pasamos a la vista desde el controlador se denominan colectivamente nuestras "asignaciones". Podríamos acceder a nuestro valor de mensajería a través de asignaciones.messenger pero a través de alguna metaprogramación, Phoenix nos brinda la sintaxis @ mucho más limpia para usar en plantillas.

Si vamos a http://localhost:4000/hello/emanuel, debería ver una página similar a esta:

Hello World, from emanuel!

jueves, 30 de marzo de 2023

Primeros pasos con Phoenix parte 5


Cuando su navegador accede a http://localhost:4000/, envía una solicitud HTTP a cualquier servicio que se esté ejecutando en esa dirección, en este caso, nuestra aplicación Phoenix. La solicitud HTTP se compone de un verbo y una ruta. Por ejemplo, las siguientes solicitudes del navegador se traducen en:

BROWSER ADDRESS             VERBO PATH

http://localhost:4000/                 GET /

http://localhost:4000/hello         GET /hello

http://localhost:4000/hello/world GET /hello/world


Hay otros verbos HTTP. Por ejemplo, enviar un formulario generalmente usa el verbo POST.

Las aplicaciones web generalmente manejan las solicitudes asignando cada par de verbo/ruta a una parte específica de su código y esto lo hace el router. Por ejemplo, podemos asignar "/artículos" a una parte de nuestra aplicación que muestra todos los artículos. Por lo tanto, para agregar esto, nuestra primera tarea es agregar una nueva ruta.

El router asigna pares únicos de verbo/ruta HTTP a pares de controlador/acción que los manejarán. Los controladores en Phoenix son simplemente módulos Elixir. Las acciones son funciones que se definen dentro de estos controladores.

Phoenix genera un archivo de router para nosotros en lib/hello_web/router.ex. 

La ruta de nuestro "¡Bienvenido a Phoenix!"  tiene este aspecto.


    get "/", PageController, :home


Si ponemos  en el browser http://localhost:4000/ estamos llamando a / con el metodo GET por lo tanto será manejada por la función home en el módulo HelloWeb.PageController definido en lib/hello_web/controllers/page_controller.ex.

La página que vamos a construir dirá "¡Hola mundo, desde Phoenix!" cuando apuntamos nuestro navegador a http://localhost:4000/hello.

Lo primero que debemos hacer es crear la ruta de la página para una nueva página. Abramos lib/hello_web/router.ex en un editor de texto. 

Agreguemos una nueva ruta al enrutador que asigna una solicitud GET para /hello a la acción de índice de un HelloWeb.HelloController que se creará próximamente dentro del bloque do scope "/" del enrutador:


scope "/", HelloWeb do
  pipe_through :browser

  get "/", PageController, :home
  get "/hello", HelloController, :index
end


Los controladores son módulos de Elixir y las acciones son funciones de Elixir definidas en ellos. El propósito de las acciones es recopilar los datos y realizar las tareas necesarias para la representación. Nuestra ruta especifica que necesitamos un módulo HelloWeb.HelloController con una función index/2.

Para hacer que suceda la acción de índice, creemos un nuevo archivo lib/hello_web/controllers/hello_controller.ex y hagamos que tenga el siguiente aspecto:

defmodule HelloWorldWeb.HelloController do
  use HelloWorldWeb, :controller

  def index(conn, _params) do
    render(conn, :index)
  end
end


Todas las acciones del controlador toman dos argumentos. El primero es conn, una estructura que contiene una gran cantidad de datos sobre la solicitud. El segundo es params, que son los parámetros de solicitud. .

El núcleo de esta acción es render(conn, :index). Le dice a Phoenix que renderice la plantilla de índice. Los módulos responsables de renderizar se denominan vistas. De forma predeterminada, las vistas de Phoenix llevan el nombre del controlador (HelloController) y el formato (HTML en este caso), por lo que Phoenix espera que exista un HelloWeb.HelloHTML y defina una función index/1.

Las vistas de Phoenix actúan como capa de presentación. Por ejemplo, esperamos que el resultado del índice de representación sea una página HTML completa. Para hacernos la vida más fácil, a menudo usamos plantillas para crear esas páginas HTML.

Vamos a crear una nueva vista. Cree lib/hello_web/controllers/hello_html.ex y haga que se vea así:

defmodule HelloWorldWeb.HelloHTML do
  use HelloWorldWeb, :html
end

Para agregar plantillas a esta vista, podemos definirlas como componentes de función en el módulo o en archivos separados.

Comencemos definiendo un componente de función:

defmodule HelloWorldWeb.HelloHTML do
  use HelloWorldWeb, :html

  def index(assigns) do
    ~H"""
    Hello!
    """
  end
end

Definimos una función que recibe asignaciones como argumentos y usamos el sigilo ~H para poner los contenidos que queremos representar. Dentro del sigilo ~H, usamos un lenguaje de plantillas llamado HEEx, que significa "HTML+EEx". EEx es una biblioteca para incrustar Elixir que se distribuye como parte del propio Elixir. "HTML+EEx" es una extensión Phoenix de EEx compatible con HTML, con soporte para validación HTML. Este último lo protege de vulnerabilidades de seguridad como Cross-Site-Scripting sin trabajo adicional de su parte.

Un archivo de plantilla funciona de la misma manera. Los componentes de función son excelentes para plantillas más pequeñas y los archivos separados son una buena opción cuando tiene mucho marcado o sus funciones comienzan a parecer inmanejables.

Probémoslo definiendo una plantilla en su propio archivo. Primero elimine nuestra función def index(assigns) de arriba y reemplácela con una declaración embed_templates:

defmodule HelloWorldWeb.HelloHTML do
  use HelloWorldWeb, :html

  embed_templates "hello_html/*"
end


Aquí le estamos diciendo a Phoenix.Component que incruste todas las plantillas .heex que se encuentran en el directorio hermano hello_html en nuestro módulo como definiciones de funciones.

A continuación, debemos agregar archivos al directorio lib/hello_web/controllers/hello_html.

Tenga en cuenta que el nombre del controlador (HelloController), el nombre de la vista (HelloHTML) y el directorio de la plantilla (hello_html) siguen la misma convención de nomenclatura y se nombran uno tras otro. También se colocan juntos en el árbol de directorios:


lib/hello_web
├── controllers
│   ├── hello_controller.ex
│   ├── hello_html.ex
│   ├── hello_html
 |         ├── index.html.heex

Un archivo de plantilla tiene la siguiente estructura: NOMBRE.FORMATO.TEMPLATING_LANGUAGE. En nuestro caso, creemos un archivo index.html.heex en lib/hello_web/controllers/hello_html/index.html.heex:

<section>
  <h2>Hello World, from Phoenix!</h2>
</section>

Los archivos de plantilla se compilan en el módulo como componentes de función en sí mismos, no hay diferencia de tiempo de ejecución o rendimiento entre los dos estilos.

Ahora que tenemos la ruta, el controlador, la vista y la plantilla, deberíamos poder dirigir nuestros navegadores a http://localhost:4000/hello.

Para reiniciar el server hacemos : 

mix phx.server

viernes, 24 de marzo de 2023

Primeros pasos con Phoenix parte 4


El directorio lib/hello_web contiene las partes relacionadas con la web de nuestra aplicación. Se ve así cuando se expande:

lib/hello_web

├── controllers

│   ├── page_controller.ex

│   ├── page_html.ex

│   ├── error_html.ex

│   ├── error_json.ex

│   └── page_html

│       └── home.html.heex

├── components

│   ├── core_components.ex

│   ├── layouts.ex

│   └── layouts

│       ├── app.html.heex

│       └── root.html.heex

├── endpoint.ex

├── gettext.ex

├── router.ex

└── telemetry.ex


Todos los archivos que se encuentran actualmente en los directorios de controladores y componentes están ahí para crear el mensaje "¡Bienvenido a Phoenix!" página que vimos en http://localhost:4000/

Al observar los directorios de controladores y componentes, podemos ver que Phoenix proporciona funciones para manejar diseños y HTML y páginas de error listas para usar.

Además de los directorios mencionados, lib/hello_web tiene cuatro archivos en su raíz. lib/hello_web/endpoint.ex es el punto de entrada para las solicitudes HTTP. Una vez que el navegador accede a http://localhost:4000, el punto final comienza a procesar los datos, lo que finalmente conduce al enrutador, que se define en lib/hello_web/router.ex. El enrutador define las reglas para enviar solicitudes a los "controladores", que llama a un módulo de vista para devolver las páginas HTML a los clientes. 

A través de Telemetry, Phoenix puede recopilar métricas y enviar eventos de monitoreo de su aplicación. El archivo lib/hello_web/telemetry.ex define al supervisor responsable de administrar los procesos de telemetría. 

Finalmente, hay un archivo lib/hello_web/gettext.ex que proporciona internacionalización a través de Gettext. Si no le preocupa la internacionalización, puede omitir con seguridad este archivo y su contenido.

El directorio de assert contiene archivos de origen relacionados con los activos front-end, como JavaScript y CSS. Desde Phoenix v1.6, usamos esbuild para compilar activos, que es administrado por el paquete esbuild Elixir. La integración con esbuild está integrada en su aplicación. La configuración relevante se puede encontrar en su archivo config/config.exs.

Sus otros activos estáticos se colocan en la carpeta priv/static, donde se guarda priv/static/assets para los activos generados. Todo en priv/static es atendido por el complemento Plug.Static configurado en lib/hello_web/endpoint.ex. Cuando se ejecuta en modo dev (MIX_ENV=dev), Phoenix observa cualquier cambio que realice en el directorio de activos y luego se encarga de actualizar su aplicación fronend en su navegador mientras trabaja.

Tenga en cuenta que cuando crea su aplicación Phoenix por primera vez usando mix phx.new, es posible especificar opciones que afectarán la presencia y el diseño del directorio assert. De hecho, las aplicaciones de Phoenix pueden traer sus propias herramientas de front-end o no tener un front-end en absoluto (útil si está escribiendo una API, por ejemplo). 

Si la integración predeterminada de esbuild no cubre sus necesidades, por ejemplo, porque desea utilizar otra herramienta de compilación, puede cambiar a una compilación de activos personalizados.

En cuanto a CSS, Phoenix incluye Tailwind CSS Framework, que proporciona una configuración básica para los proyectos. Puede pasar a cualquier marco CSS de su elección.