Translate

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

lunes, 6 de julio de 2026

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.



miércoles, 1 de julio de 2026

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.


domingo, 14 de junio de 2026

Mundial de Lenguajes de Programación



Le pregunte a la IA que onda si hacemos partidos de diferentes tecnologías con diferentes lenguajes, y este fue el resultado : 



Partido 1: Aplicaciones Web

Java vs C#


🇯🇵 Java sale con Spring Boot.

🇺🇸 C# responde con ASP.NET Core.


Ambos tienen experiencia internacional, planteles maduros y una enorme cantidad de títulos.

Resultado: Empate 1-1.


> Si tenés que hacer una API corporativa, cualquiera de los dos te lleva a octavos.


 🏟️ Partido 2: Concurrencia

Elixir vs Go


🇧🇷 Elixir apuesta por la posesión y el juego asociado con actores y supervisores.

🇩🇪 Go juega más directo con goroutines y channels.


Elixir domina el mediocampo cuando aparecen millones de procesos concurrentes.

Resultado: Elixir 2 - Go 1.

La BEAM sigue siendo una de las ligas más difíciles de enfrentar.


 🏟️ Partido 3: Aplicaciones Standalone

C++ vs Rust


🇮🇹 C++ es el veterano con ocho mundiales encima.

🇳🇱 Rust llega con una generación joven y una defensa impecable.


Rust reduce errores, pero C++ todavía tiene una experiencia enorme.

Resultado: C++ 1 - Rust 1.

La leyenda sigue vigente, pero la promesa pide pista.


 🏟️ Partido 4: Cloud

Java vs Node.js


🇦🇷 Java viene con GraalVM y Spring Boot.

🇪🇸 Node.js juega rápido y con poco peso.


En microservicios y aplicaciones cloud modernas, ambos se sienten cómodos.

Resultado: Empate.

El VAR determina que la elección depende más del equipo que del lenguaje.


🏟️ Partido 5: Inteligencia Artificial

Python vs el resto del mundo


🇫🇷 Python entra a la cancha.

Resultado: Python 5 - 0.

Hubo partido durante cinco minutos.


 🏟️ Partido 6: Programación Funcional

Scala vs Haskell

🇺🇾 Scala combina técnica y pragmatismo.

🇭🇷 Haskell juega un fútbol hermoso que pocos entienden.


Resultado: Scala 2 - Haskell 1.

La posesión fue 90% para Haskell, pero Scala convirtió las que tuvo.


🏟️ Partido 7: Frontend

TypeScript vs JavaScript


🇵🇹 JavaScript es el capitán histórico.

🇵🇹 TypeScript es el mismo equipo, pero con preparador físico.

Resultado: TypeScript 3 - JavaScript 1.

 Los errores en tiempo de compilación fueron la figura del partido.


 🏟️ Partido 8: Bases de Datos

PostgreSQL vs MySQL


🇨🇴 PostgreSQL juega con mucha técnica.

🇲🇽 MySQL apuesta por la experiencia.


Resultado: PostgreSQL 2 - MySQL 1.

PostgreSQL ganó por posesión y calidad de juego.


Y listo, no hice mucho, ni dice mucho pero es divertido :P 

miércoles, 20 de mayo de 2026

std::optional en C++


En muchos lenguajes modernos existe alguna forma de representar “un valor que puede no existir”:

  • Optional en Java
  • Option en Scala
  • Maybe en Haskell
  • Option en Rust (Some/None)


En C++, la respuesta oficial es std::optional, introducido en C++17 y mejorado muchísimo en C++23 con operaciones monádicas.

¿Qué problema resuelve?

Antes de optional, era común devolver:

  • nullptr
  • valores mágicos (-1)
  • flags adicionales
  • excepciones


Ejemplo clásico:


int findUserId(const std::string& name) {

    if(name == "emanuel")

        return 10;

    return -1;

}


Problema:

  • -1 no expresa claramente ausencia
  • alguien podría olvidarse de validarlo
  • el contrato del método no es explícito


Con std::optional:


#include <optional>


std::optional<int> findUserId(const std::string& name) {

    if(name == "emanuel")

        return 10;


    return std::nullopt;

}


Ahora el tipo expresa claramente: “puede devolver un entero… o no”.


Crear un optional

std::optional<int> number = 10;


Vacío:

std::optional<int> empty = std::nullopt;


Verificar si tiene valor

if(number.has_value()) {

    std::cout << "Tiene valor";

}


o más idiomático:

if(number) {

    std::cout << "Tiene valor";

}


Obtener el valor

std::cout << number.value();


Ojo! Si no tiene valor, lanza excepción.


Más seguro:

if(number) {

    std::cout << *number;

}


Valor por defecto

std::optional<int> x;

std::cout << x.value_or(0);


Resultado: 0


Muy parecido a:

  • orElse() en Java
  • getOrElse() en Scala


¿Por qué es mejor que punteros?

Muchas APIs antiguas usan punteros nullable:

User* findUser();


Problemas:

  • ownership ambiguo
  • riesgo de dangling pointers
  • semántica poco clara


Con optional:

std::optional<User> findUser();


El contrato queda explícito y seguro.

En programación funcional, una mónada permite:

  • encadenar operaciones
  • evitar checks manuales
  • propagar automáticamente ausencia/error


Antes de C++23 esto era incómodo.


Había que hacer:

if(result) {

    ...

}


todo el tiempo.


C++23 agregó operaciones monádicas oficiales.

transform

Equivalente a map.

Transforma el valor si existe.


std::optional<int> number = 10;


