Translate

jueves, 2 de julio de 2026

Nuestro primer parser con ANTLR (Paso a paso)


En el post anterior vimos qué es ANTLR y para qué sirve.

Ahora vamos a construir nuestro primer parser desde cero.

No te preocupes si nunca usaste ANTLR. Vamos paso por paso.


Paso 1: Crear un proyecto Maven


Creamos un proyecto vacío:

mvn archetype:generate \

  -DgroupId=com.assembly \

  -DartifactId=mate \

  -DarchetypeArtifactId=maven-archetype-quickstart \

  -DinteractiveMode=false


La estructura debería quedar así:

mate

├── pom.xml

└── src

    ├── main

    │   └── java

    └── test


Paso 2: Agregar ANTLR


Abrimos el pom.xml y agregamos:


<dependencies>

    <dependency>

        <groupId>org.antlr</groupId>

        <artifactId>antlr4-runtime</artifactId>

        <version>4.13.2</version>

    </dependency>

</dependencies>


Paso 3: Instalar el plugin de ANTLR


Ahora agregamos:


<build>

    <plugins>


        <plugin>

            <groupId>org.antlr</groupId>

            <artifactId>antlr4-maven-plugin</artifactId>

            <version>4.13.2</version>


            <executions>

                <execution>

                    <goals>

                        <goal>antlr4</goal>

                    </goals>

                </execution>

            </executions>


        </plugin>


    </plugins>

</build>


Este plugin será el encargado de generar código Java a partir de nuestras gramáticas.


Paso 4: Crear la carpeta de gramáticas


Creamos:

src/main/antlr4


Quedando:

src

└── main

    ├── antlr4

    └── java


Paso 5: Crear nuestra primera gramática

Creamos:

src/main/antlr4/Mate.g4


Contenido:

grammar Mate;

program

    : 'hola' EOF

    ;


Nuestro lenguaje, por ahora, solamente acepta una palabra:

hola


Nada más.

Es el lenguaje más inútil de la historia.

Pero funciona.


Paso 6: Generar el parser

Ejecutamos:

mvn generate-sources


Si todo salió bien veremos algo parecido a:

Generating grammar...


Y aparecerá una carpeta nueva:

target/generated-sources/antlr4


Dentro encontraremos:

MateLexer.java

MateParser.java

MateListener.java

MateBaseListener.java


Estas clases fueron generadas automáticamente por ANTLR.

Nosotros no escribimos una sola línea de ellas.


Paso 7: Crear una clase de prueba


Creamos:

package com.mate;


import org.antlr.v4.runtime.*;


public class Main {


    public static void main(String[] args) {

        String source = "hola";

        var lexer =

            new MateLexer(

                CharStreams.fromString(source));

        var tokens =

            new CommonTokenStream(lexer);

        var parser =

            new MateParser(tokens);

        parser.program();

        System.out.println("Programa válido");

    }

}


Paso 8: Ejecutar


Corremos:

mvn compile exec:java


Resultado:

Programa válido


¿Qué pasa si el programa es inválido?


Probemos:


String source = "chau";


Ahora obtenemos:


line 1:0 mismatched input 'chau' expecting 'hola'


ANTLR detectó correctamente que nuestro programa no cumple la gramática.


¿Qué acaba de pasar?

Definimos una regla:

program

    : 'hola' EOF

    ;


ANTLR generó automáticamente:

  • Un lexer
  • Un parser
  • Clases auxiliares


Y nuestro programa pudo validar si un texto respetaba o no esa regla.


miércoles, 1 de julio de 2026

Guía de agentes de datos de Google Cloud: IA autónoma para problemas de datos complejos

 

Informe M-Trends 2026: los nuevos métodos de los atacantes

 

Programación funcional en PHP


La programación funcional no es exclusiva de lenguajes como Haskell, Elixir o Scala. PHP también permite adoptar muchos de sus principios para escribir código más limpio, expresivo y fácil de mantener.

Veamos algunas de las herramientas que ofrece PHP.


Funciones como ciudadanos de primera clase

Las funciones pueden almacenarse en variables, pasarse como argumentos y devolverse desde otras funciones.


<?php

$greet = fn(string $name) => "Hello $name!";

echo $greet("Emanuel");


Salida:

Hello Emanuel!


Arrow Functions

Introducidas en PHP 7.4, las Arrow Functions permiten escribir funciones pequeñas de manera mucho más concisa.


$numbers = [1, 2, 3, 4];


$squares = array_map(

    fn($n) => $n * $n,

    $numbers

);


