Translate

lunes, 22 de septiembre de 2025

Records en Java: Clases Inmutables de Forma Sencilla



A partir de Java 14 (como preview) y de forma estable en Java 16, se introdujeron los records.

Un record es una forma concisa de declarar clases inmutables que sirven principalmente para transportar datos (clases DTO, value objects, etc.).

La sintaxis básica es:

public record Persona(String nombre, int edad) {}


Esto genera automáticamente:

  • Los campos privados y finales nombre y edad.
  • Un constructor que inicializa esos campos.
  • Los métodos getters nombre() y edad().
  • Una implementación de toString().
  • Métodos equals() y hashCode() basados en los campos.


Veamos un ejemplo de uso: 


public class Main {

    public static void main(String[] args) {

        Persona p = new Persona("Ana", 30);


        System.out.println(p.nombre()); // "Ana"

        System.out.println(p.edad());   // 30

        System.out.println(p);          // Persona[nombre=Ana, edad=30]

    }

}


Notá que no se usan getNombre() ni getEdad(), sino directamente nombre() y edad().

Los campos de un record son inmutables (implícitamente final).

No existe p.setEdad(31) → en su lugar, deberías crear un nuevo objeto Persona.


Aunque el record genera mucho código automáticamente, también podés definir métodos adicionales:


public record Persona(String nombre, int edad) {

    public boolean esMayorDeEdad() {

        return edad >= 18;

    }

}


Persona juan = new Persona("Juan", 17);

System.out.println(juan.esMayorDeEdad()); // false


Podés agregar lógica en el constructor, pero siempre respetando la inicialización de todos los campos:


public record Persona(String nombre, int edad) {

    public Persona {

        if (edad < 0) {

            throw new IllegalArgumentException("La edad no puede ser negativa");

        }

    }

}


Este es el constructor compacto: no es necesario repetir la asignación de campos, Java lo hace automáticamente.


Los records funcionan muy bien como DTOs, por ejemplo en listas o streams:


List<Persona> personas = List.of(

    new Persona("Ana", 30),

    new Persona("Luis", 25)

);


personas.stream()

    .filter(p -> p.edad() > 26)

    .forEach(System.out::println);


En Resumen: 

  • Los records simplifican la creación de clases para transportar datos.
  • Generan automáticamente: constructor, getters, equals, hashCode y toString.
  • Son inmutables por diseño.
  • Se pueden añadir métodos y validaciones de construcción.


Son ideales para DTOs, value objects y cualquier situación en la que antes usabas una clase con solo atributos y getters.

sábado, 20 de septiembre de 2025

Registros en Elm


En Elm, un registro es una colección de pares clave = valor, similar a un objeto en otros lenguajes, pero inmutable y tipado.

Son muy usados para agrupar datos de manera clara y segura.


persona : { nombre : String, edad : Int }

persona =

    { nombre = "Ana", edad = 30 }


  • nombre es de tipo String.
  • edad es de tipo Int.


nombreDeAna : String

nombreDeAna =

    persona.nombre

-- Resultado: "Ana"


También se puede usar una función anónima para acceder:


List.map .nombre [ persona, { nombre = "Luis", edad = 25 } ]

-- ["Ana", "Luis"]


Los registros en Elm son inmutables.

Para "cambiar" un campo, se crea una copia con el campo modificado:


personaMayor : { nombre : String, edad : Int }

personaMayor =

    { persona | edad = 31 }


Esto no altera persona, sino que genera un nuevo registro.

Las funciones pueden recibir registros directamente:


saludar : { nombre : String } -> String

saludar r =

    "Hola, " ++ r.nombre

saludar persona

-- "Hola, Ana"


Cuando un registro es usado en varios lugares, conviene definir un alias de tipo:


type alias Persona =

    { nombre : String

    , edad : Int

    }


juan : Persona

juan =

    { nombre = "Juan", edad = 40 }


cumplirAnios : Persona -> Persona

cumplirAnios p =

    { p | edad = p.edad + 1 }


Podemos "abrir" un registro en sus campos:


mostrar : Persona -> String

mostrar { nombre, edad } =

    nombre ++ " tiene " ++ String.fromInt edad ++ " años"

mostrar juan

-- "Juan tiene 40 años"