auto result =

    number.transform([](int x) {

        return x * 2;

    });


std::cout << *result;


Resultado: 20

Si el optional está vacío, no ejecuta nada.


and_then

Equivalente a flatMap.

Permite encadenar funciones que devuelven optional.


std::optional<int> parse(const std::string& s) {

    if(s == "42")

        return 42;


    return std::nullopt;

}


auto result =

    parse("42")

        .and_then([](int x) -> std::optional<int> {

            if(x > 0)

                return x * 2;


            return std::nullopt;

        });


Diferencia entre transform y and_then

transform convierte:

optional<T> -> optional<U>

cuando la función devuelve un valor normal, and_then convierte:

optional<T> -> optional<U>


pero la función YA devuelve optional.

Evita nested optionals: optional<optional<int>>


or_else

Permite ejecutar lógica si está vacío.


std::optional<int> value;


value.or_else([] {

    std::cout << "No había valor";

    return std::optional<int>{0};

});


Ahora podemos escribir código mucho más declarativo:


auto result =

    parse("42")

        .transform([](int x) {

            return x * 2;

        })

        .and_then([](int x) -> std::optional<int> {

            if(x < 100)

                return x;


            return std::nullopt;

        })

        .or_else([] {

            return std::optional<int>{0};

        });


Esto ya se parece muchísimo a:

  • Scala
  • Haskell
  • Rust
  • Kotlin

¿Cuándo usar optional?

Ideal para:

  • búsquedas
  • parseos
  • resultados opcionales
  • operaciones que pueden fallar naturalmente


¿Cuándo NO usarlo?

  • errores complejos
  • información detallada de fallos


En esos casos es mejor:

  • std::expected (C++23)
  • excepciones


std::optional empezó en C++17 como una forma segura de representar ausencia de valor.

Pero en C++23 evolucionó muchísimo:

  • transform
  • and_then
  • or_else


lo convierten en una herramienta claramente influenciada por programación funcional y mónadas como Maybe.

C++ sigue siendo multiparadigma, pero cada vez incorpora más ideas del mundo funcional… sin dejar de ser C++.

sábado, 4 de abril de 2026

¿Cual es el estado de los lenguajes que corren sobre la plataforma Java?



La plataforma Java no es solo el lenguaje Java. Gracias a la JVM (Java Virtual Machine), es posible ejecutar múltiples lenguajes con distintos paradigmas y objetivos.

A continuación, un resumen breve de los más relevantes:


Java

Objetivo: Lenguaje generalista, orientado a objetos.

Uso típico: Backend, enterprise, Android (históricamente).

Estado: Activo y en constante evolución (LTS recientes, mejoras funcionales).


Kotlin

Objetivo: Alternativa moderna a Java, más concisa y segura.

Uso típico: Android, backend, multiplataforma.

Estado:  Muy activo, impulsado por JetBrains y adoptado oficialmente por Google.


Scala

Objetivo: Mezclar programación funcional y orientada a objetos.

Uso típico: Big Data, sistemas distribuidos.

Estado: Activo, pero con menor adopción reciente frente a Kotlin.


Groovy

Objetivo: Lenguaje dinámico para simplificar Java.

Uso típico: Scripts, testing, herramientas como Gradle.

Estado:  Estable, pero en segundo plano.


Clojure

Objetivo: Programación funcional pura (Lisp en la JVM).

Uso típico: Sistemas concurrentes, data processing.

Estado: Activo en nichos específicos.


Jython

Objetivo: Implementación de Python sobre la JVM.

Uso típico: Integración con ecosistema Java.

Estado: Limitado (sin soporte moderno de Python 3 completo).


JRuby

Objetivo: Ejecutar Ruby en la JVM.

Uso típico: Integración con sistemas Java.

Estado: Activo, pero nicho.


Frege

Objetivo: Lenguaje funcional inspirado en Haskell.

Uso típico: Académico / experimental.

Estado: Poco activo.


Eta

Objetivo: Llevar Haskell a la JVM.

Uso típico: Funcional puro sobre JVM.

Estado: Proyecto prácticamente detenido.


 JavaScript (GraalVM)

Objetivo: Ejecutar JavaScript en la JVM mediante GraalVM.

Uso típico: Polyglot, microservicios, scripting.

Estado:  Activo y en crecimiento.


Python (GraalVM)

Objetivo: Ejecutar Python sobre la JVM con GraalVM.

Uso típico: Integración polyglot.

Estado:  Experimental.


La JVM es en una plataforma polyglot, donde distintos lenguajes conviven según la necesidad.

miércoles, 18 de febrero de 2026

Efectos algebraicos en Koka: separar el qué del cómo



Los lenguajes de programación modernos buscan equilibrio entre pureza funcional y efectos del mundo real.

Queremos mantener la lógica limpia y predecible… pero también necesitamos leer archivos, hacer peticiones HTTP o modificar estado.

Ahí entra Koka, un lenguaje que intenta resolver uno de los dilemas más antiguos de la programación funcional:

¿Cómo escribir código puro que también interactúe con el mundo impuro?

La respuesta de Koka son los efectos algebraicos —una forma elegante de expresar qué puede hacer una función, sin acoplarla al cómo lo hace.

En la mayoría de los lenguajes funcionales, los efectos se encapsulan.

Por ejemplo, en Haskell todo efecto está representado por un monad.

Una acción de entrada/salida se modela como un valor de tipo IO, que representa un programa que, al ejecutarse, producirá un efecto y un resultado.

El sistema es poderoso, pero tiene un precio: una vez que entrás en IO, todo tu código queda contaminado por ese contexto.