print_r($squares);


Resultado:

Array

(

    [0] => 1

    [1] => 4

    [2] => 9

    [3] => 16

)


map()


Aunque PHP no posee un método map() sobre los arrays, dispone de array_map().


$names = ["john", "mary", "alice"];


$upper = array_map(

    "strtoupper",

    $names

);


print_r($upper);


Resultado:

JOHN

MARY

ALICE


filter()

Para filtrar colecciones se utiliza `array_filter()`.


$numbers = [1,2,3,4,5,6];


$even = array_filter(

    $numbers,

    fn($n) => $n % 2 === 0

);


print_r($even);


Resultado:

2

4

6


reduce()

array_reduce() permite reducir una colección a un único valor.


$numbers = [1,2,3,4,5];


$sum = array_reduce(

    $numbers,

    fn($acc, $n) => $acc + $n,

    0

);


echo $sum;


Resultado

15


Funciones puras

Una función pura siempre devuelve el mismo resultado para la misma entrada y no produce efectos secundarios.


function add(int $a, int $b): int

{

    return $a + $b;

}



En cambio, esta función no es pura:


$total = 0;


function addToTotal(int $n): void

{

    global $total;

    $total += $n;

}


Inmutabilidad

PHP no posee estructuras inmutables por defecto, pero es posible trabajar evitando modificar los datos originales.

$numbers = [1,2,3];


$newNumbers = [...$numbers, 4];


print_r($numbers);

print_r($newNumbers);


Resultado:

Original:

1 2 3

Nuevo:

1 2 3 4


Composición de funciones


Podemos construir funciones más complejas combinando funciones pequeñas.


$trim = fn($s) => trim($s);

$upper = fn($s) => strtoupper($s);


$normalize = fn($s) => $upper($trim($s));


echo $normalize("   hello   ");

Resultado

HELLO


Closures

Las closures permiten capturar variables del contexto.


function multiplier(int $factor)

{

    return fn($n) => $n * $factor;

}


$double = multiplier(2);

$triple = multiplier(3);


echo $double(10);

echo $triple(10);


Resultado

20

30


Encadenando operaciones


Una secuencia típica de programación funcional consiste en filtrar, transformar y reducir datos.


$numbers = [1,2,3,4,5,6];


$result = array_reduce(

    array_map(

        fn($n) => $n * $n,

        array_filter(

            $numbers,

            fn($n) => $n % 2 === 0

        )

    ),

    fn($acc, $n) => $acc + $n,

    0

);


echo $result;


Proceso:

  • Filtra los números pares.
  • Calcula el cuadrado de cada uno.
  • Suma todos los cuadrados.


Resultado:

56


Aunque PHP sigue siendo un lenguaje predominantemente orientado a objetos, incorpora suficientes herramientas para adoptar un estilo funcional cuando resulta conveniente. El uso de funciones puras, array_map(), array_filter(), array_reduce(), closures y Arrow Functions permite escribir código más declarativo, reutilizable y sencillo de razonar.

No reemplaza a lenguajes puramente funcionales como Haskell o Elixir, pero demuestra que la programación funcional es un paradigma que puede aplicarse con éxito en prácticamente cualquier lenguaje moderno.


jueves, 25 de junio de 2026

@MockitoSpyBean en Spring Framework


Si trabajás con pruebas en Spring Boot, seguramente utilizaste @SpyBean para observar el comportamiento de un bean real y verificar sus interacciones.

Sin embargo, a partir de las versiones más recientes de Spring, @SpyBean fue reemplazado por @MockitoSpyBean, una nueva anotación que forma parte del soporte oficial de Mockito en Spring Framework.

¿Por qué aparece @MockitoSpyBean?

Durante años Spring Boot ofreció:
  • @MockBean
  • @SpyBean

para integrar Mockito con el contexto de Spring.

Con la evolución del framework, estas anotaciones fueron reemplazadas por:
  • @MockitoBean
  • @MockitoSpyBean

que proporcionan una integración más consistente y alineada con Mockito.

Un Spy es una instancia real envuelta por Mockito.

Esto significa que:
  • Los métodos reales se ejecutan normalmente.
  • Podemos verificar invocaciones.
  • Podemos modificar comportamientos específicos cuando sea necesario.

Por eso un Spy resulta ideal cuando queremos observar el comportamiento de un bean sin reemplazarlo completamente.

Supongamos el siguiente servicio:


@Service
public class EmailService {

    public void send(String to, String message) {
        System.out.println("Sending email to " + to);
    }
}