En Resumen:

  • Un registro agrupa datos con nombres de campo.
  • Son inmutables, para "modificarlos" se usa la sintaxis { r | campo = nuevoValor }.
  • Los alias de tipo (type alias) hacen el código más claro.
  • Se puede usar desestructuración para acceder fácilmente a los campos.


En Elm, los registros reemplazan el concepto de objetos de otros lenguajes, pero sin herencia ni mutabilidad.

viernes, 19 de septiembre de 2025

10 formas de generar valor con IA


Copyright © 2025 Oracle y sus asociados. Todos los derechos reservados. Oracle Corporation - Worldwide Headquarters, 2300 Oracle Way, Austin, TX 78741, United States
Oracle

 

martes, 16 de septiembre de 2025

Tipos Genéricos en Elm


En Elm, al igual que en muchos lenguajes funcionales, los tipos genéricos permiten escribir funciones y estructuras de datos que funcionan con diferentes tipos sin necesidad de duplicar código.

Un tipo genérico se indica con letras minúsculas como a, b, c, etc.

Veamos un ejemplo simple:


identity : a -> a

identity x =

    x


Aquí, la función identity recibe un valor de cualquier tipo a y lo devuelve.

  • Si le paso un Int, devuelve un Int.
  • Si le paso un String, devuelve un String.


identity 42

-- Resultado: 42

identity "hola"

-- Resultado: "hola"


Las listas en Elm también son genéricas.

Su tipo es List a, donde a puede ser cualquier tipo.


longitud : List a -> Int

longitud lista =

    List.length lista


longitud puede recibir:

longitud [1, 2, 3]          -- funciona con List Int

longitud ["a", "b", "c"]    -- funciona con List String


En ambos casos la función devuelve la cantidad de elementos, sin importar de qué tipo sean.


Las tuplas también soportan tipos genéricos:


swap : (a, b) -> (b, a)

swap (x, y) =

    (y, x)


Ejemplo de uso:


swap (1, "uno")

-- Resultado: ("uno", 1)


swap (True, 3.14)

-- Resultado: (3.14, True)


Podemos crear estructuras más complejas con varios genéricos:


maybeFirst : List a -> Maybe a

maybeFirst lista =

    case lista of

        [] ->

            Nothing


        x :: _ ->

            Just x


  • Si la lista está vacía, devuelve Nothing.
  • Si tiene elementos, devuelve Just el primero.


Funciona con cualquier tipo de lista:


maybeFirst [1, 2, 3]       -- Just 1

maybeFirst ["a", "b"]      -- Just "a"

maybeFirst []              -- Nothing


Los tipos genéricos se escriben con letras minúsculas (a, b, c).

  • Permiten que funciones y estructuras trabajen con cualquier tipo.
  • Están presentes en funciones (identity), listas (List a), tuplas ((a, b)), y en tipos como Maybe a.
  • Nos ayudan a escribir código más reutilizable y flexible.



domingo, 14 de septiembre de 2025

WebSocket Scope en Spring



Spring Framework nos ofrece diferentes scopes para manejar el ciclo de vida de los beans: singleton, prototype, request, session, etc.

Pero cuando trabajamos con WebSockets, entramos en un contexto distinto al clásico HTTP. Aquí, cada cliente mantiene una conexión persistente, y necesitamos un scope que nos permita almacenar estado por sesión WebSocket.

Para eso existe el WebSocket Scope.

El WebSocket Scope es un alcance especial que Spring habilita cuando se trabaja con @EnableWebSocket o @EnableWebSocketMessageBroker.

Permite que un bean viva mientras dure la conexión WebSocket de un cliente específico.

Es decir:

  • Cada cliente WebSocket obtiene su propia instancia del bean.
  • Cuando la conexión WebSocket se cierra, ese bean se destruye automáticamente.

Spring Boot ya trae soporte para WebSockets. Para usar el WebSocket Scope hay que agregar la anotación @Scope("websocket") sobre el bean.


import org.springframework.context.annotation.Scope;

import org.springframework.stereotype.Component;


@Component

@Scope("websocket")

public class WebSocketSessionBean {


    private int counter = 0;


    public int incrementAndGet() {

        counter++;

        return counter;

    }

}


En este ejemplo, cada cliente WebSocket tiene su propio contador independiente.

Podemos inyectar el bean con scope websocket en un controlador:


import org.springframework.messaging.handler.annotation.MessageMapping;

import org.springframework.stereotype.Controller;


@Controller

public class ChatController {