El efecto se propaga por toda la cadena de funciones.

Para componer varias acciones con efectos distintos, terminás anidando monads o recurriendo a transformadores de monads (monad transformers), una solución conceptualmente pura… pero incómoda en la práctica.


Koka propone algo diferente: cada función anota sus efectos directamente en su tipo.


Por ejemplo:


fun readLine() : string {io}

fun toUpper(s : string) : string {}

fun greet() : string {io} {

  val name = readLine()

  return "Hello " ++ toUpper(name)

}


El tipo {io} indica que la función realiza operaciones de entrada/salida.

Si una función no tiene efectos, simplemente aparece {}.

Esto convierte los efectos en una dimensión más del tipo: no solo sabemos qué tipo de valor devuelve una función, sino también qué efectos puede causar.


Lo brillante ocurre cuando los efectos se vuelven algebraicos.

En Koka, los efectos son parámetros abiertos: se pueden combinar, interceptar o redefinir sin romper la pureza.


Podés definir tus propios efectos, como exception, state o async, y luego manejarlos explícitamente.

Por ejemplo:


fun divide(x : int, y : int) : int {exception} {

  if (y == 0) raise DivideByZero

  else x / y

}


fun safeDivide(x : int, y : int) : int {

  handle divide(x, y) with

    exception.DivideByZero -> 0

}



Aquí, divide declara que puede lanzar un efecto de tipo exception.

safeDivide maneja ese efecto sin necesidad de un try/catch ni un monad especial.

El control del flujo se modela como una transformación algebraica:

el efecto se produce, se captura y se resuelve, todo dentro del sistema de tipos.


¿Por qué algebraicos? Porque los efectos se tratan como operaciones algebraicas: se definen por sus constructores (las acciones que pueden producir) y sus leyes de manejo (cómo se interpretan).


Esto permite una separación total entre:

  • la semántica del efecto (qué significa `raise`, `print`, `read`, etc.)
  • y su implementación concreta (cómo se maneja ese efecto en tiempo de ejecución).


El resultado es un sistema en el que los efectos pueden componerse y combinarse libremente, sin comprometer la pureza ni forzar estructuras de control artificiales.


Mientras Haskell encapsula efectos dentro de estructuras monádicas cerradas, Koka los representa como restricciones abiertas y composables.

En Haskell, IO es una caja: sabés que hay efectos dentro, pero no podés mirar sin ejecutarla.

En Koka, los efectos son etiquetas en el tipo, que el compilador puede seguir, combinar y hasta eliminar si son innecesarios.


Esto hace que Koka se sienta más cercano a un lenguaje con tipado por capacidades que a un sistema monádico clásico.

El resultado: código más declarativo, menos boilerplate y una pureza que no castiga la expresividad.


En lugar de ocultar los efectos, Koka los expone y los describe con precisión matemática.

El compilador sabe qué puede pasar —y lo sabe sin ejecutar el código—, lo que permite optimizaciones, razonamiento formal y mayor seguridad.



viernes, 13 de febrero de 2026

Inferencia comportamental en Iris


A veces un lenguaje de programación rompe las reglas de lo establecido.

Eso es lo que ocurre con Iris, un lenguaje funcional moderno que se anima a repensar algo que la mayoría de los lenguajes considera “resuelto”: la inferencia de tipos.

Mientras lenguajes como Haskell, Scala o TypeScript utilizan sistemas derivados de Hindley–Milner —que deducen tipos a partir de la forma y las restricciones estáticas del código— Iris propone algo distinto: una inferencia comportamental, donde los tipos no se deducen solo por lo que una expresión es, sino por lo que hace.


Para entender a Iris, conviene empezar por Haskell.

En Haskell, el compilador busca deducir el tipo más general posible de una expresión.

Por ejemplo:

f x = x + 1


El compilador infiere que f :: Num a => a -> a.

Eso significa: f toma algo de tipo a, siempre que a sea una instancia de Num, y devuelve otro a.

La inferencia en Haskell es declarativa: parte de un conjunto de reglas sobre cómo los tipos se combinan.

El compilador no intenta entender qué hace f, sino cómo encaja dentro del sistema de tipos predefinido.


Iris toma otro camino.

Cuando uno escribe algo como:

fn addOne(x) = x + 1


el compilador no busca una clase de tipo Num ni una restricción declarativa.

En cambio, observa el comportamiento del código:

  • nota que x participa en una operación aritmética,
  • deduce que x debe pertenecer a un tipo que sepa sumarse,
  • y propaga esa característica como una capacidad requerida, no como una categoría rígida.


De esta forma, Iris no infiere tipos por forma, sino por rol.

El tipo de x no es una etiqueta estática, sino un conjunto de propiedades comportamentales deducidas del uso.


Este enfoque convierte a Iris en un lenguaje que parece dinámico, pero mantiene seguridad estática.

Por ejemplo, si una función usa un valor solo para imprimirlo, Iris no necesita saber que es un String; basta con que ese valor sepa representarse como texto.

Si más adelante ese mismo valor participa en una suma, el sistema de tipos lo amplía para incluir también la capacidad de actuar como número, sin perder la anterior.


Así, el tipo resultante se vuelve una composición de comportamientos inferidos.

En vez de limitar, los tipos se adaptan al uso que el programa les da.


En Haskell, el tipo Num a => a -> a expresa una restricción declarativa: a debe cumplir las leyes de Num.

En Iris, el tipo inferido sería más bien una descripción semántica: “esta función requiere algo que sepa sumarse.”


