Translate

lunes, 6 de julio de 2026

Diseñando MateScript: Transforms, extendiendo el lenguaje sin modificar el compilador


A medida que avanzaba con el diseño de MateScript apareció una pregunta interesante.

¿Cómo agregamos azúcar sintáctico al lenguaje sin llenar el compilador de casos especiales?

Muchos lenguajes modernos terminan incorporando nuevas palabras reservadas, operadores o construcciones que solo existen porque el compilador las reconoce de forma explícita.


Quería explorar un enfoque diferente.

¿Y si gran parte del azúcar sintáctico pudiera implementarse fuera del compilador?

Así nacieron los Transforms.


¿Qué es un Transform?

Un Transform es una regla declarativa que toma una construcción sintáctica y la reemplaza por otra antes de que el compilador comience el análisis semántico.


La idea es extremadamente simple.


Cuando encuentres esto...

        ↓

Reemplázalo por esto.



No ejecuta código.

No modifica el compilador.

No realiza reemplazos de texto.

Simplemente transforma una representación sintáctica en otra.

En artículos anteriores decidimos que el desarrollador podrá escribir:


let name: String?;


Internamente eso es equivalente a:


let name: Optional<String>;


En lugar de implementar esta característica directamente dentro del compilador, podemos definir un Transform.


transform Nullable {

    T?

        =>

    Optional<T>

}


A partir de ese momento, cualquier aparición de:


String?


será transformada automáticamente en:


Optional<String>


El compilador nunca necesita conocer el operador ?.



Supongamos que MateScript decide no incorporar arrays como estructura del lenguaje.

Sin embargo, muchos desarrolladores están acostumbrados a escribir:


Int[10]


Podemos definir otro Transform.


transform Arrays {

    T[N]

        =>

    ArrayList<T>(N)

}


El resultado será equivalente a:

ArrayList<Int>(10)



Nuevamente, el compilador no necesita incorporar una característica nueva.

Simplemente aplica una transformación.


Una decisión importante es mantener estas reglas separadas del código fuente.

Los archivos de MateScript utilizarán la extensión: .mt

Mientras que los Transforms vivirán en archivos independientes: .tmt


Por ejemplo:

src/

    main/

        matescript/

            Person.mt

            Main.mt

        transforms/

            nullable.tmt

            arrays.tmt

            collections.tmt


De esta manera, el código de la aplicación permanece limpio y las transformaciones pueden evolucionar de forma independiente.

No todos los Transforms tendrán que ser escritos por el desarrollador.

El propio SDK de MateScript incluirá un conjunto de transformaciones oficiales.


Por ejemplo:

  • Nullable (T? -> Optional<T>)
  • Colecciones
  • Rangos
  • Literales
  • Otras construcciones de alto nivel


El desarrollador simplemente las utilizará sin preocuparse por su implementación.


Lo realmente interesante aparece cuando cualquier biblioteca puede distribuir sus propios Transforms.

Imaginemos una librería para programación reactiva.

Podría incluir nuevas construcciones sintácticas sin modificar el compilador.

Lo mismo podría ocurrir con librerías para SQL, testing, concurrencia o cualquier otro dominio.

En lugar de esperar una nueva versión del lenguaje, la comunidad podría extenderlo mediante nuevos Transforms.


¿Son macros? No exactamente.

Cuando escuchamos la palabra macro, solemos pensar en sistemas capaces de ejecutar código durante la compilación o incluso modificar el propio compilador.

Ese no es el objetivo de MateScript.

Los transforms son mucho más simples.

Son reglas declarativas de transformación sintáctica.

No realizan reemplazos de texto.

No tienen acceso al sistema de archivos.

No ejecutan código arbitrario.

No cambian el comportamiento del compilador.

Su única responsabilidad es transformar una construcción sintáctica en otra equivalente.


Gracias a este mecanismo, el compilador incorpora una nueva etapa.


Código fuente (.mt)

        │

        ▼

      Lexer

        │

        ▼

      Parser

        │

        ▼

 AST de sintaxis

        │

        ▼

 Carga de Transforms

 (SDK + proyecto)

        │

        ▼

 Motor de Transforms

        │

        ▼

 AST transformado

        │

        ▼

 Análisis semántico

        │

        ▼

 Generación de bytecode


El analizador semántico siempre trabaja sobre un único modelo del lenguaje.

No necesita conocer las distintas formas sintácticas que utilizaron los desarrolladores.


Quizás el aspecto más interesante de esta idea sea filosófico.

En lugar de incorporar cientos de características al compilador, MateScript puede mantener un núcleo pequeño y estable.

Las construcciones de alto nivel pueden implementarse mediante Transforms reutilizables.

Esto hace que el lenguaje sea más fácil de mantener, más sencillo de aprender y mucho más flexible para evolucionar con el tiempo.


