Translate

lunes, 9 de noviembre de 2026

El 93% de los profesionales latinos afirman conocer o trabajar con Agentes de IA

Más resultados, menos instrucciones con la IA

miércoles, 4 de noviembre de 2026

Scala 3: entendiendo los implícitos desde cero parte 2


Ya sabemos que es implícitos ahora veamos un ejemplo más realista: dar formato a los precios.

Utilizar un String como idioma sirve para entender el mecanismo, pero no es una buena práctica para representar una configuración importante. Podríamos confundir el idioma con cualquier otra cadena de texto.


Vamos a crear un tipo específico.


case class Moneda(simbolo: String)

given monedaArgentina: Moneda = Moneda("$")


def mostrarPrecio(precio: Double)(using moneda: Moneda): String =

  s"${moneda.simbolo}$precio"


¿Qué significa cada parte?

case class Moneda define un tipo de dato para representar una moneda.

simbolo es el símbolo que queremos mostrar.

given monedaArgentina declara una moneda disponible para el compilador.

using moneda: Moneda indica que la función necesita una moneda para trabajar.


Ahora podemos ejecutar:


mostrarPrecio(1500)

mostrarPrecio(2500)


El resultado será:

$1500.0

$2500.0


La función no necesita recibir la moneda en cada llamada. Scala la encuentra en el contexto.

Este ejemplo es deliberadamente sencillo: en una aplicación real, el formato de los precios también debería contemplar separadores decimales, miles y otras reglas regionales.


¿Qué ocurre si tenemos dos monedas?

Supongamos que nuestra aplicación necesita trabajar tanto con pesos argentinos como con euros.


Podríamos declarar:

given pesos: Moneda = Moneda("$")

given euros: Moneda = Moneda("€")


Ahora tenemos dos valores del mismo tipo.


Si llamamos a:

mostrarPrecio(1500)


el compilador no tiene una única moneda que pueda elegir. Ambas coinciden con el tipo que necesita la función.

En este caso, la resolución del parámetro contextual es ambigua y el programa no compila.

¿Cómo lo resolvemos?

Podemos indicar explícitamente cuál queremos utilizar:


mostrarPrecio(1500)(using pesos)

mostrarPrecio(1500)(using euros)


También podemos organizar el código para que cada contexto tenga disponible solamente la moneda que corresponde.

La lección es importante: given no significa que Scala elige cualquier valor. El compilador debe encontrar un valor adecuado y una resolución válida.


A veces queremos pedirle al compilador que encuentre un valor contextual, sin estar dentro de una función que tenga un parámetro using.


Para eso existe summon.


case class Moneda(simbolo: String)

given monedaArgentina: Moneda = Moneda("$")

val monedaActual: Moneda = summon[Moneda]


println(monedaActual.simbolo)


El resultado es: $

La expresión: summon[Moneda] significa, aproximadamente: «Compilador, encontrá un valor contextual de tipo Moneda».


Si no existe un valor apropiado, el programa no compila.

No hay que confundir summon con una búsqueda que ocurre en cualquier momento mientras el programa se ejecuta. La resolución de estos valores es principalmente una tarea del compilador.


Hasta ahora vimos cómo proporcionar una configuración, pero los parámetros contextuales también permiten construir abstracciones más generales.

Imaginemos que queremos imprimir distintos tipos de valores. Para algunos tipos sabemos cómo convertirlos en texto; para otros, todavía no.


Podemos definir una abstracción:


trait Mostrar[A]:

  def mostrar(valor: A): String


Esta definición dice que Mostrar[A] representa una forma de mostrar valores de tipo A como texto.

Por ejemplo, podemos proporcionar una implementación para los enteros:


given mostrarEnteros: Mostrar[Int] with

  def mostrar(valor: Int): String =

    s"El número es $valor"


Y otra para las cadenas:


given mostrarTextos: Mostrar[String] with

  def mostrar(valor: String): String =

    s"El texto es '$valor'"


Ahora podemos definir una función genérica:


def imprimir[A](valor: A)(using m: Mostrar[A]): Unit =

  println(m.mostrar(valor))


¿Qué sucede cuando llamamos a la función?


imprimir(42)

imprimir("Hola")


En el primer caso, Scala busca un Mostrar[Int].

En el segundo, busca un Mostrar[String].


Como hemos declarado ambas implementaciones mediante given, el compilador puede seleccionar la correspondiente al tipo del argumento.


El resultado es:

El número es 42

El texto es 'Hola'


Este ejemplo muestra una de las ventajas más importantes del mecanismo: podemos definir una función general sin conocer de antemano cómo se representa cada tipo.

Las implementaciones concretas se proporcionan por separado y el compilador las conecta.

El ejemplo anterior es una forma de implementar una type class.


Una type class permite definir una capacidad que puede tener un tipo sin necesidad de modificar la definición original de ese tipo.


Por ejemplo, podemos definir cómo mostrar un entero y cómo mostrar una cadena, aunque Int y String sean tipos que no hemos creado nosotros.


La estructura es:

1. Definimos una abstracción, como Mostrar[A].

2. Proporcionamos implementaciones para los tipos que nos interesan mediante given.

3. Escribimos funciones que solicitan esa capacidad mediante using.

4. El compilador encuentra la implementación apropiada.


Esta idea es muy utilizada en Scala para implementar operaciones genéricas, comparaciones, serialización, conversión de datos y muchas otras abstracciones.


Scala 3 introdujo una sintaxis más específica para implicitos :

  • given declara instancias o valores contextuales.
  • using declara parámetros contextuales.
  • summon permite solicitar un valor contextual.
  • Conversion permite definir conversiones entre tipos.
  • extension permite agregar métodos de extensión a tipos existentes.


Los implícitos de Scala permiten que el compilador encuentre valores y comportamientos que una función necesita, sin que debamos pasarlos explícitamente en cada llamada.

domingo, 1 de noviembre de 2026

Scala 3: entendiendo los implícitos desde cero


Scala tiene una característica muy interesante: permite que el compilador encuentre ciertos valores o funcionalidades sin que tengamos que escribirlos explícitamente cada vez que los necesitamos.

Esta característica se conoce tradicionalmente como implicits (implícitos).

En Scala 3, los mecanismos principales para trabajar con implícitos son given y using. También existen otras herramientas relacionadas, como las conversiones automáticas mediante Conversion y las extensiones mediante extension.

Pero antes de hablar de palabras reservadas, vamos a entender el problema que intentan resolver.

Imaginemos que estamos desarrollando un programa que saluda a las personas.

Podríamos escribir una función como esta:


def saludar(nombre: String, idioma: String): String =

  if idioma == "es"

    then s"Hola, $nombre"

    else s"Hello, $nombre"


Para saludar a una persona, debemos indicar su nombre y el idioma:


saludar("Ana", "es")

saludar("Juan", "en")


Funciona perfectamente.

Sin embargo, imaginemos que toda nuestra aplicación está en español. Cada vez que llamamos a saludar, debemos repetir "es".


¿Qué pasaría si pudiéramos indicarle al programa que, por defecto, el idioma es español y que la función lo utilice automáticamente?

En Scala 3 podemos hacerlo mediante un parámetro contextual.


Veamos una implementación:


given idioma: String = "es"


def saludar(nombre: String)(using idioma: String): String =

  if idioma == "es"

    then s"Hola, $nombre"

    else s"Hello, $nombre"


Hay dos conceptos nuevos.

given: declarar un valor disponible para ser encontrado por el compilador.


given idioma: String = "es"


Estamos declarando un valor de tipo String que el compilador puede utilizar cuando necesite encontrar un valor de ese tipo en un contexto compatible.


using: declarar un parámetro que el compilador debe buscar.


def saludar(nombre: String)(using idioma: String): String =

  ...



La función necesita dos datos: el nombre y el idioma. El nombre se pasa normalmente, mientras que el idioma es un parámetro contextual.


Ahora podemos escribir:


saludar("Ana")

saludar("Juan")