Y un servicio que lo utiliza:

@Service
public class UserService {

    private final EmailService emailService;

    public UserService(EmailService emailService) {
        this.emailService = emailService;
    }

    public void register(String email) {
        emailService.send(email, "Welcome!");
    }
}

Utilizando @MockitoSpyBean


@SpringBootTest
class UserServiceTest {

    @Autowired
    private UserService userService;

    @MockitoSpyBean
    private EmailService emailService;

    @Test
    void shouldSendWelcomeEmail() {

        userService.register("john@example.com");

        verify(emailService)
                .send("john@example.com", "Welcome!");
    }
}

La prueba utiliza el bean real de Spring, pero Mockito registra las interacciones permitiéndonos verificar llamadas y argumentos.

Aunque el Spy ejecuta la implementación real, podemos modificar métodos específicos:

doNothing()
    .when(emailService)
    .send(anyString(), anyString());


De esta manera evitamos ejecutar la lógica real mientras seguimos verificando las invocaciones.

En la mayoría de los proyectos Spring Boot basta con incluir:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

Si usas gradle:

testImplementation 'org.springframework.boot:spring-boot-starter-test'

Esta dependencia incorpora Mockito y las utilidades de testing de Spring.

@MockitoBean vs @MockitoSpyBean ¿Cuándo conviene usarlo?

@MockitoSpyBean resulta útil cuando:
  • Queremos verificar interacciones sobre un bean real.
  • Necesitamos conservar parte de la lógica original.
  • Queremos evitar crear mocks complejos.
  • Estamos realizando pruebas de integración con Spring.

Los Spies son poderosos, pero también pueden volver las pruebas más sensibles a cambios internos de implementación.

Como regla general:
  • Utilizá @MockitoBean para aislar dependencias.
  • Utilizá @MockitoSpyBean cuando necesites observar el comportamiento real de un bean.

@MockitoSpyBean representa la evolución natural de @SpyBean y es la alternativa recomendada en las versiones actuales de Spring.

Permite combinar la ejecución real de un componente con todas las capacidades de verificación de Mockito, logrando pruebas más expresivas y fáciles de diagnosticar cuando algo falla.

Y si no entienden la imagen es Mr Bean de espía ;) 

martes, 23 de junio de 2026

Bases de Datos Vectoriales: la memoria de las aplicaciones de IA


Cuando hablamos de Inteligencia Artificial y modelos de lenguaje (LLMs), solemos pensar en el modelo en sí. Sin embargo, gran parte de las aplicaciones modernas de IA dependen de otro componente fundamental: las bases de datos vectoriales.

¿Por qué son necesarias?

Los LLMs poseen conocimiento general, pero no conocen nuestros documentos, manuales, correos o información específica de una empresa.

Si queremos construir un chatbot que responda preguntas sobre nuestra propia información, necesitamos una forma eficiente de almacenar y buscar ese conocimiento.

Aquí es donde entran las bases de datos vectoriales.


¿Qué es un vector?

Un vector es una representación numérica de un texto, una imagen o cualquier otro dato.

Por ejemplo:

"Java es un lenguaje orientado a objetos"

[0.24, -0.81, 0.53, 0.17, ...]


Este proceso se realiza mediante un modelo de embeddings.

La idea es que textos con significados similares tendrán vectores cercanos entre sí.


Por ejemplo:

"¿Cómo crear una API REST con Spring?"

"Tutorial de Spring Boot para servicios REST"


Sus vectores estarán próximos en el espacio vectorial, aunque las frases sean distintas.


¿Cómo funciona una búsqueda vectorial?

Supongamos que almacenamos miles de documentos.

Cuando el usuario realiza una pregunta:

¿Cómo conectarse a una base de datos con Spring Boot?


1. La pregunta se convierte en un vector.

2. Se buscan los vectores más cercanos.

3. Se recuperan los documentos más relevantes.

4. Esa información se envía al LLM.

5. El modelo genera una respuesta basada en esos documentos.


Este patrón se conoce como RAG (Retrieval-Augmented Generation).


Usuario

   ↓

Pregunta

   ↓

Embedding

   ↓

Base de datos vectorial

   ↓

Documentos relevantes

   ↓

LLM

   ↓

Respuesta


¿Cómo se mide la similitud?

Las bases de datos vectoriales utilizan diferentes métricas:


Similitud del coseno

Mide qué tan parecidos son dos vectores según el ángulo entre ellos.


Coseno = 1

