Translate

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

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


domingo, 25 de enero de 2026

Scala 3.8: Una evolución significativa del lenguaje


El 22 de enero de 2026 se anunció el lanzamiento de Scala 3.8, una versión que moderniza partes importantes del lenguaje y prepara el camino para Scala 3.9 LTS (soporte a largo plazo). Esta actualización trae cambios técnicos profundos, mejoras en la biblioteca estándar y estabilizaciones de características esperadas por la comunidad. 


Scala 3.8 requiere JDK 17 o posterior para compilar y ejecutar programas. Esto marca una ruptura con versiones anteriores que soportaban JDK 8, y responde a cambios en las futuras versiones de la JVM donde APIs internas como sun.misc.Unsafe ya no son accesibles por defecto. 


Hasta ahora la biblioteca estándar de Scala se compilaba con Scala 2.13 y se reutilizaba desde Scala 3 gracias a la compatibilidad binaria cuidada. En Scala 3.8 la biblioteca estándar ya se compila con Scala 3 nativamente.

Esto no solo moderniza su implementación, sino que sienta las bases para liberar la biblioteca estándar de las dependencias históricas de Scala 2 en versiones futuras. 


A partir de Scala 3.8 el REPL ya no se incluye directamente en la distribución principal del compilador. Ahora es un artefacto independiente que debe agregarse explícitamente si lo necesitás. 

Esto permite:

  • Reducir el tamaño base de Scala.
  • Integrar mejor el REPL en herramientas y entornos de desarrollo.
  • Mejorar la experiencia de uso con nuevas librerías como fansi y pprint, haciendo la salida más legible. 


Scala 3.8 estabiliza varias mejoras importantes al lenguaje que estaban en preview:

“Better Fors”:  La versión mejorada de las comprensiones for que se desugarizan de forma más eficiente y natural ya está activada por defecto. Esto hace al código más predecible y evita algunas operaciones innecesarias de map/flatMap. 


La nueva característica runtimeChecked (SIP-57) reemplaza el uso histórico de: @unchecked cuando queremos que ciertas verificaciones de patrón se hagan en tiempo de ejecución. Es más clara y usable en cadenas de operaciones que pueden lanzar excepciones por patrones no exhaustivos. 


Características experimentales:


Scala 3.8 también introduce varias mejoras en estado de preview, disponibles con el flag -preview:

  • Implicits con into: Permite permitir conversiones implícitas de forma más controlada usando la palabra clave into.
  • Igualdad estricta con patrones: Una novedad experimental que facilita el uso de pattern matching seguro bajo strictEquality.
  • Varargs flexibles: Soporte experimental para spread múltiple en argumentos, haciendo el uso de varargs más expresivo y menos limitado. 


Además hay cambios del compilador y la biblioteca estándar:

  • Recomendaciones en herramientas de construcción: SBT, Mill y Scala CLI necesitan versiones actualizadas para trabajar correctamente con Scala 3.8. 
  • IDEs como IntelliJ IDEA y Metals están adaptándose para dar soporte completo a las nuevas características y cambios en la librería. 


Scala 3.8 marca el final del ciclo de nuevas características antes de entrar en feature freeze para preparar la versión Scala 3.9 LTS, que se espera sea la próxima distribución con soporte a largo plazo. 

Esto significa que muchas de las funciones estabilizadas en 3.8 serán la base para la próxima versión LTS, sin cambios incompatibles mayores cuando se publique. 


Scala 3.8 representa un hito en la evolución del lenguaje:

  • Moderniza la biblioteca estándar.
  • Eleva el requisito de JDK 17+.
  • Mejora el desugaring y el runtimeChecked.
  • Separa el REPL como artefacto.
  • Estabiliza mejoras largamente esperadas.
  • Abre la puerta a nuevas características experimentales.


En resumen, es una versión que cambia la base técnica del lenguaje, prepara el terreno para el futuro y consolida Scala como una de las opciones más potentes en la JVM para programación moderna. 


Dejo link:  https://www.scala-lang.org/news/3.8/

domingo, 30 de noviembre de 2025

Parámetros implícitos en Scala 3: given y using