El compilador encuentra el given de tipo String y lo utiliza para completar el parámetro using.


El resultado es:

Hola, Ana

Hola, Juan


¡No necesitamos escribir el idioma en cada llamada!

Es importante entender algo: el idioma no aparece mágicamente. Lo declaramos nosotros con given. Lo que hace Scala es encontrarlo y pasarlo automáticamente cuando corresponde.


¿Qué hace realmente el compilador?

Podemos pensar que el compilador completa la llamada a la función.


Nosotros escribimos:

saludar("Ana")


Conceptualmente, el compilador resuelve esa llamada como si hubiéramos escrito:

saludar("Ana")(using idioma)


No es necesario escribir esa segunda forma cuando el compilador puede encontrar el valor contextual adecuado.


Esta característica se conoce como contextual parameters, o parámetros contextuales.


El mecanismo permite separar dos responsabilidades:

  • La función declara qué información necesita.
  • El contexto proporciona esa información.


La función no necesita saber dónde se definió el idioma. Solo necesita recibir un String.


miércoles, 28 de octubre de 2026

[Infografía] 4 prioridades de infraestructura para escalar la IA de agentes

 

Me llegó este correo y lo quiero compartir : 

miércoles, 21 de octubre de 2026

Terraform avanzado: dynamic blocks y dependencias entre recursos


En los posts anteriores empezamos con los conceptos básicos de Terraform y fuimos aumentando la complejidad.

Ahora podemos empezar a enfrentarnos con un problema que aparece constantemente en infraestructura real.


Un resource puede contener bloques de configuración repetitivos.


Por ejemplo, imaginemos una regla de firewall:

regla 1 → puerto 80

regla 2 → puerto 443

regla 3 → puerto 8080


Podríamos escribir cada bloque manualmente.

Pero si las reglas vienen de una variable, ¿cómo hacemos para generar esos bloques dinámicamente?

Ahí aparece: dynamic

Un dynamic block permite generar bloques de configuración repetidos dentro de un resource.

La idea es parecida a un for_each, pero hay una diferencia fundamental.

Con for_each podemos crear resources:


resource "..." "..." {

  for_each = ...

}


Con dynamic podemos generar nested blocks dentro de un resource.


Conceptualmente:


for_each

   │

   └── crea resources


dynamic

   │

   └── crea bloques dentro de un resource


Supongamos que un resource tuviera una estructura como:


resource "example" "server" {

  rule {

    port = 80

  }


  rule {

    port = 443

  }

}


Tenemos dos bloques:


server

 ├── rule

 └── rule


Ahora supongamos que los puertos vienen de una variable:


variable "ports" {

  default = [80, 443, 8080]

}


Queremos generar:


rule {

  port = 80

}


rule {

  port = 443

}


rule {

  port = 8080

}


Podemos utilizar dynamic.


resource "example" "server" {


  dynamic "rule" {

    for_each = var.ports


    content {

      port = rule.value

    }

  }

}


Terraform genera conceptualmente:


server

 ├── rule { port = 80 }

 ├── rule { port = 443 }

 └── rule { port = 8080 }


¿Qué significa content?

Esta parte suele resultar extraña al principio. Podemos leerlo como: Por cada elemento de var.ports, generá un bloque llamado rule cuyo contenido sea el bloque content.


Si: var.ports = [80, 443, 8080]


Terraform conceptualmente produce:


rule {

  port = 80

}


rule {

  port = 443

}


rule {

  port = 8080

}


dynamic necesita que el provider soporte ese bloque

Este detalle es importante.

No podemos inventar cualquier bloque:


dynamic "foo" {

  ...

}


El resource tiene que soportar un bloque foo.

Por eso dynamic depende de la estructura definida por el provider.


Por ejemplo, un resource puede tener:


resource "..." "..." {


  ingress {

    ...

  }


  egress {

    ...

  }

}


Entonces podemos generar dinámicamente ingress o egress.

Pero no podemos inventar:


dynamic "banana" {

  ...

}


si ese resource no tiene un bloque banana.