→ Muy similares


Coseno = 0

→ Sin relación


Coseno = -1

→ Opuestos


Es una de las métricas más utilizadas en NLP.


Distancia Euclidiana

Mide la distancia entre dos puntos.


Menor distancia

→ Mayor similitud


Producto escalar (Dot Product)

Evalúa la relación entre vectores considerando tanto dirección como magnitud.


¿Por qué no usar una base de datos relacional?

En una base de datos tradicional podríamos hacer:

SELECT *

FROM documentos

WHERE contenido LIKE '%Spring%'


Pero esto presenta limitaciones:

  • Solo encuentra coincidencias literales.
  • No comprende el significado.
  • No detecta sinónimos.
  • Escala mal con millones de embeddings.


Una búsqueda vectorial permite consultas semánticas:

Pregunta:

¿Cómo consumir una API REST?


Documento encontrado:

Guía para invocar servicios web HTTP con Spring.

Aunque las palabras sean distintas, el significado es similar.


Comparar un vector con millones de otros puede ser costoso.

Por ello, las bases de datos vectoriales utilizan algoritmos como:

  • HNSW (Hierarchical Navigable Small World)
  • IVF (Inverted File Index)
  • PQ (Product Quantization)


Estos algoritmos permiten encontrar los vecinos más cercanos de manera eficiente sin tener que recorrer todos los vectores.


Bases de datos vectoriales populares

Pinecone

Servicio administrado diseñado específicamente para IA.

Características:

  • Escalabilidad automática.
  • API sencilla.
  • Alta disponibilidad.


Milvus

Proyecto open source muy popular.

Características:

  • Alto rendimiento.
  • Soporte para miles de millones de vectores.
  • Compatible con GPU.


Qdrant

Base de datos open source escrita en Rust.

Características:

  • Excelente rendimiento.
  • Filtros avanzados.
  • API REST y gRPC.


Weaviate

Incluye capacidades de búsqueda híbrida y soporte para GraphQL.


Chroma

Muy utilizada en proyectos pequeños y prototipos.


PostgreSQL + pgvector

Permite agregar capacidades vectoriales a PostgreSQL.

Ideal cuando ya se utiliza PostgreSQL y no se necesita una solución especializada.


¿Siempre necesito una base de datos vectorial?

No necesariamente.


Para muchos proyectos pequeños, PostgreSQL con la extensión pgvector puede ser suficiente.

Una base vectorial especializada comienza a mostrar ventajas cuando:

  • Existen millones de embeddings.
  • Se requieren búsquedas muy rápidas.
  • Hay múltiples usuarios concurrentes.
  • Se necesita alta escalabilidad.


Las bases de datos vectoriales se han convertido en uno de los componentes fundamentales de las aplicaciones modernas de IA.

Su objetivo no es reemplazar a las bases de datos tradicionales, sino complementar a los LLMs proporcionando una forma eficiente de almacenar y recuperar información basada en significado y no únicamente en palabras exactas.

En otras palabras, si los LLMs son el cerebro de una aplicación de IA, las bases de datos vectoriales actúan como su memoria externa, permitiendo implementar asistentes, chatbots y sistemas RAG capaces de trabajar con conocimiento actualizado y específico.

lunes, 22 de junio de 2026

ANTLR: El primer paso para construir un lenguaje de programación


Cuando usamos Java, Kotlin o Scala, normalmente escribimos código sin pensar demasiado en lo que ocurre detrás de escena.


int result = 10 + 20;


Sin embargo, antes de que la JVM pueda ejecutar ese programa, alguien tuvo que diseñar un compilador capaz de entender ese texto y transformarlo en bytecode.

En esta serie vamos a construir nuestro propio lenguaje para la JVM. No pretendemos competir con Kotlin o Scala, pero sí recorrer muchos de los mismos conceptos que utilizan estos lenguajes internamente.


Y el primer paso es aprender una herramienta fundamental: ANTLR.


¿Qué es ANTLR?

ANTLR (ANother Tool for Language Recognition) es una herramienta que permite generar analizadores léxicos y sintácticos a partir de una gramática.

En lugar de escribir manualmente todo el código necesario para interpretar un lenguaje, simplemente describimos las reglas del mismo y ANTLR genera automáticamente gran parte de la infraestructura necesaria.


Por ejemplo, podemos definir una regla como:


expression

    : expression '+' expression

    | NUMBER

    ;


A partir de ella ANTLR podrá reconocer expresiones como:

1 + 2 + 3


Un compilador suele dividirse en varias etapas:


Código Fuente

      │

      ▼

 Lexer

      │

      ▼

 Tokens

      │

      ▼

 Parser

      │

      ▼

 AST

      │

      ▼

 Análisis Semántico

      │

      ▼

 Generación de Código


ANTLR participa principalmente en las dos primeras:

  • Lexer
  • Parser


¿Qué hace el Lexer?

El Lexer toma una secuencia de caracteres y la divide en tokens.


Por ejemplo:

let age = 42


se transforma en:

LET

IDENTIFIER(age)

ASSIGN

NUMBER(42)


El Lexer no entiende el significado del programa. Solo identifica patrones.


¿Qué hace el Parser?

El Parser toma los tokens generados por el Lexer y verifica que respeten las reglas definidas por la gramática.


Por ejemplo:

LET IDENTIFIER ASSIGN NUMBER


podría convertirse en:

Assignment

 ├── Variable(age)

 └── Number(42)


Esta estructura representa la forma del programa y servirá como base para las siguientes etapas.


¿Qué es una gramática?

Una gramática define qué construcciones son válidas dentro de un lenguaje.


Por ejemplo:

variableDeclaration

    : 'let' IDENTIFIER '=' expression

    ;


Esta regla indica que una declaración válida debe contener:

  • La palabra reservada let
  • Un identificador
  • El símbolo =
  • Una expresión


¿ANTLR genera compiladores?

No.


ANTLR resuelve principalmente:

  • Análisis léxico
  • Parsing


Todavía debemos implementar:

  • Árboles sintácticos abstractos (AST)
  • Tablas de símbolos
  • Sistema de tipos
  • Generación de bytecode JVM


Por eso ANTLR es una herramienta muy importante, pero representa solo una parte del compilador completo.


viernes, 19 de junio de 2026

Java 26: LazyConstant


Una de las novedades de Java 26 es LazyConstant, una API experimental que permite definir constantes inicializadas bajo demanda (*lazy initialization*) de forma segura y eficiente.


Supongamos que tenemos un objeto costoso de crear:


public class Config {

    private static final ExpensiveObject INSTANCE =

            new ExpensiveObject();

}


El objeto se crea cuando la clase es cargada, aunque nunca llegue a utilizarse.

Una solución clásica es utilizar el patrón Initialization-on-demand holder:


public class Config {


    private static class Holder {

        static final ExpensiveObject INSTANCE =

                new ExpensiveObject();

    }


    public static ExpensiveObject instance() {

        return Holder.INSTANCE;

    }

}


Funciona, pero es un patrón poco evidente y algo verboso.

Con Java 26 podemos declarar un valor que será calculado únicamente la primera vez que se necesite:


private static final LazyConstant<ExpensiveObject> INSTANCE =

        LazyConstant.of(ExpensiveObject::new);


y obtenerlo mediante:


ExpensiveObject obj = INSTANCE.get();


La instancia se crea una sola vez y es segura para múltiples hilos.


Veamos un ejemplo:


class DatabaseConnection {


    private static final LazyConstant<ConnectionPool> POOL =

            LazyConstant.of(ConnectionPool::new);


    static ConnectionPool pool() {

        return POOL.get();

    }

}


La conexión se abrirá únicamente cuando alguien invoque:


DatabaseConnection.pool();


¿Qué ventajas tiene?

  • Inicialización diferida (lazy initialization).
  • Thread-safe.
  • Evita implementar patrones complejos.
  • Más legible que usar synchronized, volatile o double-checked locking.
  • Puede reemplazar muchos usos de AtomicReference.


LazyConstant está pensado para recursos costosos o poco utilizados, donde tiene sentido retrasar su creación.

Durante años, Java obligó a utilizar patrones como el Holder idiom o double-checked locking para conseguir inicialización perezosa segura. Java 26 introduce LazyConstant para convertir ese patrón en una característica del lenguaje y la biblioteca estándar, haciendo el código más simple, legible y menos propenso a errores.


miércoles, 17 de junio de 2026

¿Por qué Clojure compila diferente? Ventajas y desventajas de ser un "Hosted Language" en la JVM


Cuando pensamos en lenguajes para la JVM, solemos meter en la misma bolsa a Java, Scala, Kotlin y Clojure. Después de todo, todos terminan ejecutándose sobre la misma máquina virtual.


Pero hay una diferencia fundamental en cómo están construidos.


Scala y Kotlin generan bytecode JVM de manera bastante directa. Clojure, en cambio, adopta una filosofía distinta: es un hosted language, es decir, un lenguaje "hospedado" sobre la plataforma Java.