La diferencia puede parecer semántica, pero en realidad separa dos filosofías:

  • Haskell razona sobre qué debe cumplirse para que el programa sea válido.
  • Iris razona sobre qué comportamiento se manifiesta en el código.


Haskell clasifica; Iris describe.

El primero etiqueta los valores; el segundo observa lo que hacen.

La inferencia comportamental de Iris puede parecer un experimento académico, pero en realidad apunta a una tendencia profunda: sistemas de tipos que entienden la intención del código, no solo su estructura.

Durante décadas, el tipado estático fue visto como un conjunto de reglas sintácticas para detectar errores antes de ejecutar el programa.

Iris sugiere otra visión: los tipos como modelos del significado del programa.

En lugar de decir “esto es un número”, el compilador podría decir “esto se comporta como algo sumable, imprimible o clonable”.


Ese cambio abre un nuevo horizonte:

  • una inferencia más rica, que se adapta al contexto,
  • una frontera difusa entre lenguajes estáticos y dinámicos,
  • y optimizaciones semánticas, donde el compilador comprende la intención del código.


En última instancia, Iris nos recuerda que los tipos no tienen por qué ser jaulas.

Pueden ser descripciones de comportamiento, espejos del flujo semántico del programa.

Y si el futuro del tipado estático pasa por hacer que las máquinas comprendan el significado del código, quizás Iris esté señalando, silenciosamente, hacia ese horizonte.


Dejo link: https://github.com/connorjacobsen/iris


lunes, 15 de diciembre de 2025

Pattern Matching en C++26 — ¿Qué es y cómo cambiará tu código?

 


C++ ha sido históricamente rico en herramientas de selección de flujo (if, switch, visitantes sobre std::variant, etc.), pero carece de una estructura nativa y unificada de pattern matching como la que vemos en Rust, Haskell o Swift. C++26 apunta (a través de la propuesta P2688R5) a llenar ese vacío. 

En ciencias de la computación, pattern matching es una forma declarativa de comparar una estructura de datos con uno o más patrones y —si coincide— ejecutar código asociado, extraer valores de forma segura y reducir mucho el boilerplate de código de control. 


Si alguna vez usaste pattern matching en lenguajes funcionales o en Rust:


match value {

    Some(x) => println!("Tiene valor: {}", x),

    None    => println!("No tiene valor"),

}


C++ tiene herramientas poderosas (como std::variant + std::visit), pero:

  • No existe un constructo nativo para comparar y destructurar tipos de forma concisa.
  • El tradicional switch solo funciona con valores integrales y carece de destructuración o bindings.


Pattern matching permite cosas como:

  • Comprobar la forma estructural de un valor.
  • Extraer datos de una std::tuple, std::variant, o tipos compuestos.
  • Reducir código repetitivo en código de control complejo.


Esto hace que tu código sea más claro, seguro y mantenible. 

¿Pattern Matching estará en C++26? Todavía no es definitivo.

El estándar C++26 aún está en borrador y pattern matching aún está en discusión dentro del comité WG21. La propuesta principal —P2688R5— introduce un nuevo constructo match muy similar a lo que otros lenguajes usan. 

Algunos desarrolladores creen que puede entrar en C++26 si todo marcha rápido. 

Otros opinan que podría retrasarse hasta C++29 debido a temas de diseño/sintaxis. 


Desde la propuesta P2688, se perfila un enfoque parecido a:


if (expr match [0, let foo]) {

    // foo está ligado a algo útil aquí

}


expr match -> result_type {

    pattern1 => /* acción 1 */,

    pattern2 => /* acción 2 */,

    _        => /* caso por defecto */

}


Aquí, match intenta casar expr con cada patrón.

let introduce variable(s) extraídas.

La sintaxis con => recuerda a Rust/Haskell, pero adaptada a C++ con sus propias reglas. 


Esta sintaxis aún no es parte definitiva del estándar — puede cambiar hasta la ratificación final.


viernes, 21 de noviembre de 2025

Parámetros implícitos en Scala ¿y en otros languages?



Scala ofrece una poderosa característica llamada parámetros implícitos (implicit parameters), que permite pasar argumentos a funciones sin tener que especificarlos explícitamente cada vez. Esta capacidad se utiliza mucho para inyección de dependencias, contextos compartidos o type classes.


def saludar(nombre: String)(implicit saludo: String): Unit =

  println(s"$saludo, $nombre!")


implicit val saludoPorDefecto: String = "Hola"


saludar("Emanuel") // imprime "Hola, Emanuel!"


Aquí, el parámetro saludo se pasa de manera implícita gracias a la definición previa de un valor implicit.

Si se define otro valor implícito en el mismo alcance, ese será el utilizado, lo que permite una gran flexibilidad contextual.

Aunque el concepto de implícito es característico de Scala, existen ideas similares:

Haskell usa type classes, que se resuelven de forma implícita por el compilador.

Por ejemplo, la clase `Eq` o `Show` se comporta como una inyección automática de comportamientos según el tipo.


show 42 -- el compilador infiere automáticamente la instancia de Show Int


Rust usa traits y type inference, que cumplen un rol similar. Las implementaciones de traits se aplican automáticamente sin especificarlas cada vez.


println!("{}", 42); // Usa automáticamente el trait Display para i32


C# no tiene parámetros implícitos como tal, pero existen aproximaciones:

  • Inyección de dependencias en frameworks como ASP.NET.
  • Attributes y default parameters.
  • Desde C# 12, Primary constructors y default interface methods permiten inyectar comportamientos contextuales, aunque no son implícitos en tiempo de compilación.