Los Transforms representan una de las ideas más ambiciosas de MateScript.

Permiten agregar azúcar sintáctico sin modificar el compilador, mantienen el lenguaje pequeño y ofrecen una forma sencilla para que tanto la SDK como la comunidad puedan extender el lenguaje.

En cierto modo, los Transforms convierten al compilador en una plataforma sobre la cual pueden construirse nuevas formas de escribir código, sin perder coherencia ni simplicidad.


Diseñando MateScript: Diseñando Object, Null y Optional


Cuando pensamos en un lenguaje de programación, solemos imaginar su sintaxis, su compilador o su sistema de tipos.

Sin embargo, existe otro componente igual de importante: la biblioteca estándar.


De poco sirve tener una sintaxis elegante si los tipos fundamentales del lenguaje no forman un modelo coherente.

En este artículo comenzaremos a diseñar tres de los tipos más importantes de MateScript:

  • Object
  • Null
  • Optional


Los tres estarán profundamente relacionados.

En el artículo anterior llegamos a una conclusión interesante.


Queríamos que el desarrollador pudiera escribir:

let name: String?;


Pero no queríamos que el operador ? fuera una característica mágica del compilador.

Por eso decidimos definirlo como un alias de tipos.

T?

es equivalente a:

Optional<T>


Y Optional<T> se implementa utilizando un Union Type.


class Optional<T> {

    value: T | Null;

}


Ahora la pregunta cambia.


¿Cómo deben comportarse Optional y Null?


Como MateScript se ejecutará sobre la JVM, tiene sentido adoptar un modelo de objetos similar al de Java.


Todos los objetos heredarán de una clase base.


class Object {


    equals(other: Object): Boolean


    hashCode(): Int


    toString(): String


}


Esto significa que cualquier objeto del lenguaje compartirá el mismo comportamiento básico.


Por ejemplo:

person.toString()

list.hashCode()

optional.equals(other)


No existen excepciones.

Todo es un objeto.


MateScript intenta reducir la cantidad de código repetitivo.

Por eso no será necesario escribir modificadores como:


public


private


protected


La regla será mucho más simple.

Los atributos serán privados por defecto.

Los métodos serán públicos por defecto.


Por ejemplo:


class Person {


    name: String

    age: Int


    greet() {

        print("Hello!")

    }

}


Es una sintaxis muy cercana a TypeScript, pero manteniendo un modelo de objetos inspirado en Java.


En muchos lenguajes, null es un valor especial incorporado por el compilador.

En MateScript queremos seguir un enfoque diferente.


Null será un objeto singleton.


object Null {


    toString(): String


    equals(other: Object): Boolean


    hashCode(): Int


}


Existirá una única instancia.

Como cualquier otro objeto del lenguaje.

No será un valor mágico.

No requerirá reglas especiales.


Cuando empezamos a diseñar Optional, apareció una idea interesante.

Supongamos el siguiente código.


let name: String? = ...


let upper =

    name.map(x => x.toUpperCase());


¿Qué ocurre si name no contiene ningún valor?

Una posibilidad sería lanzar una excepción.

Otra sería devolver un nuevo Optional.

Pero existe una alternativa mucho más elegante.

Que el propio objeto Null implemente el mismo comportamiento.


Por ejemplo:

Null.map(...)


simplemente devolvería:

Null


Lo mismo ocurre con:

Null.flatMap(...)

Null.filter(...)


Todas estas operaciones simplemente devolverían nuevamente Null.


Esto permite que el código continúe fluyendo de forma natural sin necesidad de realizar comprobaciones constantes.


¿Entonces map pertenece a Object?

Podría parecer una buena idea.

Después de todo, todos los objetos podrían responder al método map().

Sin embargo, rápidamente aparecen situaciones extrañas.

5.map(...)

person.map(...)

car.map(...)


¿Qué significa transformar un número mediante map()?

¿O una persona?


La realidad es que map() no representa una operación común a todos los objetos.

Representa una operación sobre estructuras que contienen un valor.


En lugar de agregar map() a Object, resulta más razonable definir una interfaz específica.

Conceptualmente podría verse así:


interface Functor<T> {


    map<R>(

        mapper: (T) -> R

    ): Functor<R>;


}


Esta idea proviene de la programación funcional y aparece en lenguajes como Haskell, Scala y muchos otros.


No es necesario conocer teoría de categorías para utilizarla.

La idea es muy sencilla.


Un Functor es cualquier estructura capaz de transformar el valor que contiene sin perder su estructura.


Ahora Optional puede implementar esa interfaz.


class Optional<T>

    implements Functor<T> {


    value: T | Null;


}


Cuando existe un valor, map() aplica la función recibida.

Cuando el valor es Null, simplemente devuelve Null.

Desde el punto de vista del desarrollador, el código siempre se escribe de la misma manera.