Los recursos de infraestructura suelen tener configuraciones como reglas de red.


Podríamos tener:


variable "ingress_rules" {

  default = [

    {

      port     = 80

      protocol = "tcp"

    },

    {

      port     = 443

      protocol = "tcp"

    }

  ]

}


Y un resource que soporte bloques ingress.


Conceptualmente:


resource "example_security_group" "app" {


  dynamic "ingress" {

    for_each = var.ingress_rules


    content {

      port     = ingress.value.port

      protocol = ingress.value.protocol

    }

  }

}


La configuración resultante tendría dos bloques:


ingress

  port = 80

  protocol = tcp


ingress

  port = 443

  protocol = tcp


Esto es mucho más flexible que escribir las reglas manualmente.

¿Por qué no utilizar simplemente for_each? Porque for_each y dynamic resuelven problemas diferentes.

Supongamos que tenemos:


resource "example_server" "server" {

  for_each = var.servers

}


Esto crea:

server["web"]

server["api"]

server["worker"]


Estamos creando tres resources.


En cambio:


resource "example_server" "server" {

  dynamic "rule" {

    for_each = var.rules

    content {

      ...

    }

  }

}


crea un solo resource pero con varios bloques:


server

 ├── rule

 ├── rule

 └── rule


La diferencia es fundamental.


Ahora pasemos a otro concepto importante.

Terraform necesita conocer las dependencias entre los recursos.


Supongamos que tenemos:


network

   │

   ▼

server


La máquina necesita que la red exista antes.

¿Cómo sabe Terraform que existe esa relación?

Muchas veces la dependencia aparece automáticamente a través de referencias.


Por ejemplo:


resource "example_network" "main" {

  name = "main"

}


resource "example_server" "app" {

  network_id = example_network.main.id

}


La expresión:

example_network.main.id


no solamente obtiene un valor.

También establece una dependencia.

Terraform entiende:


example_network.main

          │

          ▼

example_server.app


Por lo tanto, primero debe crear la red y después el servidor.


Dependencias implícitas: Este tipo de dependencia se conoce como una dependencia implícita.


Por ejemplo:

resource "A" "a" {

  ...

}


resource "B" "b" {

  resource_id = A.a.id

}


Terraform puede deducir: A → B


No necesitamos decir explícitamente: "Creá A antes que B."

La referencia ya expresa esa relación.

Esto es una de las ventajas del enfoque declarativo.


A veces existe una dependencia que Terraform no puede deducir automáticamente.

Por ejemplo:

resource "example_network" "main" {

  ...

}


resource "example_server" "app" {

  ...

}


Supongamos que sabemos que el servidor depende de la red, pero nuestra configuración no tiene ninguna referencia directa entre ellos.


Podemos indicarlo:


resource "example_server" "app" {


  depends_on = [

    example_network.main

  ]

}


Ahora estamos diciendo explícitamente:


example_network.main

          │

          ▼

example_server.app


¿Cuándo usar depends_on?

La regla general es: Utilizá dependencias implícitas cuando sea posible.

Es decir, preferimos:

network_id = example_network.main.id


antes que:

depends_on = [

  example_network.main

]


si realmente necesitamos el id de la red.

¿Por qué? Porque la referencia expresa dos cosas al mismo tiempo:

  1. qué valor necesitamos;
  2. de qué recurso dependemos.

Es mucho más descriptivo. depends_on no debería ser nuestra primera herramienta

Un error frecuente es empezar a agregar: depends_on = [...] por todas partes.


Por ejemplo:


resource "A" "a" {

  ...

}


resource "B" "b" {

  depends_on = [A.a]

}


resource "C" "c" {

  depends_on = [B.b]

}


resource "D" "d" {

  depends_on = [C.c]

}


Podemos terminar creando una enorme cadena artificial: A → B → C → D

aunque Terraform podría haber determinado parte de esas relaciones por sí mismo.


Esto reduce la capacidad de Terraform para ejecutar operaciones en paralelo.

Este punto es muy interesante.