Python tampoco tiene parámetros implícitos, pero se puede emular con:

  • Decoradores.
  • Context managers (with).
  • Argumentos por defecto o variables globales.


Los parámetros implícitos de Scala logran un equilibrio interesante entre claridad y potencia, especialmente en contextos funcionales.

Su uso debe ser cuidadoso, ya que abusar de ellos puede hacer que el flujo de datos sea menos evidente.

Sin embargo, cuando se aplican bien, son una herramienta que simplifica enormemente el código y reduce la verbosidad.

miércoles, 12 de noviembre de 2025

Elm vs Haskell: dos caminos del paradigma funcional


Haskell y Elm comparten una raíz común: ambos son lenguajes funcionales puros, con tipado estático, inferencia de tipos y un fuerte énfasis en la inmutabilidad.

Sin embargo, cada uno tomó un camino distinto.

Haskell apostó por la abstracción, la teoría de tipos avanzada y la expresividad; Elm, por la simplicidad, la seguridad y la experiencia del desarrollador.

Haskell nació en el ámbito académico, con el objetivo de ser un lenguaje funcional puro que sirviera como base de investigación.

Por eso prioriza la expresividad, la abstracción y la corrección formal.

Su lema podría ser: “todo puede expresarse en tipos”.

Elm, en cambio, nació del mundo web.

Su meta no es la investigación, sino la confiabilidad.

Fue diseñado para construir interfaces web sin errores en tiempo de ejecución.

Su lema podría ser: “ningún runtime exception, nunca”.


En resumen:

Haskell es una herramienta para explorar los límites del paradigma funcional.

Elm es una herramienta para aplicar ese paradigma con seguridad y pragmatismo.


Ambos tienen tipado estático e inferencia, pero el sistema de tipos de Haskell es mucho más poderoso.

Haskell permite type classes, kind polymorphism, type families, GADTs, monads, existentials y un sinfín de extensiones.

Elm tiene un sistema de tipos mucho más pequeño, pero más legible y predecible.

En Haskell, el tipo puede expresar conceptos avanzados:


fmap :: Functor f => (a -> b) -> f a -> f b


Mientras que en Elm, los tipos se mantienen simples y directos:


List.map : (a -> b) -> List a -> List b


En Haskell podés crear tus propias type classes (Eq, Ord, Monad, etc.).

En Elm no existen type classes: sólo hay un conjunto fijo de restricciones (number, comparable, appendable).


Haskell es un lenguaje donde los tipos son una herramienta de abstracción.

Elm los usa más bien como una herramienta de seguridad.


Tanto Haskell como Elm son lenguajes funcionales puros: ninguna función puede tener efectos secundarios sin declararlo explícitamente.

Pero los abordan de forma distinta.


Haskell utiliza el sistema de monads para modelar efectos: IO, Maybe, State, etc.

Cada efecto se encapsula en un tipo, y se encadenan usando do notation.


main :: IO ()

main = do

    name <- getLine

    putStrLn ("Hola, " ++ name)


Elm evita completamente las monads.

En su lugar, usa un modelo explícito de efectos: los Commands (Cmd) y Subscriptions (Sub), que forman parte del Elm Architecture.

Esto mantiene la pureza del lenguaje sin exponer al programador a conceptos teóricos complejos.


update : Msg -> Model -> (Model, Cmd Msg)

update msg model =

    case msg of

        CargarDatos ->

            ( model, Http.get {...} )


        DatosCargados datos ->

            ( { model | items = datos }, Cmd.none )


Haskell ofrece poder; Elm ofrece control.

En Haskell, el manejo de efectos es flexible y extensible.

En Elm, es seguro y predecible.


Elm está diseñado para ser simple y consistente.

La sintaxis es limpia, sin operadores ambiguos ni extensiones opcionales.

Haskell, en cambio, permite una gran expresividad, pero también puede resultar críptico.


En Elm:


sumar : Int -> Int -> Int

sumar x y =

    x + y


En Haskell:


sumar :: Int -> Int -> Int

sumar x y = x + y


Parecen casi iguales, pero Haskell permite redefinir operadores, crear infix personalizados o usar point-free style, lo que puede aumentar la complejidad.

Elm evita deliberadamente esa flexibilidad para mantener el código legible para todos.


Haskell es un lenguaje generalista: se usa en compiladores, sistemas financieros, backends web, análisis estático y más.

Su ecosistema es vasto y diverso, aunque muchas librerías varían en calidad y mantenimiento.


Elm se centra exclusivamente en el frontend web.

Todo su diseño gira en torno a construir aplicaciones en el navegador.

No hay ambigüedad: un proyecto Elm siempre es una aplicación web.

A cambio de esa limitación, ofrece una experiencia coherente, con un compilador extremadamente útil y mensajes de error ejemplares.


La diferencia más grande entre ambos lenguajes quizá sea emocional. Haskell a veces puede parecer un rompecabezas: poderoso, elegante, pero con una curva de aprendizaje pronunciada.

Elm, en cambio, busca que programar sea agradable, incluso para quienes no tienen experiencia previa en programación funcional.

El compilador de Elm no sólo te dice qué está mal, sino cómo arreglarlo.

El de Haskell, aunque más sofisticado, puede ser más críptico si no conocés sus fundamentos teóricos.


Haskell y Elm son dos lenguajes que muestran dos filosofías complementarias del mundo funcional.

Haskell te da un universo para explorar la abstracción.

Elm te da un terreno firme donde construir sin errores.

martes, 11 de noviembre de 2025