name

    .map(...)

    .map(...)

    .map(...)


Sin preguntar constantemente si existe un valor.


Lo interesante es que nunca planeé incorporar conceptos de programación funcional.

Simplemente estaba buscando una forma elegante de implementar Optional.

Sin embargo, al avanzar en el diseño comenzaron a aparecer naturalmente ideas como:

  • objetos singleton;
  • Union Types;
  • Functors.


Y eso es una de las cosas más fascinantes de diseñar un lenguaje.

Las buenas abstracciones suelen aparecer como consecuencia de resolver problemas reales, no como un objetivo en sí mismo.


En este punto, MateScript comienza a tener una identidad propia.

  • Todos los objetos heredan de Object.
  • Null deja de ser un valor mágico para convertirse en un objeto singleton.
  • Optional<T> representa explícitamente la posibilidad de ausencia mediante T | Null.
  • Las operaciones de transformación se modelan mediante interfaces específicas, manteniendo la clase Object pequeña y coherente.



domingo, 5 de julio de 2026

Diseñando MateScript: ¿Cómo representar la ausencia de un valor?


Uno de los aspectos más interesantes de diseñar un lenguaje de programación es descubrir que muchas de las características que damos por sentadas están profundamente relacionadas entre sí.

Un buen ejemplo es la representación de los valores nulos.

A simple vista parece una decisión menor. Sin embargo, termina afectando el sistema de tipos, el compilador, la biblioteca estándar e incluso el runtime del lenguaje.

En lugar de intentar inventar una solución completamente nueva, MateScript adopta una filosofía diferente: tomar buenas ideas de otros lenguajes y combinarlas de una forma consistente.


En este artículo aparecen varias influencias claras:

  • El operador ? de Kotlin y C#.
  • Los Union Types de TypeScript.
  • Los objetos singleton de Scala.


Lo interesante es que ninguna de estas ideas fue incorporada de forma aislada. Todas terminan formando parte de un único diseño.

Desde el punto de vista del desarrollador, me gusta mucho la sintaxis introducida por Kotlin y posteriormente adoptada por C#.


let name: String?;


La intención es evidente.

La variable puede no contener un valor.

Sin embargo, no me gusta que el operador ? sea una característica mágica del compilador.


Siempre que sea posible prefiero que las características del lenguaje puedan explicarse utilizando otros conceptos ya existentes. 

En lugar de convertir ? en una regla especial del compilador, podemos definirlo como un alias de tipos.


Es decir:

T?

es simplemente equivalente a:

Optional<T>


Por ejemplo:

let name: String?;


sería exactamente lo mismo que escribir:

let name: Optional<String>;


Una vez realizado este reemplazo, el compilador deja de conocer la existencia del operador ?.

Para el resto del proceso de compilación solamente existe Optional<T>.


Ahora aparece una pregunta mucho más interesante. ¿Cómo implementamos Optional<T>?


Podríamos imaginar algo como esto:


class Optional<T> {

    private value: T;

}


Pero inmediatamente encontramos un problema. ¿Cómo representamos un value vacío?

En Java utilizaríamos null. Pero MateScript no tendrá un valor mágico llamado null.

Entonces necesitamos otra solución.


La idea tomada de Scala es tratar a Null como un objeto singleton del lenguaje.


Conceptualmente:

object Null


Esto significa que existe una única instancia de Null, exactamente igual que cualquier otro singleton.

No es una palabra reservada.

No es un valor especial incorporado por el compilador.

Es simplemente un objeto del lenguaje.


Hasta aquí parece que el problema está resuelto.

Pero todavía queda una pregunta.

Si value puede contener un objeto de tipo T o el objeto Null, entonces su tipo ya no puede ser simplemente T.

Necesitamos representar que un valor puede pertenecer a más de un tipo.


Es decir: T | Null


Y casi sin buscarlo llegamos a otro concepto conocido. Los Union Types.


La idea proviene de TypeScript.

Un Union Type representa un valor que puede pertenecer a uno de varios tipos.


Por ejemplo:

String | Null


indica que un valor puede ser:

un objeto String, o

el objeto Null.


Gracias a esto podemos redefinir Optional.

class Optional<T> {

    private value: T | Null;

}


Ahora sí el modelo es completamente consistente.


Un Optional<String> puede contener un String o el objeto Null.

No existe ningún valor mágico dentro del lenguaje.

Todo está expresado mediante el sistema de tipos.


Lo interesante es que los Union Types no aparecieron porque quisiera agregarlos al lenguaje.

Surgieron como una consecuencia natural del diseño de Optional.


Una vez que el compilador fue capaz de representar tipos como: T | Null apareció una pregunta inevitable.

¿Por qué limitar esa capacidad únicamente a Optional?


Si el compilador ya sabe trabajar con Union Types, también puede permitir que los desarrolladores los utilicen directamente.