Supongamos:


network

   │

   ├── server

   │

   └── database


server y database dependen de network, pero no dependen entre ellos.

Terraform puede conceptualizarlo como:

        network

        /     \

       /       \

   server    database


Una vez creada la red, ambos recursos pueden avanzar independientemente.

En cambio, si agregamos dependencias innecesarias:


network

   │

   ▼

server

   │

   ▼

database


estamos obligando a Terraform a realizar operaciones secuencialmente.

Por eso las dependencias correctas son importantes tanto para la correctitud como para el rendimiento.

Todo esto nos lleva a una idea muy interesante.

Terraform puede representar nuestra infraestructura como un grafo de dependencias.


Por ejemplo:

             network

            /       \

           /         \

       subnet       database

          |

          ▼

        server

          |

          ▼

    load_balancer


Los nodos representan recursos.

Las flechas representan dependencias.

Terraform utiliza esta información para determinar el orden de las operaciones.

Por eso una configuración declarativa puede funcionar sin que nosotros escribamos:

  1. crear network
  2. crear subnet
  3. crear server
  4. crear load balancer


Terraform construye ese orden a partir de las relaciones.

Ahora podemos combinar varios conceptos que vimos en los últimos artículos.

Supongamos:


variable "servers" {

  type = map(object({

    size = string

  }))


  default = {

    web = {

      size = "small"

    }


    api = {

      size = "medium"

    }

  }

}


Podemos crear múltiples servidores:


resource "example_server" "server" {

  for_each = var.servers


  name = each.key

  size = each.value.size

}


Y podríamos tener otro recurso que dependa de esos servidores.

Por ejemplo:


resource "example_load_balancer" "main" {


  backend_ids = [

    for server in example_server.server :

    server.id

  ]

}


La expresión:


[

  for server in example_server.server :

  server.id

]


construye una lista con los IDs de todos los servidores.

Terraform puede deducir que el load balancer depende de ellos.

Tenemos entonces:


                      network

                            │

                            ▼

      ┌───────┴───────┐

      ▼                                         ▼

    web                                        api

      \                                           /

        \                                       /

          ▼                              ▼

                 load balancer


Esto ya se parece bastante a una infraestructura real.

Aunque dynamic es útil, hay que tener cuidado.

Es tentador transformar cualquier cosa en:


dynamic "something" {

  for_each = ...

  content {

    ...

  }

}


Pero podemos terminar con una configuración difícil de leer.

Por ejemplo, si solamente tenemos dos reglas:


ingress {

  port = 80

}


ingress {

  port = 443

}


puede ser mucho más claro escribirlas directamente.

No todo necesita ser dinámico.

Una buena regla práctica es: Usá dynamic cuando realmente necesites generar un número variable de bloques.


Si la configuración es pequeña y fija, escribirla explícitamente puede ser mejor.


Esto:


dynamic "ingress" {

  for_each = var.ingress_rules


  content {

    port     = ingress.value.port

    protocol = ingress.value.protocol

  }

}


es muy útil cuando:


development → 2 reglas

production  → 8 reglas


Pero si siempre tenemos exactamente:


HTTP

HTTPS


quizás sea más claro:


ingress {

  port     = 80

  protocol = "tcp"

}


ingress {

  port     = 443

  protocol = "tcp"

}


Terraform es código.

Y, como cualquier código, la claridad importa.

En los primeros artículos Terraform parecía bastante sencillo: resource

Después agregamos:

  • variables
  • outputs
  • modules


Y ahora tenemos:


resources

   │

   ├── count

   ├── for_each

   ├── expressions

   └── dynamic blocks

            │

            ▼

      dependencias

            │

            ▼

       dependency graph


Esto cambia bastante la forma en que podemos modelar infraestructura.

Ya no estamos simplemente escribiendo: "Crear un servidor."

Estamos describiendo relaciones: "Quiero estos servidores, con estas configuraciones, conectados a esta infraestructura y utilizados por este balanceador."

Terraform se ocupa de convertir ese modelo en un conjunto de operaciones.