    private final WebSocketSessionBean sessionBean;


    public ChatController(WebSocketSessionBean sessionBean) {

        this.sessionBean = sessionBean;

    }


    @MessageMapping("/message")

    public String handleMessage(String message) {

        int count = sessionBean.incrementAndGet();

        return "Mensaje #" + count + ": " + message;

    }

}


Aquí:

  • Cada vez que el mismo cliente envía un mensaje, se incrementa su contador privado.
  • Otro cliente tiene su propio contador independiente.


Ciclo de vida

  • Inicio: el bean se crea cuando el cliente establece la conexión WebSocket.
  • Destrucción: se elimina automáticamente cuando la conexión se cierra.

Esto lo hace ideal para manejar estado ligado a una sesión WebSocket, sin necesidad de usar mapas estáticos o manejar manualmente IDs de sesión.


El WebSocket Scope en Spring nos permite manejar estado de forma segura y aislada por conexión.

Es útil para casos como:

  • Chats con información por usuario.
  • Juegos multijugador donde cada sesión mantiene su progreso.
  • Aplicaciones colaborativas en tiempo real.


Con esta herramienta, Spring hace más simple mantener datos por cliente en arquitecturas basadas en WebSockets.


Dejo link:  

https://docs.spring.io/spring-framework/reference/web/websocket/stomp/scope.html

Lambdas en Elm


En Elm, las lambdas (o funciones anónimas) son una herramienta fundamental para escribir código conciso y expresivo. Una lambda es simplemente una función sin nombre, definida directamente en el lugar donde se necesita.

La sintaxis de una lambda es:

\param1 param2 -> expresion


Por ejemplo:

\x -> x + 1


Es una función que recibe un número x y devuelve x + 1.


Con más de un parámetro:

\a b -> a + b


Las lambdas son muy útiles cuando trabajamos con funciones como map, filter o fold:


List.map (\x -> x * 2) [1,2,3]

-- Resultado: [2,4,6]


List.filter (\x -> x > 5) [3,7,1,8]

-- Resultado: [7,8]


Las lambdas pueden capturar variables de su entorno, convirtiéndose en clausuras:


sumarN : Int -> List Int -> List Int

sumarN n lista =

    List.map (\x -> x + n) lista


sumarN 5 [1,2,3]

-- Resultado: [6,7,8]


Acá la lambda \x -> x + n recuerda el valor de n aunque se ejecute después.

En Elm, los operadores son funciones. Esto significa que:


(+) 3 4 -- 7

(<) 2 5 -- True


Y en lugar de escribir:

\a b -> a < b


Podés usar directamente:

(<)


Ejemplo:

List.sortWith (<) [3,1,2]

-- Resultado: [1,2,3]


Todas las funciones en Elm son curried. Esto significa que se pueden aplicar parcialmente:


(>) 10

-- Es equivalente a \x -> 10 > x


List.filter ((>) 5) [1,7,3,9]

-- Resultado: [1,3]


Las lambdas en Elm hacen que trabajar con funciones de orden superior sea simple y elegante. Entre la posibilidad de capturar variables (clausuras), usar operadores como funciones y aplicar parcialmente, Elm ofrece un estilo de programación muy expresivo y seguro.

jueves, 11 de septiembre de 2025

Listas en Elm: Una Guía Práctica


En Elm, las listas son una de las estructuras de datos más utilizadas. Son colecciones inmutables de elementos del mismo tipo, y ofrecen muchas funciones para trabajar con ellas de forma segura y declarativa.

Podés crear una lista escribiendo los elementos entre corchetes [] separados por comas:


numeros : List Int

numeros = [1, 2, 3, 4, 5]


palabras : List String

palabras = ["hola", "elm", "listas"]


Una lista vacía se define como:


vacia : List Int

vacia = []


Acceder al primer elemento


List.head [1,2,3] -- Just 1


 Acceder al resto de la lista


List.tail [1,2,3] -- Just [2,3]


Largo de la lista


List.length [1,2,3] -- 3


Concatenar listas


[1,2] ++ [3,4] -- [1,2,3,4]


Aplica una función a cada elemento:


List.map (\x -> x * 2) [1,2,3] -- [2,4,6]


Filtra elementos que cumplen una condición:


List.filter (\x -> x > 2) [1,2,3,4] -- [3,4]


Reduce una lista a un único valor:


List.foldl (+) 0 [1,2,3,4] -- 10


Elm ofrece el operador :: para construir listas agregando un elemento al inicio:


1 :: [2,3,4] -- [1,2,3,4]


"hola" :: ["elm", "listas"] -- ["hola","elm","listas"]


Ejemplo completo :


numeros : List Int

numeros = [1, 2, 3, 4, 5]


pares : List Int

pares = List.filter (\n -> modBy 2 n == 0) numeros


cuadrados : List Int

cuadrados = List.map (\n -> n * n) 


main =

    Debug.log "Pares" pares

    |> Debug.log "Cuadrados" cuadrados


Las listas en Elm son poderosas, seguras y fáciles de manipular gracias a la librería estándar. Una vez que domines map, filter y fold, vas a poder expresar la mayoría de las transformaciones sobre colecciones de forma clara y concisa.




martes, 9 de septiembre de 2025

Introducción a Borgo Programming Language


Ya hablamos de Borgo y en este post vamos a recorrer paso a paso cómo empezar a trabajar con él, desde la instalación hasta la ejecución de un programa sencillo. 

Actualmente, Borgo está disponible como un compilador ligero que se distribuye en GitHub. Para instalarlo:


# Clonar el repositorio oficial

git clone https://github.com/borgo-lang/borgo.git

cd borgo

# Compilar el compilador

make

# Instalar en tu sistema (requiere permisos de administrador)

sudo make install


Una vez instalado, podés comprobar la versión con:

borgoc --version


Un proyecto en Borgo no necesita estructura compleja. Podés empezar creando una carpeta y un archivo de código fuente:


mkdir mi_proyecto

cd mi_proyecto

touch main.borgo


El clásico primer ejemplo. En Borgo, un "Hola Mundo" se vería así:


fn main() {

    print("Hola, mundo!")

}


Para compilar tu programa:

borgoc main.borgo -o main


Esto genera un ejecutable llamado main. Para ejecutarlo:


./main


Y deberías ver:

Hola, mundo!


Podemos sumar dos números y mostrar el resultado:


fn suma(a: Int, b: Int): Int {

    return a + b

}


fn main() {

    let resultado = suma(3, 4)

    print("El resultado es: ", resultado)

}


Compilás y ejecutás de la misma manera:


borgoc main.borgo -o main

./main


Salida:

El resultado es: 7


Borgo es un lenguaje minimalista pero potente, con una curva de aprendizaje muy baja. Lo interesante es que te permite escribir y compilar programas rápidamente, sin demasiada configuración.

domingo, 7 de septiembre de 2025

Comandos y suscripciones

 Anteriormente vimos cómo la arquitectura Elm gestiona las interacciones del ratón y el teclado, pero ¿qué ocurre con la comunicación con los servidores? ¿Con la generación de números aleatorios?

Para responder a estas preguntas, es útil comprender mejor cómo funciona la arquitectura Elm en segundo plano. Esto explicará por qué las cosas funcionan de forma ligeramente diferente a lenguajes como JavaScript, Python, etc.


Sandbox

No le he dado mucha importancia, pero hasta ahora todos nuestros programas se crearon con Browser.sandbox. Proporcionamos un modelo inicial y describimos cómo actualizarlo y visualizarlo.

Puedes imaginar Browser.sandbox como la configuración de un sistema como este:



Nos mantenemos en el mundo de Elm, escribiendo funciones y transformando datos. Esto se conecta al sistema de ejecución de Elm. Este sistema determina cómo renderizar HTML eficientemente. ¿Ha cambiado algo? ¿Cuál es la modificación mínima del DOM necesaria? También detecta cuándo alguien hace clic en un botón o escribe en un campo de texto. Lo convierte en un mensaje y lo introduce en el código de Elm.

Al separar claramente toda la manipulación del DOM, es posible utilizar optimizaciones extremadamente agresivas. Por lo tanto, el sistema de ejecución de Elm es una de las razones principales por las que Elm es una de las opciones más rápidas disponibles.


element

En los siguientes ejemplos, usaremos Browser.element para crear programas. Esto introducirá los conceptos de comandos y suscripciones que nos permiten interactuar con el mundo exterior.

Puedes imaginar Browser.element como la configuración de un sistema como este:


Además de generar valores HTML, nuestros programas también enviarán valores Cmd y Sub al sistema de ejecución. En este contexto, nuestros programas pueden ordenar al sistema de ejecución que realice una solicitud HTTP o genere un número aleatorio. También pueden suscribirse a la hora actual.