Scala 3 introdujo una nueva forma de manejar los parámetros implícitos, reemplazando las viejas palabras clave implicit val y implicit def por un sistema más legible: given y using.


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

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


given String = "Hola"


saludar("Emanuel") // Usa el valor dado automáticamente


given: define un valor que puede usarse de forma implícita.

using: marca el parámetro que puede ser resuelto automáticamente.


Ya no hay necesidad de usar la palabra implicit, lo que hace el código más claro y menos propenso a ambigüedades.

Veamos un ejemplo con varios contextos:


given idioma: String = "español"

given tono: String = "amistoso"


def saludar(nombre: String)(using idioma: String, tono: String): Unit =

  println(s"Saludo en $idioma con tono $tono: ¡Hola, $nombre!")


saludar("Emanuel")


El compilador resuelve ambos using buscando given del tipo adecuado en el ámbito actual.


Scala 2: implicit → flexible, pero a veces confuso.

Scala 3: given/using → más explícito y seguro.


El concepto sigue siendo el mismo: inyectar contextos automáticamente, pero con una sintaxis que favorece la claridad y la mantenibilidad del código.

domingo, 23 de noviembre de 2025

Cómo resuelve Scala los parámetros implícitos

En Scala, los parámetros implícitos permiten que una función reciba argumentos sin que el programador los pase explícitamente.

El compilador se encarga de buscar un valor adecuado en el ámbito para completar la llamada.

Veamos un ejemplo simple:


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

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


implicit val saludoPorDefecto: String = "Hola"


saludar("Emanuel") // Usa el valor implícito definido arriba


Cuando Scala ve que falta un argumento para un parámetro implicit, sigue este proceso:

  1. Busca en el ámbito local: valores o funciones marcados como implicit del tipo requerido.
  2. Si no encuentra ninguno, busca en el objeto compañero (companion object) del tipo esperado.
  3. Si encuentra más de uno y no puede decidir cuál usar, el compilador lanza un error por ambigüedad.
  4. Si no encuentra ninguno, lanza un error por parámetro implícito no encontrado.

Veamos un ejemplo de ambigüedad:


implicit val saludo1: String = "Hola"

implicit val saludo2: String = "Buenas"


saludar("Emanuel") // Error: ambiguous implicits


El compilador no puede elegir entre `saludo1` y `saludo2`.


Scala resuelve los parámetros implícitos buscando un valor del tipo adecuado en:

El ámbito local,

Los imports activos,

Y los companion objects relacionados.


Es un mecanismo potente que permite propagar contextos automáticamente (por ejemplo, ExecutionContext, Ordering, Numeric, etc.), reduciendo la necesidad de pasar dependencias manualmente.

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.

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.


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, 10 de mayo de 2025

Clases padres, clases hijas… ¿y las madres qué?


La programación orientada a objetos (POO) nos trajo muchas cosas lindas: encapsulación, herencia, polimorfismo, y sobre todo, la posibilidad de inventarnos familias disfuncionales de clases sin necesidad de pasar por terapia.

Pero hay algo que siempre nos hizo ruido:

  • ¿Por qué hablamos de clases padre y clases hijas? 
  • ¿Dónde quedaron las madres, los tíos, las primas, o la abuela que todo lo sabe?

Lo más raro es que una clase padre puede ser hija de otra clase. Rarisisimo...

Todo empieza con el inglés. En POO se habla de parent class para referirse a la clase de la cual heredan otras. Y parent, significa “padre o madre”.

Pero claro, en español, por alguna razón misteriosa que seguro involucra a la Real Academia, siempre traducimos parent class como “clase padre”, y no “clase madre” o “clase progenitor/a” (aunque esa última suena a trámites en ANSES).

Entonces… ¿por qué decimos “clase hija”? Acá la gramática mete la cuchara. Como la palabra “clase” es femenina, cuando hablamos de su descendencia lógica usamos “hija” para que concuerde: La clase padre tiene muchas clases hijas.

¿Y si dijéramos “clase madre”?

¡Podemos! No hay ninguna ley que lo impida. De hecho, si queremos romper esquemas y escribir:


class Mamífero // clase madre

class Perro extends Mamífero // clase hija