Type constraints en Elm


Elm es un lenguaje de tipado estático e inferencia fuerte: el compilador deduce los tipos automáticamente, pero también te permite declarar funciones genéricas que funcionan con más de un tipo.

Por ejemplo:


identity : a -> a

identity x = x


Esta función acepta cualquier tipo a.

Sin embargo, a veces queremos restringir qué tipos son válidos.

Ahí entran en juego los type constraints (restricciones de tipo).

En Elm, los type constraints permiten decir:

> “Este tipo genérico debe cumplir con ciertas propiedades (por ejemplo, ser comparable o numérico)”.


A diferencia de Haskell o Scala, Elm no tiene type classes, pero ofrece un pequeño conjunto de restricciones integradas que cubren los casos más comunes.

Elm define cuatro categorías de tipos con restricciones que podés usar en tus firmas de tipo:

  • number: Tipos que soportan operaciones aritméticas como Int, Float
  • comparable: Tipos que pueden ordenarse o compararse  como Int, Float, Char, String, tuples de comparables
  • appendable: Tipos que pueden concatenarse como String, List a
  • compappend:| Tipos que son a la vez comparable y appendable 


Podés restringir una función a operar solo sobre números:


sumar : number -> number -> number

sumar x y =

    x + y


Esto funciona con Int o Float, pero no con String.


Si necesitás ordenar o comparar valores:


menor : comparable -> comparable -> comparable

menor a b =

    if a < b then

        a

    else

        b


O incluso:


ordenar : List comparable -> List comparable

ordenar lista =

    List.sort lista


Cuando querés concatenar elementos:


concatenar : appendable -> appendable -> appendable

concatenar a b =

    a ++ b


Funciona con:


concatenar "Hola, " "mundo!"        -- "Hola, mundo!"

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


En Elm no se pueden definir tipos personalizados que sean comparable o appendable.

Por ejemplo, este tipo:


type alias Persona =

    { nombre : String, edad : Int }


No puede usarse en una función List.sort directamente.

Pero podés ordenarlo con una clave usando List.sortBy:


ordenarPorEdad : List Persona -> List Persona

ordenarPorEdad personas =

    List.sortBy .edad personas


O definir un criterio personalizado:


ordenarPorNombreDesc : List Persona -> List Persona

ordenarPorNombreDesc personas =

    List.sortWith (\a b -> compare b.nombre a.nombre) personas


Elm mantiene su sistema de tipos simple pero poderoso: no hay typeclasses ni herencia, pero sí restricciones útiles y seguras para los casos más comunes.


lunes, 6 de octubre de 2025

Mónadas en Elm: Encadenando Cálculos con Elegancia


Las mónadas son un concepto central en la programación funcional. Aunque suenen complicadas, en Elm ya las usamos todo el tiempo sin darnos cuenta.

En términos simples: Una mónada es un funtor con una forma de encadenar operaciones que devuelven estructuras.

En Elm esto se hace con funciones como andThen (también llamada bind en otros lenguajes).

Maybe como mónada: Cuando trabajamos con valores opcionales (Maybe), podemos encadenar operaciones sin preocuparnos por el caso Nothing.


dividir : Int -> Int -> Maybe Int