¿Y qué significa eso? ¿Tiene ventajas? ¿Tiene costos?

Lenguajes como Java, Scala o Kotlin siguen aproximadamente este esquema:


Código fuente

      ↓

Compilador

      ↓

Bytecode JVM (.class)

      ↓

JVM


Muchas características del lenguaje se traducen directamente en clases, métodos y bytecode especializado.


Por ejemplo, una case class de Scala:

case class Person(name: String)

genera automáticamente métodos como:

  • equals()
  • hashCode()
  • copy()
  • toString()


Todo esto queda representado directamente en bytecode.


Clojure también produce bytecode JVM, pero gran parte del comportamiento del lenguaje vive en su runtime:


Código Clojure

      ↓

Compilador

      ↓

Bytecode + Runtime de Clojure

      ↓

JVM


Por eso Rich Hickey describe a Clojure como un hosted language.


La plataforma Java no es solamente un destino de compilación: es el ecosistema sobre el cual está construido el lenguaje.


Ventaja: Aprovecha toda la plataforma Java


Desde Clojure podemos usar directamente cualquier clase Java:


(import java.time.LocalDate)

(LocalDate/now)


No hay puentes especiales ni adaptadores.

Clojure reutiliza:

  • la JVM;
  • el recolector de basura;
  • las bibliotecas Java;
  • los hilos;
  • las excepciones;
  • las herramientas de profiling;
  • todo el ecosistema existente.


En vez de reinventar la rueda, se apoya en ella.


Ventaja: Un compilador relativamente pequeño


Muchas características importantes de Clojure viven en bibliotecas y no en el compilador:

  • secuencias perezosas;
  • colecciones persistentes;
  • STM;
  • transducers.


Esto hace que el compilador sea considerablemente más sencillo que los de Scala o Kotlin.


Ventaja: Desarrollo interactivo y REPL


Una de las mayores fortalezas de Clojure es su modelo de desarrollo interactivo.

Podemos definir una función:


(defn cuadrado [x]

  (* x x))


y cargarla inmediatamente en la REPL, sin recompilar todo el proyecto.


Este enfoque favorece:

  • feedback rápido;
  • hot reloading;
  • experimentación;
  • desarrollo incremental.


Ventaja: Evolución mediante librerías


Muchas características avanzadas de Clojure se agregaron sin modificar demasiado el lenguaje:

  • transducers;
  • core.async;
  • spec;
  • STM.


Al depender más del runtime y menos del compilador, la evolución del ecosistema suele ser más flexible.


Las desventajas

Menos optimizaciones

Scala y Kotlin generan bytecode más especializado.

En Clojure, muchas operaciones pasan por componentes del runtime como:

  • IFn;
  • PersistentVector;
  • PersistentMap;
  • RT.


Esto introduce niveles adicionales de indirección y, en ciertos casos, puede afectar el rendimiento.


Más reflexión

Si escribimos:

(defn largo [s]

  (.length s))


el compilador puede recurrir a reflexión.

Podemos ayudarlo con type hints:


(defn largo [^String s]

  (.length s))


pero esto requiere intervención del programador.


Menos comprobaciones en tiempo de compilación


Scala y Kotlin verifican muchas cosas antes de ejecutar:

  • tipos;
  • nulabilidad;
  • exhaustividad del pattern matching;
  • restricciones genéricas.


Clojure, al ser dinámico, detecta muchos errores recién en tiempo de ejecución.


Interoperabilidad asimétrica

Desde Clojure usar Java es muy fácil.

Pero desde Java consumir código Clojure no es tan natural:


IFn plus = Clojure.var("myns", "plus");


Mientras que una clase escrita en Scala o Kotlin suele verse como una clase Java convencional.


Dos filosofías distintas

No es que una aproximación sea mejor que la otra.

Scala y Kotlin intentan enriquecer la JVM mediante compiladores sofisticados y bytecode especializado.

Clojure adopta otra filosofía: construir un lenguaje pequeño y dinámico que reutilice al máximo la plataforma existente.

Al final, la diferencia más interesante no es a qué compilan, porque todos terminan ejecutándose en la JVM.

La verdadera diferencia es: ¿Cuánto del lenguaje vive en el compilador y cuánto vive en el runtime?

Y en esa decisión de diseño está gran parte de la personalidad de Clojure.


martes, 16 de junio de 2026

¿Cuáles son las tendencias en la infraestructura de IA?