...nadie de Scala te va a venir a buscar. Al contrario, tal vez sumes puntos con tus profes de literatura.

Eso sí, el término "clase madre" no es tan común, así que si lo usás, preparate para explicar o educar. (O poner una nota al pie tipo “uso madre porque soy inclusivo/a y rebelde”).

La POO no distingue género, pero el lenguaje humano sí. Y en nuestra necesidad de ponerle nombre a todo, terminamos replicando convenciones culturales sin cuestionarlas.

¿Querés decir clase madre? ¡Decilo!

¿Preferís clase base? ¡También está bien!

Lo importante es que tus clases compilen… y que no traumen a sus hijas.



lunes, 28 de abril de 2025

El Poder del underscore (_) en Scala


Scala es un lenguaje conciso y expresivo, y una de sus herramientas más versátiles es el underscore (_). Aunque parece un simple guion bajo, su significado depende del contexto, y puede representar una variable anónima, un tipo genérico, una función parcial, o incluso un importador wildcard. Vamos a repasar sus usos principales con ejemplos simples.

Cuando usás una función que espera un argumento, podés usar _ para decir “acá va ese argumento”.

val nums = List(1, 2, 3)

val dobles = nums.map(_ * 2)  // equivale a nums.map(x => x * 2)

Esto es súper útil para evitar código repetitivo.

Se puede utilizar para ignorar parámetros no usados

Cuando definís una función pero no te interesa usar todos los parámetros:


val funcion = (_: Int) => 42  // ignora el valor que recibe y siempre retorna 42


También se usa para desestructuración parcial:

val (a, _) = (1, 2)  // Ignora el segundo valor


Importaciones tipo wildcard. Igual que en Java con *, pero en Scala se usa _:


import scala.collection.mutable._  // importa todas las clases de mutable


Referencia a métodos como funciones. Cuando pasás un método como función, usas _ para convertirlo a función de orden superior:


def cuadrado(x: Int): Int = x * x

val lista = List(1, 2, 3).map(cuadrado)     // OK

val lista2 = List(1, 2, 3).map(cuadrado _)  // También válido, por conversión explícita


Inicialización por defecto en clases o valores:

var x: String = _  // valor por defecto: null

var y: Int = _     // valor por defecto: 0


Esto es más común en Java-style code o interoperabilidad con frameworks como Spark o Akka.


Tipos genéricos anónimos, como vimos antes:

val lista: List[_] = List("a", 1, true)  // lista de algún tipo desconocido


También podés usar _ <: Animal o _ >: Perro para acotar subtipos o supertypos.

Podés dejar argumentos sin aplicar en una llamada y usar _ para marcar que falta ese valor:


def multiplicar(a: Int, b: Int): Int = a * b

val porDos = multiplicar(2, _: Int)  // función que multiplica por 2

println(porDos(5))  // 10


Dominar el uso del _ te permite escribir código Scala más idiomático y elegante.




martes, 22 de abril de 2025

Tipos Genéricos Anónimos en Scala: Wildcards y Subtipado


Antes de empezar este post tal vez sería bueno que leas este post antes :D

Scala permite definir colecciones y estructuras de datos genéricas, pero a veces no necesitamos saber con precisión qué tipo contienen, o simplemente queremos permitir varios tipos relacionados. Para esos casos existen los tipos genéricos anónimos, representados por el underscore (_).

Un tipo genérico anónimo en Scala se escribe con un guion bajo (_) en lugar de especificar un tipo concreto. Es útil cuando:

  • No necesitás conocer el tipo exacto.
  • Queremos aceptar varios subtipos o supertypos.

Veamos un ejemplo:

Lista de cualquier tipo (wildcard total)


val lista: List[_] = List(1, "hola", true)


Esto indica que lista es de algún tipo List[T], pero no importa cuál es T.


Subtipado: _ <: Tipo

Permite aceptar cualquier subtipo del tipo dado (covarianza).


class Animal

class Perro extends Animal

class Gato extends Animal


val animales: List[_ <: Animal] = List(new Perro, new Gato)


Esto significa: una lista de algo que es subtipo de Animal.


Supertyping: _ >: Tipo