Creo que los comandos y las suscripciones cobran más sentido al ver ejemplos, por lo tanto, en los próximos posts veremos ejemplos. 



sábado, 6 de septiembre de 2025

Patrones de Diseño Comunes en el Uso de Colas



En sistemas distribuidos y arquitecturas modernas, el uso de colas de mensajes (Message Queues) se ha vuelto fundamental. Una cola permite que distintos procesos o servicios se comuniquen de forma asíncrona, desacoplada y tolerante a fallos.

Pero más allá de la idea básica, a lo largo del tiempo han surgido patrones de diseño comunes que nos ayudan a organizar y resolver problemas típicos en el uso de colas.


Productor – Consumidor

Es el patrón más simple:

  • Un productor envía mensajes a la cola.
  • Un consumidor los procesa en orden.

Ventajas: desacopla quién genera la tarea de quién la resuelve.

Ejemplo: un servicio de e-commerce que recibe pedidos (productor) y un servicio de facturación que los procesa (consumidor).


Work Queue (Cola de trabajo)

Extiende el patrón anterior:

  • Varios consumidores leen de la misma cola.
  • Los mensajes se reparten entre ellos.

Ventajas: balanceo de carga automático.

Ejemplo: procesamiento de imágenes en paralelo por múltiples workers.


Publish – Subscribe (Pub/Sub)

Un mensaje publicado puede llegar a muchos consumidores al mismo tiempo.

Cada suscriptor recibe su propia copia.

Ventajas: facilita arquitecturas basadas en eventos.

Ejemplo: al registrarse un nuevo usuario, se notifican:

  • el servicio de email de bienvenida,
  • el sistema de analíticas,
  • y el motor de recomendaciones.


Routing (Enrutamiento de mensajes)

Los mensajes llevan una clave de enrutamiento, y solo ciertos consumidores los reciben.

Ventajas: flexibilidad en la distribución de tareas.

Ejemplo:

Clave email → va al microservicio de correo.

Clave sms → va al microservicio de mensajería.


Priority Queue (Cola con prioridad)

Los mensajes tienen prioridad, y los más urgentes se procesan primero.

Ventajas: asegura la atención de los eventos críticos.

Ejemplo: en un sistema de soporte, los tickets urgentes se atienden antes que los normales.


Dead Letter Queue (Cola de mensajes fallidos)

Cuando un mensaje no puede ser procesado correctamente después de varios intentos, se envía a una cola especial de errores.

Ventajas: no se pierden mensajes y se pueden auditar.

Ejemplo: una orden de compra inválida que requiere revisión manual.


Delayed Queue (Cola con retardo)

Los mensajes no se procesan inmediatamente, sino que quedan en espera hasta que pasa cierto tiempo.

Ventajas: permite programar tareas futuras.

Ejemplo: enviar un recordatorio de pago 24 horas antes del vencimiento.


Las colas no son solo un mecanismo para mover datos entre procesos:

  • Implementan patrones que ayudan a construir sistemas robustos, escalables y fáciles de mantener.
  • Elegir el patrón correcto (Productor-Consumidor, Work Queue, Pub/Sub, Routing, etc.) puede simplificar la arquitectura y mejorar el rendimiento.


En el mundo de los microservicios y las arquitecturas event-driven, los patrones de diseño con colas son una pieza clave del rompecabezas.

jueves, 4 de septiembre de 2025

Uso de la cláusula WITH de oracle


Cuando trabajamos con consultas SQL complejas en Oracle, a menudo tenemos que repetir subconsultas o escribir expresiones largas que hacen que el código sea difícil de leer y mantener.

Para resolver esto, Oracle nos ofrece la cláusula WITH, también conocida como subquery factoring.

La cláusula WITH permite definir subconsultas temporales que luego pueden ser utilizadas dentro de una consulta principal.

Es como declarar variables en programación: escribimos la subconsulta una sola vez, le damos un nombre, y la reutilizamos en la consulta final.

Veamos un ejemplo básico

Supongamos que tenemos una tabla ventas con las columnas:

  • id
  • producto
  • cantidad
  • precio


Queremos obtener el total de ventas por producto y luego filtrar aquellos productos con ventas mayores a 1000.