dividir a b =

    if b == 0 then

        Nothing

    else

        Just (a // b)


calculo : Maybe Int

calculo =

    Just 100

        |> Maybe.andThen (\x -> dividir x 2)

        |> Maybe.andThen (\y -> dividir y 5)


-- Resultado: Just 10


Si en algún paso se produce un Nothing, toda la cadena devuelve Nothing automáticamente.


Result como mónada


Con Result, podemos propagar errores sin necesidad de escribir mucho código repetitivo:


parseEntero : String -> Result String Int

parseEntero s =

    case String.toInt s of

        Just n -> Ok n

        Nothing -> Err "No es un número"


invertir : Int -> Result String Float

invertir n =

    if n == 0 then

        Err "División por cero"

    else

        Ok (1 / toFloat n)


calculo : Result String Float

calculo =

    parseEntero "10"

        |> Result.andThen invertir


-- Resultado: Ok 0.1


Si el parseo falla, se corta la cadena con Err. Si no, se sigue con el siguiente cálculo.


List como mónada


Con listas, una mónada nos permite generar todas las combinaciones posibles de elementos:


pares : List (Int, Int)

pares =

    [1, 2]

        |> List.concatMap (\x ->

            [3, 4] |> List.map (\y -> (x, y))

        )


-- Resultado: [(1,3),(1,4),(2,3),(2,4)]


Esto es equivalente a las list comprehensions en Haskell.


Resumen de funciones clave en Elm

  • Maybe.andThen → encadena operaciones opcionales.
  • Result.andThen → encadena operaciones que pueden fallar con error.
  • List.concatMap → encadena operaciones que generan más listas.


Todas siguen el mismo patrón monádico.

En Elm, aunque no hablemos directamente de “Mónadas” en la sintaxis, las usamos constantemente.

viernes, 26 de septiembre de 2025

Simulando Listas por Comprensión en Elm


En lenguajes como Haskell o Python, las listas por comprensión permiten generar y transformar listas con una sintaxis muy compacta.

En Elm no existen listas por comprensión como sintaxis, pero sí podemos expresar lo mismo con funciones de orden superior como map, filter y concatMap.

En Haskell:


[x * 2 | x <- [1..5]]

-- Resultado: [2,4,6,8,10]


En Elm:


dobles : List Int

dobles =

    [1,2,3,4,5]

        |> List.map (\x -> x * 2)


Resultado: [2,4,6,8,10]


Con condición (filter)


En Haskell:


[x * 2 | x <- [1..5], x > 2]

-- Resultado: [6,8,10]


En Elm:


doblesMayoresQueDos : List Int

doblesMayoresQueDos =

    [1,2,3,4,5]

        |> List.filter (\x -> x > 2)

        |> List.map (\x -> x * 2)


-- Resultado: [6,8,10]


Generando pares (concatMap)


En Haskell:


[(x,y) | x <- [1,2], y <- [3,4]]

-- Resultado: [(1,3),(1,4),(2,3),(2,4)]


En Elm:


pares : List (Int, Int)

pares =

    [1,2]

        |> List.concatMap (\x ->

            [3,4] |> List.map (\y -> (x, y))

        )


-- Resultado: [(1,3),(1,4),(2,3),(2,4)]


Aunque Elm no tiene listas por comprensión como sintaxis, podemos lograr la misma expresividad combinando funciones de orden superior:

  • List.map → transformación.
  • List.filter → condiciones.
  • List.concatMap → anidación (equivalente a los múltiples generadores de Haskell).

De esta forma, Elm mantiene un estilo declarativo y expresivo, alineado con su filosofía de simplicidad y claridad.

Lazy Evaluation en Elm: ¿Existe?


En muchos lenguajes funcionales, como Haskell, la lazy evaluation (evaluación perezosa) es una característica central: los valores no se calculan hasta que realmente se necesitan.

En Elm, en cambio, la evaluación es estricta por defecto (eager evaluation), lo que significa que todos los argumentos de una función se evalúan inmediatamente antes de ser usados.


Veamos un ejemplo simple:


calcular : Int -> Int -> Int

calcular x y =

    x + 1


resultado =

    calcular 5 (Debug.log "evaluando..." 10)


Al ejecutar este código, veremos en la consola:


evaluando...


Aunque el parámetro y nunca se use, Elm lo evalúa igual, porque siempre evalúa sus argumentos antes de ejecutar la función.


Aunque Elm no es lazy por diseño, sí ofrece el módulo Lazy, que permite diferir la evaluación de expresiones costosas hasta que realmente las necesitemos.


import Lazy exposing (Lazy)


perezoso : Lazy Int

perezoso =

    Lazy.lazy (\_ -> 1 + 2)


resultado : Int

resultado =

    Lazy.force perezoso


En este caso:

  • Lazy.lazy encapsula un cálculo.
  • Lazy.force ejecuta el cálculo cuando es necesario.


¿Cuándo usar Lazy?


El módulo Lazy es útil en:

  • Estructuras grandes que no siempre se recorren por completo.
  • Cálculos costosos que solo se necesitan en ciertas condiciones.
  • Simulación de comportamiento perezoso, para optimizar rendimiento.


Sin embargo, Elm sigue favoreciendo la evaluación estricta en la mayoría de los casos, para mantener la simplicidad y la predictibilidad del lenguaje.

domingo, 18 de mayo de 2025

Tipos Abstractos y Polimorfismo en Programación Funcional


Cuando pensamos en abstracción y polimorfismo, solemos imaginar clases, interfaces y herencia. Pero ¿sabías que la programación funcional también tiene sus propios superpoderes para modelar el comportamiento genérico y abstraer detalles? 

En POO, usamos clases abstractas o interfaces para definir estructuras que deben ser implementadas. En programación funcional, el enfoque es distinto, pero el objetivo es similar: ocultar detalles de implementación y exponer un comportamiento general.

Un ADT define un tipo por sus operaciones, no por cómo están implementadas. Por ejemplo:


data Pila a = Vacía | Empujar a (Pila a)


Este tipo Pila podría representar una pila genérica, y podríamos tener funciones que operen sobre ella sin importar cómo esté construida internamente.

En la programación funcional se identifican varios tipos de polimorfismo:

Polimorfismo Paramétrico: Permite escribir funciones genéricas sobre cualquier tipo. Es como los genéricos de Java, pero más poderoso:


identidad :: a -> a

identidad x = x


La función identidad funciona para cualquier tipo a.


Polimorfismo ad-hoc (Typeclasses / Traits / Protocolos) : En Haskell, Rust, Scala o Elixir podemos definir interfaces de comportamiento según el tipo. Esto recuerda al "método virtual" de POO.


class Metrico a where

    distancia :: a -> a -> Double


instance Metrico (Double, Double) where

    distancia (x1, y1) (x2, y2) =

        sqrt ((x2 - x1)^2 + (y2 - y1)^2)


Veamos un ejemplo en Scala:


trait Metrico[T] {

  def distancia(a: T, b: T): Double

}


implicit object Punto2D extends Metrico[(Double, Double)] {

  def distancia(a: (Double, Double), b: (Double, Double)) =

    math.sqrt(math.pow(a._1 - b._1, 2) + math.pow(a._2 - b._2, 2))

}


def calcularDistancia[T](a: T, b: T)(implicit m: Metrico[T]) =

  m.distancia(a, b)


Pattern Matching como Polimorfismo Estructural: Otro recurso poderoso es el pattern matching, que permite seleccionar comportamiento según la "forma" del dato.


sealed trait Forma

case class Circulo(r: Double) extends Forma

case class Rectangulo(ancho: Double, alto: Double) extends Forma


def area(f: Forma): Double = f match {

  case Circulo(r)       => math.Pi * r * r

  case Rectangulo(a, h) => a * h

}


¿Y qué ganamos con esto?

  • Abstracción sin herencia: no hay jerarquías rígidas.
  • Mayor seguridad de tipos: muchos errores se detectan en tiempo de compilación.
  • Separación de datos y comportamiento: las funciones no "viven" dentro de las estructuras de datos, lo cual facilita la composición y el testing.


La programación funcional ofrece mecanismos muy sólidos y expresivos para manejar abstracción y polimorfismo. Aunque no se usa herencia, se logra el mismo efecto (o incluso uno más flexible) usando funciones genéricas, pattern matching y typeclasses.


sábado, 11 de enero de 2025

Quicksort en prolog


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

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

Vamos al código:  


quicksort([], []).

quicksort([X], [X]).

quicksort([Pivot|Rest], Sorted) :-

    partition(Rest, Pivot, Smaller, Greater),

    quicksort(Smaller, SortedSmaller),

    quicksort(Greater, SortedGreater),

    append(SortedSmaller, [Pivot|SortedGreater], Sorted).


partition([], _, [], []).

partition([X|Rest], Pivot, [X|Smaller], Greater) :-

    X =< Pivot,

    partition(Rest, Pivot, Smaller, Greater).

partition([X|Rest], Pivot, Smaller, [X|Greater]) :-

    X > Pivot,

    partition(Rest, Pivot, Smaller, Greater).


Vamos a probarlo: 


?- quicksort([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5], Sorted).

Sorted = [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9].



martes, 19 de noviembre de 2024

QuickSort en Pony


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

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


Vamos al código:  


 actor Main

  new create(env: Env) =>

    let data = [4; 2; 7; 1; 3; 6; 5]

    env.out.print("Original: " + data.string())


    let sorted = quicksort(data)

    env.out.print("Sorted: " + sorted.string())


  fun quicksort(data: Array[U64]): Array[U64] =>

    if data.size() <= 1 then

      data

    else

      let pivot = data(0)?

      let less = data.values().filter(lambda(x: U64): Bool => x < pivot end)

      let greater = data.values().filter(lambda(x: U64): Bool => x > pivot end)


      let less_sorted = quicksort(less)

      let greater_sorted = quicksort(greater)


      let result = less_sorted.append(pivot)

      result.append(greater_sorted)

    end


martes, 5 de noviembre de 2024

Pattern Matching en Typescript con ts-pattern


En TypeScript, a menudo necesitamos simplificar la lógica de condiciones, y aunque existen alternativas como if-else o switch, estas pueden volverse confusas en estructuras más complejas. Aquí es donde ts-pattern entra en juego, ofreciendo una forma poderosa y funcional de hacer pattern matching. Inspirado en técnicas de lenguajes como Haskell y Scala, ts-pattern permite coincidir patrones de datos y manejar casos de manera clara y estructurada.

ts-pattern es una biblioteca que lleva el pattern matching a TypeScript, haciéndolo flexible y adecuado para manejar varios tipos de datos y patrones complejos. Esto ayuda a escribir código más conciso y fácil de mantener, especialmente en proyectos grandes donde las condiciones exhaustivas son comunes.

Para instalar ts-pattern en tu proyecto, simplemente ejecuta:


npm install ts-pattern



Luego, impórtalo en tu archivo TypeScript:


import { match } from 'ts-pattern';


Veamos un ejemplo básico para entender cómo funciona match en strings:


const saludo = match('hola')

  .with('hola', () => '¡Hola Mundo!')

  .with('adiós', () => '¡Hasta luego!')

  .otherwise(() => 'Desconocido');

console.log(saludo);  // Salida: ¡Hola Mundo!


ts-pattern también permite manejar objetos anidados o arrays, facilitando el trabajo con estructuras más detalladas. Supongamos que tenemos un estado de carga y queremos manejarlo de manera exhaustiva:


type Estado = 'cargando' | 'éxito' | 'error';


const mensaje = match<Estado>('éxito')

  .with('cargando', () => 'Cargando...')

  .with('éxito', () => '¡Datos cargados!')

  .with('error', () => 'Hubo un problema.')

  .exhaustive();

console.log(mensaje);  // Salida: ¡Datos cargados!


Con .exhaustive(), ts-pattern se asegura de que todos los posibles valores de `Estado` están cubiertos, ayudándote a evitar errores futuros.

ts-pattern simplifica el manejo de múltiples condiciones en TypeScript, mejorando la claridad y la mantenibilidad del código. 

Dejo link: https://github.com/gvergnaud/ts-pattern

domingo, 3 de noviembre de 2024

Monadex en Elixir


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

Primero, agrega Monadex a tu archivo mix.exs:

defp deps do

  [

    {:monadex, "~> 1.1"}

  ]

end


Ejecuta mix deps.get para instalar la dependencia.


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

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


alias Monadex.Maybe


def get_user_info(user) do

  Maybe.return(user)

  |> Maybe.and_then(&get_name/1)

  |> Maybe.and_then(&upcase_name/1)

end


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

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


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


user = %{name: "Juan"}

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


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


Procesemos datos que pueden fallar en algún paso.


alias Monadex.Either


def process_data(data) do

  Either.return(data)

  |> Either.and_then(&validate/1)

  |> Either.and_then(&transform/1)

end


defp validate(data) do

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

end


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


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

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


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

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


viernes, 1 de noviembre de 2024

Quicksort en F#


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

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

Vamos al código:  


let rec quicksort list =

    match list with

    | [] -> []

    | pivot :: tail ->

        let lower = tail |> List.filter (fun x -> x <= pivot)

        let higher = tail |> List.filter (fun x -> x > pivot)

        quicksort lower @ [pivot] @ quicksort higher