Permite aceptar cualquier supertipo del tipo dado (contravarianza).


val cosas: List[_ >: Perro] = List(new Animal, new Perro)


Esto significa: una lista de algo que es supertipo de Perro.


Y ¿Por qué usar genéricos anónimos?

  • Cuando escribís funciones genéricas que pueden aceptar muchos tipos.
  • Para asegurar compatibilidad con estructuras covariantes/contravariantes.
  • Para restringir o abrir el tipo de manera controlada.

Los tipos anónimos no te permiten hacer mucho con los elementos (no podés acceder a sus métodos específicos), porque Scala no sabe exactamente qué tipo hay.


val lista: List[_] = List("hola", "chau")

// lista(0).toUpperCase()  // ERROR: no se puede garantizar el tipo


En Java existe el signo de pregunta para esto (?):


List<?> lista;

List<? extends Animal> subtipos;

List<? super Perro> supertypos;


En Scala es más limpio y expresivo:


List[_]

List[_ <: Animal]

List[_ >: Perro]


Los tipos genéricos anónimos en Scala te permiten trabajar con estructuras de datos más genéricas y flexibles, especialmente en APIs o librerías donde no se necesita o no se conoce el tipo exacto. Son ideales para mantener la seguridad de tipos sin perder generalidad.


domingo, 20 de abril de 2025

Genéricos en Scala: Covarianza y Contravarianza


Scala es un lenguaje poderoso que combina programación funcional y orientada a objetos. Uno de sus conceptos más sofisticados es el sistema de tipos paramétricos, o genéricos, que permiten escribir código flexible y seguro. 

Los genéricos permiten parametrizar clases, traits y métodos con tipos. Por ejemplo:


class Caja[T](valor: T) {

  def get: T = valor

}


Esta clase puede contener un Int, un String, o cualquier otro tipo.

Imaginemos que tenemos las siguientes clases:


class Animal

class Perro extends Animal


Ahora, supongamos que existe una clase genérica Caja[T]. ¿Debería Caja[Perro] ser un subtipo de Caja[Animal]?


En Scala, la relación de subtipos entre tipos genéricos no se asume automáticamente. Vos decidís explícitamente cómo se comporta con respecto a la varianza.

Scala permite controlar la varianza de un tipo genérico con anotaciones en la declaración del tipo.


Invarianza (sin anotación):

class Caja[T](valor: T)

No hay relación de subtipos entre Caja[Perro] y Caja[Animal].


Covarianza (+T)

class Caja[+T](valor: T)

Caja[Perro] es subtipo de Caja[Animal] si Perro es subtipo de Animal.

Usá covarianza cuando vas a leer datos del tipo genérico, pero no escribir.


Ejemplo:

class ListaCovariante[+A](val cabeza: A)


No podés definir un método como:

def setCabeza(a: A): Unit // ERROR con +A


Porque podría romper la seguridad de tipos.


Contravarianza (-T)


class Procesador[-T] {

  def procesar(t: T): Unit = println("Procesando")

}


Procesador[Animal] es subtipo de Procesador[Perro].

Esto es útil cuando recibís datos del tipo genérico (por ejemplo, funciones o procesadores).

Veamos un ejemplo:


class Animal

class Perro extends Animal

class Gato extends Animal


// Covariante: productor

class CajaProductora[+A](val valor: A)


// Contravariante: consumidor

class Procesador[-A] {

  def procesar(a: A): Unit = println(s"Procesando: $a")

}


Uso:


val perro = new Perro

val gato = new Gato


val caja: CajaProductora[Animal] = new CajaProductora[Perro](perro)

val procesador: Procesador[Perro] = new Procesador[Animal]()


procesador.procesar(perro)


En Scala, los tipos de las funciones son contravariantes en los parámetros y covariantes en el resultado.


val f: Perro => Animal = (a: Animal) => a // válido


¿Por qué? Porque si necesitás una función que acepte Perro, es seguro usar una función que acepte Animal, ya que Animal puede incluir a Perro.


La varianza en Scala te permite expresar con precisión las relaciones de subtipos entre clases genéricas:

  • +T (covariante): útil para leer
  • -T (contravariante): útil para escribir
  • T (invariante): comportamiento por defecto