WITH ventas_totales AS (

    SELECT producto,

           SUM(cantidad * precio) AS total

    FROM ventas

    GROUP BY producto

)

SELECT producto, total

FROM ventas_totales

WHERE total > 1000;


1. En la parte del WITH definimos ventas_totales como una subconsulta.

2. Luego usamos ventas_totales como si fuera una tabla en la consulta principal.


Las ventajas son:

  • Hace que las consultas largas sean más legibles.
  • Evita duplicar subconsultas.
  • Permite estructurar consultas de forma modular.


Podemos definir más de una subconsulta en el mismo WITH:


WITH

    ventas_totales AS (

        SELECT producto, SUM(cantidad * precio) AS total

        FROM ventas

        GROUP BY producto

    ),

    productos_top AS (

        SELECT producto

        FROM ventas_totales

        WHERE total > 1000

    )

SELECT p.producto, v.total

FROM productos_top p

JOIN ventas_totales v ON p.producto = v.producto;


Aquí usamos dos subconsultas (ventas_totales y productos_top) y las combinamos en la consulta final.

Desde Oracle 11g, la cláusula WITH también soporta consultas recursivas, muy útiles para recorrer jerarquías (como empleados y jefes, o categorías anidadas).

Ejemplo:


WITH empleados_recursivo (id, nombre, manager_id, nivel) AS (

    SELECT id, nombre, manager_id, 1

    FROM empleados

    WHERE manager_id IS NULL

    UNION ALL

    SELECT e.id, e.nombre, e.manager_id, er.nivel + 1

    FROM empleados e

    JOIN empleados_recursivo er ON e.manager_id = er.id

)

SELECT *

FROM empleados_recursivo;


Esto nos da una vista jerárquica de empleados y sus jefes, similar a CONNECT BY, pero más flexible.


Conclusión:

La cláusula WITH en Oracle es una herramienta poderosa para:

  • Hacer más claras las consultas complejas.
  • Reutilizar resultados intermedios.
  • Explorar jerarquías con recursividad.


Cuando tus consultas SQL se vuelvan largas o difíciles de leer, pensá en usar WITH para organizarlas mejor.


martes, 2 de septiembre de 2025

Informe de errores en Elm


Quizás tengamos un sitio web donde la gente introduzca su edad. Podríamos comprobar si la edad es razonable con una función como esta:

isReasonableAge : String -> Result String Int

isReasonableAge input =

  case String.toInt input of

    Nothing ->

      Err "That is not a number!"


    Just age ->

      if age < 0 then

        Err "Please try again after you are born."


      else if age > 135 then

        Err "Are you some kind of turtle?"


      else

        Ok age


-- isReasonableAge "abc" == Err ...

-- isReasonableAge "-13" == Err ...

-- isReasonableAge "24"  == Ok 24

-- isReasonableAge "150" == Err ...


No solo podemos comprobar la edad, sino que también podemos mostrar mensajes de error según los detalles de la entrada. ¡Este tipo de retroalimentación es mucho mejor que nada!

El tipo Result también puede ayudarte a recuperarte de errores. Esto se observa al realizar solicitudes HTTP. Supongamos que queremos mostrar el texto completo de Ana Karenina de León Tolstói. Nuestra solicitud HTTP genera una cadena de error de resultado para indicar que la solicitud puede tener éxito con el texto completo o puede fallar de diversas maneras:


type Error

= BadUrl String

| Timeout

| NetworkError

| BadStatus Int

| BadBody String


-- Ok "All happy ..." : Result Error String

-- Err Timeout : Result Error String

-- Err NetworkError : Result Error String

A partir de ahí, podemos mostrar mensajes de error más atractivos, como ya comentamos, pero también podemos intentar recuperarnos del fallo. Si vemos un Timeout, puede que funcione esperar un poco e intentarlo de nuevo. Mientras que si vemos un BadStatus 404, no tiene sentido volver a intentarlo.


Result de Elm


El tipo Maybe puede ser útil con funciones simples que podrían fallar, pero no indica el motivo. Imagina que un compilador simplemente indicara "Nothing" si algo fallaba en tu programa. ¡Buena suerte intentando averiguar qué falló!

Aquí es donde el tipo Result resulta útil. Se define así:


type Result error value

  = Ok value

  | Err error


El objetivo de este tipo es proporcionar información adicional cuando algo falla. Es muy útil para el informe y la recuperación de errores.