Por ejemplo:

let value: String | Int;

let result: Success | Error;

let animal: Dog | Cat;


Lo que comenzó siendo una solución para representar la ausencia de un valor terminó convirtiéndose en una nueva característica del lenguaje.


Curiosamente, cada una de estas decisiones resuelve un problema distinto, pero juntas forman un diseño mucho más consistente.


El desarrollador escribe:

String?


El parser lo transforma en:

Optional<String>


Y Optional se implementa como:

Optional<T>

    └── value : T | Null


Todo utilizando conceptos generales del lenguaje.


No existen excepciones.

No existen reglas especiales.

No existen valores mágicos.


Diseñar un lenguaje muchas veces consiste en descubrir que una buena solución termina resolviendo varios problemas al mismo tiempo.


En MateScript decidimos combinar ideas provenientes de distintos lenguajes:

  • De Kotlin y C# tomamos la sintaxis ? para representar tipos opcionales.
  • De TypeScript adoptamos los Union Types para representar valores que pueden pertenecer a más de un tipo.
  • De Scala incorporamos la idea de los objetos singleton para representar Null como un objeto del lenguaje y no como un valor mágico del compilador.


Lo interesante es que ninguna de estas características fue agregada por separado.

Cada decisión fue llevando naturalmente a la siguiente, hasta formar un sistema de tipos más uniforme y coherente.



viernes, 3 de julio de 2026

Diseñando MateScript: un JavaScript de tipado estático para la JVM


Hace tiempo que me interesa el diseño de lenguajes de programación.

No solamente cómo construir compiladores, sino también cómo tomar decisiones sobre la sintaxis, el sistema de tipos y la experiencia de desarrollo.

Por eso decidí comenzar un proyecto experimental: MateScript.


¿Qué es MateScript?

MateScript es un lenguaje inspirado en JavaScript, pero con tipado estático y compilación a bytecode JVM.

JavaScript es uno de los lenguajes más populares del mundo, pero también arrastra varios problemas derivados de sus decisiones históricas.


Por ejemplo:

let age = 42;

age = "hola";


Esto es perfectamente válido.

MateScript buscará detectar estos problemas durante la compilación.


let age = 42;

age = "hola";


Resultado:

Type mismatch: expected Int found String


MateScript intentará mantener las características más atractivas de JavaScript:

  • Sintaxis sencilla
  • Funciones de primera clase
  • Lambdas
  • Inferencia de tipos
  • Programación funcional
  • Objetos


Pero agregando:

  • Tipado estático
  • Null Safety
  • Pattern Matching
  • Compilación a bytecode JVM
  • Interoperabilidad con Java


¿Por qué no usar TypeScript?

TypeScript es una excelente herramienta.

Sin embargo, sigue dependiendo del ecosistema JavaScript.


El objetivo de MateScript es diferente:

  • Compilar directamente a bytecode JVM
  • Ejecutarse sin Node.js
  • Integrarse con bibliotecas Java
  • Aprovechar el rendimiento y madurez de la plataforma JVM


Primeras decisiones de diseño

Variables inmutables por defecto

let name = "Emanuel";


Una variable declarada con let no podrá modificarse.


Para variables mutables utilizaremos:

var counter = 0;


Inferencia de tipos

No será necesario declarar tipos explícitamente.


let age = 42;


El compilador inferirá:

Int


Funciones

La sintaxis será muy similar a JavaScript.


function greet(name: String): String {

    return "Hola " + name;

}


Lambdas

let square = x => x * x;


Null Safety

Los tipos no aceptarán valores nulos por defecto.

let name: String = null;

Error de compilación.


Para permitir nulos:

let name: String?;


Y acá podemos hacer algo bastante elegante, podemos hacer que los tipos tengan alias por lo tanto : 

String? va a ser -> Optional<String> 


Ahora bien, como hago Optinal<T> , acá podemos utilizar el camino que usa Typescript union de tipos : T|Null y podemos hacer que Null sea un object como Scala. Y no vamos a tener nulos en nuestro lenguaje. 


¿Por qué la JVM?

La JVM es una de las plataformas más maduras de la industria.

  • Proporciona:
  • Garbage Collector
  • JIT Compiler
  • Ecosistema enorme
  • Bibliotecas maduras
  • Herramientas de monitoreo


En lugar de construir una máquina virtual propia, MateScript aprovechará toda esa infraestructura.


¿Qué sigue?

En los próximos artículos comenzaremos la implementación de MateScript.

El primer paso será construir una gramática utilizando ANTLR capaz de reconocer programas simples como:


let name = "Emanuel";

print(name);


A partir de allí iremos avanzando gradualmente hacia un compilador completo capaz de generar bytecode JVM.


La meta no es crear el próximo Kotlin. La meta es aprender cómo se construyen realmente los lenguajes de programación.


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 ;)