Comprender este sistema es clave para diseñar APIs robustas, especialmente en programación funcional y colecciones.


sábado, 18 de enero de 2025

El Operador |> de Elixir y sus equivalentes en otros lenguajes


En Elixir, el operador |> pasa el resultado de una expresión como el primer argumento de la siguiente función. Ya lo explicamos en el post anterior. 


" hello "

|> String.trim()

|> String.upcase()

Resultado: "HELLO"


Este diseño promueve una lectura fluida del código, eliminando la necesidad de paréntesis anidados.


F#, un lenguaje funcional inspirado en ML, también tiene un operador pipe |> con un propósito similar al de Elixir.


" hello "

|> String.trim

|> String.uppercase


El operador en F# permite que el flujo de datos sea explícito, facilitando la composición de funciones.


Python no tiene un operador pipe nativo, pero existen bibliotecas que lo emulan, como `pipe` o `toolz`. Sin embargo, sin bibliotecas adicionales, puedes lograr algo similar con reduce:


from functools import reduce


data = " hello "

result = reduce(lambda acc, fn: fn(acc), [str.strip, str.upper], data)

print(result)  # HELLO


Con una biblioteca como pipe:


from pipe import Pipe


result = " hello " | Pipe(str.strip) | Pipe(str.upper)

print(result)  # HELLO


JavaScript aún no tiene un operador pipe oficial, pero hay una propuesta en desarrollo en el comité TC39 (etapa 2 al momento de escribir). Con esta propuesta, el pipe se usa de la siguiente manera:


" hello "

  |> (x => x.trim())

  |> (x => x.toUpperCase());


Por ahora, puedes emularlo con funciones:


const pipeline = (...fns) => x => fns.reduce((v, f) => f(v), x);


const result = pipeline(

  x => x.trim(),

  x => x.toUpperCase()

)(" hello ");

console.log(result); // HELLO


Scala no tiene un operador pipe nativo, pero es posible definir uno:


implicit class PipeOps[T](val value: T) extends AnyVal {

  def |>[R](f: T => R): R = f(value)

}


val result = " hello "

  |> (_.trim)

  |> (_.toUpperCase)

println(result) // HELLO


En C#, aunque no existe un operador pipe, los métodos de extensión de LINQ se comportan de manera similar:


string result = " hello "

    .Trim()

    .ToUpper();

Console.WriteLine(result); // HELLO


El concepto detrás del operador pipe (`|>`) es universal: facilita la composición de funciones y mejora la legibilidad. Aunque su implementación varía entre lenguajes, su propósito sigue siendo el mismo: transformar datos paso a paso de manera clara y concisa.


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


sábado, 9 de noviembre de 2024

GraalVM + sistema operativo = GraalOS


GraalOS es una iniciativa experimental que integra la tecnología de GraalVM directamente en el sistema operativo, permitiendo que las aplicaciones, especialmente las desarrolladas en lenguajes JVM (Java, Scala, Kotlin), se ejecuten de manera más eficiente y directa sobre el hardware. GraalOS busca ser un sistema operativo minimalista y optimizado para ejecutar aplicaciones de alto rendimiento, proporcionando un entorno ideal para microservicios, procesamiento en la nube y aplicaciones en tiempo real.

Las principales características de GraalOS son: 

  1. Soporte Nativo para Lenguajes JVM: GraalOS permite ejecutar código de JVM directamente sobre el sistema operativo sin capas intermedias, ofreciendo un rendimiento nativo para lenguajes como Java, Kotlin y Scala.
  2. Integración con GraalVM: GraalOS está construido sobre la base de GraalVM, lo que permite la compilación AOT (Ahead-of-Time) y el uso de `native-image` para generar binarios nativos que corren eficientemente sobre el hardware.
  3. Ecosistema Multilenguaje: Aunque está optimizado para lenguajes de la JVM, GraalOS también soporta otros lenguajes como JavaScript, Python y R, aprovechando la compatibilidad de GraalVM.
  4. Optimización para Microservicios: GraalOS está diseñado para ejecutarse en contenedores ligeros, ideales para arquitecturas de microservicios y entornos de computación en la nube.

Uno de los puntos fuertes de GraalOS es el uso de la tecnología de compilación Ahead-of-Time (AOT) de GraalVM. La compilación AOT permite que el código de JVM se convierta en código nativo, lo cual mejora significativamente el tiempo de inicio y reduce el uso de memoria.

native-image -jar tu_aplicacion.jar

Este comando convierte un archivo JAR en un binario nativo, optimizado y listo para ejecutarse en GraalOS. Los binarios nativos generados pueden arrancar casi instantáneamente y son ideales para aplicaciones que requieren respuesta en tiempo real.

GraalOS ofrece un entorno perfecto para el despliegue de aplicaciones en la nube gracias a su integración optimizada con GraalVM. Además, permite manejar aplicaciones en tiempo real gracias a su bajo tiempo de respuesta y consumo de recursos. Su diseño minimalista y eficiente hace que sea una opción atractiva para desarrolladores que busquen optimizar costos y rendimiento en entornos de microservicios o serverless.

Aunque GraalOS es experimental, se puede probar en entornos de contenedores o como un sistema operativo en máquinas virtuales para evaluar su rendimiento en aplicaciones específicas. Para comenzar, puedes instalar GraalOS en una máquina virtual y luego utilizar GraalVM para compilar y ejecutar aplicaciones.


apt update && apt install graalos


GraalOS representa un avance en la forma en que interactuamos con el hardware a nivel de sistema operativo para ejecutar aplicaciones nativas. Aunque en sus primeras etapas, su integración con GraalVM abre la puerta a nuevas oportunidades en la ejecución de aplicaciones de alto rendimiento y microservicios en la nube.

Con una promesa de rendimiento optimizado, tiempos de respuesta ultrarrápidos y soporte multilenguaje, GraalOS podría transformar la forma en que desarrollamos e implementamos aplicaciones nativas.

Dejo like : 

https://blogs.oracle.com/java/post/introducing-graalos

https://graal.cloud/

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

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


lunes, 28 de octubre de 2024

Quicksort en Elixir


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 y lisp.

Ahora le toca a Elixir. 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 vacia o tiene un elemento, ya esta ordenada. 

Vamos al código: 


defmodule QuickSort do

  def sort([]), do: []

  def sort([pivot | tail]) do

    lower = Enum.filter(tail, &(&1 <= pivot))

    higher = Enum.filter(tail, &(&1 > pivot))


    sort(lower) ++ [pivot] ++ sort(higher)

  end

end


martes, 22 de octubre de 2024

Borgo, lenguaje de programación que compila a Go.


Borgo es un lenguaje de programación relativamente nuevo que está diseñado para ser versátil y eficiente en varios entornos de desarrollo. Aunque aún está en sus primeras etapas, promete ofrecer un equilibrio entre alto rendimiento y facilidad de uso, haciendo énfasis en la simplicidad y la expresividad del código.

  1. Simplicidad y Legibilidad: Uno de los pilares de Borgo es la simplicidad en su sintaxis. Se enfoca en evitar la verbosidad innecesaria, permitiendo que los desarrolladores escriban código más limpio y comprensible.
  2. Eficiencia y Rendimiento: Borgo está optimizado para ejecutar código de manera eficiente, lo que lo hace ideal para aplicaciones de alto rendimiento. Su compilador se enfoca en generar binarios pequeños y rápidos.
  3. Soporte para Programación Funcional y Orientada a Objetos: Al igual que lenguajes como Scala y Kotlin, Borgo combina paradigmas funcionales y orientados a objetos, lo que permite a los desarrolladores elegir el enfoque más adecuado para su proyecto.
  4. Concurrencia Nativa: Al ser un lenguaje moderno, Borgo incluye soporte para concurrencia y paralelismo de manera nativa, facilitando la creación de aplicaciones altamente escalables sin tener que recurrir a bibliotecas externas.
  5. Ecosistema Modular: Borgo apuesta por un ecosistema modular, donde los desarrolladores pueden añadir funcionalidad mediante paquetes externos, similar a lo que ofrecen lenguajes como Python con `pip` o Node.js con `npm`.

Relindas las características pero veamos un poco de código: 


use fmt

enum NetworkState<T> {

    Loading,

    Failed(int),

    Success(T),

}


struct Response {

    title: string,

    duration: int,

}


fn main() {

    let res = Response {

        title: "Hello world",

        duration: 0,

    }


    let state = NetworkState.Success(res)


    let msg = match state {

        NetworkState.Loading => "still loading",

        NetworkState.Failed(code) => fmt.Sprintf("Got error code: %d", code),

        NetworkState.Success(res) => res.title,

    }


    fmt.Println(msg)

}


Como ven, es como que go y rust hubieran tenido un hijo... 

Borgo es un lenguaje emergente con mucho potencial, diseñado para combinar simplicidad con eficiencia. Aunque aún no es ampliamente adoptado, sus características prometen hacer que valga la pena seguirlo de cerca, especialmente para aquellos desarrolladores que buscan una alternativa moderna a lenguajes tradicionales. 

Dejo link: https://borgo-lang.github.io/

viernes, 4 de octubre de 2024

Pattern Matching en TypeScript


Pattern Matching es una característica poderosa que permite comparar una estructura de datos con un patrón y ejecutar el código dependiendo de cómo coincidan. Aunque TypeScript no tiene un soporte nativo de pattern matching al estilo de lenguajes como Scala o Haskell, pero se puede simular de manera efectiva utilizando algunas características como los tipos discriminados y el refinamiento de tipos para implementar pattern matching. Estos tipos combinan un campo común discriminante que puede diferenciar uniones de tipos de forma segura.

Veamos un ejemplo:


type Shape = 

  | { kind: 'circle', radius: number }

  | { kind: 'square', sideLength: number }

  | { kind: 'rectangle', width: number, height: number };


function area(shape: Shape): number {

  switch (shape.kind) {

    case 'circle':

      return Math.PI * shape.radius ** 2;

    case 'square':

      return shape.sideLength ** 2;

    case 'rectangle':

      return shape.width * shape.height;

  }

}


const myCircle: Shape = { kind: 'circle', radius: 5 };

console.log(area(myCircle)); // 78.53981633974483


Otra forma de hacer pattern matching es mediante guard clauses, que son condiciones específicas para cada caso. Aquí tienes un ejemplo:


function printNumber(x: number | string): string {

  if (typeof x === 'number') {

    return `Es un número: ${x}`;

  } else if (typeof x === 'string') {

    return `Es una cadena de texto: ${x}`;

  } else {

    return `Valor no soportado`;

  }

}


// Uso

console.log(printNumber(42));   // Es un número: 42

console.log(printNumber('42')); // Es una cadena de texto: 42


TypeScript también permite un estilo de pattern matching mediante desestructuración de objetos y arrays.


type Person = { name: string, age: number };

type Animal = { species: string };


function describe(input: Person | Animal): string {

  if ('name' in input) {

    return `Persona: ${input.name}, Edad: ${input.age}`;

  } else {

    return `Especie: ${input.species}`;

  }

}


// Uso

const person: Person = { name: 'John', age: 30 };

const animal: Animal = { species: 'Dog' };


console.log(describe(person)); // Persona: John, Edad: 30

console.log(describe(animal)); // Especie: Dog


El uso de `switch` puede complementarse con guardias para realizar un matching más fino de patrones, filtrando por condiciones adicionales.


function classifyNumber(x: number): string {

  switch (true) {

    case x < 0:

      return 'Número negativo';

    case x === 0:

      return 'Cero';

    case x > 0:

      return 'Número positivo';

    default:

      return 'Valor desconocido';

  }

}


console.log(classifyNumber(-5));  // Número negativo

console.log(classifyNumber(0));   // Cero

console.log(classifyNumber(10));  // Número positivo


Si bien TypeScript no tiene soporte nativo para el pattern matching al nivel de otros lenguajes funcionales, podemos simularlo utilizando sus características de refinamiento de tipos, tipos discriminados, guard clauses y desestructuración.

Con estos enfoques, puedes aplicar las ideas de pattern matching de forma clara y eficiente en TypeScript. Este tipo de técnica puede mejorar la legibilidad de tu código y hacerlo más fácil de mantener.