Translate

Mostrando las entradas con la etiqueta Kotlin. Mostrar todas las entradas
Mostrando las entradas con la etiqueta Kotlin. Mostrar todas las entradas

domingo, 5 de julio de 2026

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


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

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

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

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


En este artículo aparecen varias influencias claras:

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


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

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


let name: String?;


La intención es evidente.

La variable puede no contener un valor.

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


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

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


Es decir:

T?

es simplemente equivalente a:

Optional<T>


Por ejemplo:

let name: String?;


sería exactamente lo mismo que escribir:

let name: Optional<String>;


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

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


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


Podríamos imaginar algo como esto:


class Optional<T> {

    private value: T;

}


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

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

Entonces necesitamos otra solución.


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


Conceptualmente:

object Null


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

No es una palabra reservada.

No es un valor especial incorporado por el compilador.

Es simplemente un objeto del lenguaje.


Hasta aquí parece que el problema está resuelto.

Pero todavía queda una pregunta.

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

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


Es decir: T | Null


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


La idea proviene de TypeScript.

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


Por ejemplo:

String | Null


indica que un valor puede ser:

un objeto String, o

el objeto Null.


Gracias a esto podemos redefinir Optional.

class Optional<T> {

    private value: T | Null;

}


Ahora sí el modelo es completamente consistente.


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

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

Todo está expresado mediante el sistema de tipos.


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

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


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

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


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


Por ejemplo:

let value: String | Int;

let result: Success | Error;

let animal: Dog | Cat;


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


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


El desarrollador escribe:

String?


El parser lo transforma en:

Optional<String>


Y Optional se implementa como:

Optional<T>

    └── value : T | Null


Todo utilizando conceptos generales del lenguaje.


No existen excepciones.

No existen reglas especiales.

No existen valores mágicos.


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


En MateScript decidimos combinar ideas provenientes de distintos lenguajes:

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


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

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



miércoles, 17 de junio de 2026

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


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


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


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


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

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


Código fuente

      ↓

Compilador

      ↓

Bytecode JVM (.class)

      ↓

JVM


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


Por ejemplo, una case class de Scala:

case class Person(name: String)

genera automáticamente métodos como:

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


Todo esto queda representado directamente en bytecode.


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


Código Clojure

      ↓

Compilador

      ↓

Bytecode + Runtime de Clojure

      ↓

JVM


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


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


Ventaja: Aprovecha toda la plataforma Java


Desde Clojure podemos usar directamente cualquier clase Java:


(import java.time.LocalDate)

(LocalDate/now)


No hay puentes especiales ni adaptadores.

Clojure reutiliza:

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


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


Ventaja: Un compilador relativamente pequeño


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

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


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


Ventaja: Desarrollo interactivo y REPL


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

Podemos definir una función:


(defn cuadrado [x]

  (* x x))


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


Este enfoque favorece:

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


Ventaja: Evolución mediante librerías


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

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


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


Las desventajas

Menos optimizaciones

Scala y Kotlin generan bytecode más especializado.

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

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


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


Más reflexión

Si escribimos:

(defn largo [s]

  (.length s))


el compilador puede recurrir a reflexión.

Podemos ayudarlo con type hints:


(defn largo [^String s]

  (.length s))


pero esto requiere intervención del programador.


Menos comprobaciones en tiempo de compilación


Scala y Kotlin verifican muchas cosas antes de ejecutar:

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


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


Interoperabilidad asimétrica

Desde Clojure usar Java es muy fácil.

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


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


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


Dos filosofías distintas

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

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

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

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

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

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


miércoles, 13 de mayo de 2026

Kotlin 2.4.0


La versión 2.4.0 de Kotlin sigue consolidando muchas características que venían evolucionando desde releases anteriores.

Más que agregar “una gran feature”, esta versión termina de estabilizar varias piezas importantes del ecosistema.

El nuevo compilador K2 dejó de sentirse “experimental” y pasó a ser la base real del futuro de Kotlin.


¿Qué aporta?

  • Compilaciones más rápidas
  • Mejor análisis de tipos
  • Mensajes de error más claros
  • Infraestructura más simple para futuras features


K2 no es solamente una optimización, es prácticamente una reescritura del compilador.


Los Context Parameters siguen evolucionando y acercan a Kotlin a ideas similares a:

  • implicits de Scala
  • type classes funcionales
  • dependency injection implícita


Ejemplo:


context(Logger)

fun processOrder() {

    log("Processing order")

}


Esto permite escribir APIs mucho más declarativas.


Kotlin sigue empujando fuerte el desarrollo multiplataforma; en 2.4.0 hay mejoras importantes en:

  • compilación incremental
  • interoperabilidad con iOS
  • performance de Kotlin/Native
  • sharing de código entre plataformas


El garbage collector y el manejo de memoria continúan mejorando para Kotlin/Native. 


Esto impacta directamente en:

  • apps iOS
  • aplicaciones embebidas
  • performance general


Históricamente Kotlin/Native era uno de los puntos más débiles del ecosistema. Las últimas versiones muestran una mejora enorme.


También hay mejoras en:

  • IntelliJ IDEA
  • Gradle
  • debugging
  • análisis estático
  • tiempos de indexing


Muchas veces estas mejoras no aparecen en los titulares, pero son las que realmente cambian la experiencia diaria.


Lo más interesante de Kotlin 2.4.0 quizás no sea una feature puntual.

Es que muchas ideas que antes parecían experimentales ahora empiezan a sentirse “normales”:

  • K2
  • Multiplatform
  • Native
  • Context Parameters


Kotlin está entrando en una etapa mucho más madura del lenguaje.


martes, 21 de abril de 2026

Records vs Clases vs Lombok vs Kotlin vs Scala


¿Cuál es la mejor forma de modelar datos? Desde los Struct de c++ nos venimos preguntando esto. Vamos a ver algunas opciones modernas que nos provee la plataforma java. 

Cuando trabajamos con objetos que representan datos (DTOs, Value Objects, etc.), distintos lenguajes ofrecen soluciones para evitar el boilerplate.


En este post comparamos:

  • Records en Java
  • Clases tradicionales
  • Lombok
  • Data classes en Kotlin
  • Case classes en Scala


1. Clase tradicional (Java)


public class Persona {

    private final String nombre;

    private final int edad;


    public Persona(String nombre, int edad) {

        this.nombre = nombre;

        this.edad = edad;

    }


    public String getNombre() { return nombre; }

    public int getEdad() { return edad; }


    @Override public boolean equals(Object o) { ... }

    @Override public int hashCode() { ... }

    @Override public String toString() { ... }

}

Ventajas

  • Total control
  • Compatible con todo (JPA, frameworks)


Desventajas

  • Mucho boilerplate
  • Propenso a errores


2. Records (Java)


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


Ventajas

  • Ultra conciso
  • Inmutabilidad garantizada
  • Sin dependencias


Desventajas

  • Menos flexible
  • No sirve bien con JPA
  • No herencia


3. Lombok (@Data)


import lombok.Data;


@Data

public class Persona {

    private String nombre;

    private int edad;

}


Ventajas

  • Reduce mucho código
  • Mutable o inmutable (configurable)


Desventajas

  • Dependencia externa
  • "Magia" en compilación (puede confundir)
  •  Problemas en tooling/debug


4. Data Classes (Kotlin)


data class Persona(val nombre: String, val edad: Int)


Ventajas

  • Muy conciso
  • Inmutable por defecto
  • copy() incluido
  • Destructuring


val (nombre, edad) = persona


Desventajas

  • Requiere usar Kotlin
  • Interoperabilidad Java no siempre perfecta


5. Case Classes (Scala)


case class Persona(nombre: String, edad: Int)


Ventajas

  • Inmutables
  • Pattern matching nativo
  • copy() automático
  • Muy expresivas


persona match {

  case Persona(nombre, edad) => println(nombre)

}


Desventajas

  • Curva de aprendizaje
  • Ecosistema más complejo


Y entonces? Y ninguno es super mejor, pero podemos tener estas reglas: 


Java Records

Ideal para:

  • DTOs simples
  • APIs REST
  • Código moderno sin dependencias


Son el "mínimo viable elegante" en Java.


Lombok

Ideal para:

  • Proyectos legacy
  • Equipos que ya lo usan


 Soluciona el problema… pero no es parte del lenguaje.


Kotlin

La mejor experiencia general para modelado de datos.

  • copy()
  • destructuring
  • null-safety


Es claramente superior en ergonomía.


Scala

El más poderoso conceptualmente.

  • Pattern matching real
  • Inmutabilidad fuerte
  • Integración con FP


Pero más complejo.


 Clases Java

 Siguen siendo necesarias cuando:

  • Usás JPA
  • Necesitás mutabilidad
  • Requerís control total


Si estás en Java moderno → Records

Si querés máxima productividad → Kotlin

Si buscás poder expresivo → Scala

Si estás en legacy → Lombok o clases



miércoles, 15 de abril de 2026

¿Qué es LLVM? Introducción a la infraestructura detrás de los compiladores modernos


Si alguna vez te preguntaste cómo lenguajes como Rust o Swift generan código tan eficiente…

La respuesta probablemente sea: LLVM. LLVM no es un compilador tradicional es una infraestructura para construir compiladores.


Durante años, herramientas como GCC siguieron este modelo:

  • Todo en uno (frontend + optimización + backend)
  • Difícil de extender
  • Costoso crear nuevos lenguajes


Resultado: crear un lenguaje era MUY complejo


LLVM cambia el enfoque: Separar el compilador en piezas reutilizables


¿Cómo funciona LLVM?

El proceso se divide en 3 partes:

Frontend

  • Convierte tu lenguaje a LLVM IR
  • Cada lenguaje define su frontend


LLVM IR (Intermediate Representation)

Es el corazón del sistema:

  • Representación intermedia
  • Independiente del hardware
  • Optimizable


Ejemplo:

llvm id="u7j1k0"

define i32 @main() {

  %1 = add i32 10, 20

  ret i32 %1

}


Un “lenguaje común” entre todos los compiladores


Backend

Convierte el IR en código máquina:

  • x86
  • ARM
  • RISC-V

LLVM se encarga de esto


¿Por qué es tan importante? Porque reduce drásticamente la complejidad:

Antes: Lenguaje = compilador completo

Ahora: Lenguaje = frontend + LLVM


LLVM es muy usado, entre los lenguajes que lo usan podemos encontrar:

  • Rust
  • Swift
  • C/C++ con Clang
  • Kotlin Native


LLVM separa responsabilidades:

  • Lenguaje → Frontend
  • Optimización → LLVM
  • Hardware → Backend


Esto permite innovación más rápida y reutilización masiva

LLVM es el motivo por el cual hoy crear un lenguaje es mucho más accesible que antes.

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.

jueves, 11 de julio de 2024

Como podemos manejar las referencias nulas?


El error más frecuente en Java es NullPointerException y me imagino que en otros lenguajes alguno similar...  Para abordar esto, se han introducido estructuras y operadores que ayudan a manejar la ausencia de valores de manera más segura y explícita. 

Por ejemplo en Java se introdujo la clase `Optional` en la versión 8 para manejar valores potencialmente nulos de una manera más segura. `Optional` es un contenedor que puede o no contener un valor no nulo.

import java.util.Optional;


public class OptionalExample {

    public static void main(String[] args) {

        Optional<String> optional = Optional.of("Hello, World!");

        

        // Verificar si hay un valor presente

        if (optional.isPresent()) {

            System.out.println(optional.get());

        }

        

        // Uso del método ifPresent

        optional.ifPresent(System.out::println);

        

        // Proveer un valor predeterminado

        String value = optional.orElse("Default Value");

        System.out.println(value);

        

        // Proveer un valor predeterminado usando un Supplier

        value = optional.orElseGet(() -> "Default Value from Supplier");

        System.out.println(value);

    }

}


Scala utiliza la clase `Option` para representar un valor opcional. `Option` tiene dos subclases: `Some` y `None`, lo que proporciona una forma elegante y funcional de manejar valores que pueden estar ausentes. Esta idea es similar a la monada `Maybe` en Haskell.


object OptionExample extends App {

  val someValue: Option[String] = Some("Hello, World!")

  val noneValue: Option[String] = None


  // Uso de getOrElse

  println(someValue.getOrElse("Default Value"))

  println(noneValue.getOrElse("Default Value"))


  // Uso del patrón de coincidencia (Pattern Matching)

  someValue match {

    case Some(value) => println(value)

    case None => println("No value")

  }


  noneValue match {

    case Some(value) => println(value)

    case None => println("No value")

  }

}


Scala "copio" esta forma de Haskell. Haskell utiliza el tipo de datos `Maybe` para manejar valores opcionales `Maybe` puede ser `Just` un valor o `Nothing`.


main :: IO ()

main = do

    let someValue = Just "Hello, World!"

    let noneValue = Nothing


    -- Uso de fromMaybe

    putStrLn (fromMaybe "Default Value" someValue)

    putStrLn (fromMaybe "Default Value" noneValue)


    -- Uso del patrón de coincidencia (Pattern Matching)

    case someValue of

        Just value -> putStrLn value

        Nothing -> putStrLn "No value"


    case noneValue of

        Just value -> putStrLn value

        Nothing -> putStrLn "No value"


Kotlin es similar a Scala en muchos aspectos pero no en este. Kotlin introduce el operador `?` para facilitar la gestión de valores nulos. Este operador se utiliza para declarar tipos de datos que pueden ser nulos y para realizar operaciones seguras contra nulos.


fun main() {

    var nullableString: String? = "Hello, World!"


    // Uso del operador ?. para llamadas seguras

    println(nullableString?.length)


    // Uso del operador ?: para proporcionar un valor predeterminado

    val length = nullableString?.length ?: 0

    println(length)


    nullableString = null


    // Uso de let para ejecutar código solo si el valor no es nulo

    nullableString?.let {

        println(it)

    }

}


C# ha incluido varias características para manejar valores nulos, como el operador `?`, que facilita el manejo seguro de tipos que pueden ser nulos.


using System;


class Program

{

    static void Main()

    {

        string? nullableString = "Hello, World!";

        

        // Uso del operador ?. para llamadas seguras

        Console.WriteLine(nullableString?.Length);


        // Uso del operador ?? para proporcionar un valor predeterminado

        int length = nullableString?.Length ?? 0;

        Console.WriteLine(length);


        nullableString = null;


        // Uso de pattern matching para verificar nulos

        if (nullableString is string nonNullString)

        {

            Console.WriteLine(nonNullString);

        }

    }

}


Rust maneja la ausencia de valores y los errores de una manera robusta utilizando los tipos `Option` y `Result`. `Option` puede ser `Some` o `None`, mientras que `Result` puede ser `Ok` o `Err`.


fn main() {

    let some_value: Option<String> = Some("Hello, World!".to_string());

    let none_value: Option<String> = None;


    // Uso de unwrap_or

    println!("{}", some_value.unwrap_or("Default Value".to_string()));

    println!("{}", none_value.unwrap_or("Default Value".to_string()));


    // Uso del patrón de coincidencia (Pattern Matching)

    match some_value {

        Some(value) => println!("{}", value),

        None => println!("No value"),

    }


    match none_value {

        Some(value) => println!("{}", value),

        None => println!("No value"),

    }

}


Go no tiene un tipo de datos específico para manejar valores opcionales, pero utiliza la convención de retornar múltiples valores, incluyendo un valor y un `error`. Que la verdad no me gusta, te pasas preguntando todo el tiempo si hay error o si los valores son nulos. 


package main


import (

    "errors"

    "fmt"

)


func getValue() (string, error) {

    return "Hello, World!", nil

}


func getNullableValue() (string, error) {

    return "", errors.New("no value")

}


func main() {

    value, err := getValue()

    if err != nil {

        fmt.Println("Error:", err)

    } else {

        fmt.Println("Value:", value)

    }


    nullableValue, err := getNullableValue()

    if err != nil {

        fmt.Println("Error:", err)

    } else {

        fmt.Println("Value:", nullableValue)

    }

}


Python utiliza la palabra clave `None` para representar la ausencia de valor. Aunque no tiene una estructura específica como `Optional`, los desarrolladores pueden utilizar condicionales y manejo de excepciones.


def get_value():

    return "Hello, World!"


def get_nullable_value():

    return None


value = get_value()

nullable_value = get_nullable_value()


if value is not None:

    print(value)

else:

    print("Default Value")


if nullable_value is not None:

    print(nullable_value)

else:

    print("Default Value")


Ruby utiliza `nil` para representar la ausencia de valor. Al igual que en Python, no tiene una estructura específica para valores opcionales, pero proporciona métodos para manejar `nil`.


value = "Hello, World!"

nullable_value = nil


# Uso del operador ||

puts value || "Default Value"

puts nullable_value || "Default Value"


# Uso de condicionales

puts value.nil? ? "Default Value" : value

puts nullable_value.nil? ? "Default Value" : nullable_value


C++ utiliza punteros inteligentes (`smart pointers`) para gestionar la memoria y prevenir errores relacionados con punteros nulos. Los punteros inteligentes, como `std::unique_ptr` y `std::shared_ptr`, se encargan de la gestión automática de la memoria.


#include <iostream>

#include <memory>


int main() {

    std::unique_ptr<int> uniquePtr(new int(42));

    if (uniquePtr) {

        std::cout << *uniquePtr << std::endl;

    }


    std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);

    if (sharedPtr) {

        std::cout << *sharedPtr << std::endl;

    }


    // Uso de weak_ptr para evitar ciclos de referencia

    std::weak_ptr<int> weakPtr = sharedPtr;

    if (auto lockedPtr = weakPtr.lock()) {

        std::cout << *lockedPtr << std::endl;

    }


    return 0;

}


TypeScript, un superconjunto de JavaScript, permite tipos opcionales y tiene un soporte robusto para manejar valores `null` y `undefined`.


let nullableString: string | null = "Hello, World!";


// Uso del operador ? para llamadas seguras

console.log(nullableString?.length ?? 0);


// Uso de if para asegurar valores no nulos

if (nullableString !== null) {

    console.log(nullableString);

}


TypeScript utiliza tipos opcionales para manejar valores que pueden ser `null` o `undefined`, proporcionando un enfoque seguro para evitar errores comunes relacionados con valores nulos. El operador `?.` permite realizar llamadas seguras, y el operador `??` proporciona valores predeterminados en caso de valores `null` o `undefined`.

En fin, aunque la gestión de valores nulos varía entre lenguajes, la idea subyacente es la misma: proporcionar mecanismos más seguros y expresivos para manejar la ausencia de valores. Ya sea mediante clases contenedoras como `Optional` en Java y `Option` en Scala, tipos de datos como `Maybe` en Haskell, operadores específicos como `?` en Kotlin y C#, punteros inteligentes en C++, o enfoques específicos en Rust, Go, Python y Ruby, estos enfoques ayudan a reducir los errores y a escribir un código más robusto y mantenible.


miércoles, 27 de septiembre de 2023

Libros gratuitos

 Me llegaron ests libros de web code geeks :

Get Schooled By Web Code Geeks

Download FREE IT Guides!

 

Starting with Kotlin Cheatsheet

Welcome to the Kotlin Cheatsheet! This document aims to provide you with a quick reference guide to Kotlin programming language. Whether you are a beginner getting started with Kotlin or...

 
 

Querying Graphs with Neo4j Cheatsheet

This cheatsheet is your guide to effectively querying graphs using Neo4j. Whether you’re a seasoned database professional looking to expand your skills or a curious enthusiast eager to...

 
 

Getting Started with GraphQL Cheatsheet

In this cheatsheet, we will embark on a journey to explore the core principles of GraphQL and its ecosystem. We'll cover topics such as schema design, querying and mutation operations,...

 
 

Starting with Windows PowerShell Cheatsheet

This cheatsheet provides an overview of the most commonly used PowerShell commands, grouped by category. Whether you’re new to PowerShell or an experienced user, this cheatsheet will...

 
 

Core Python Cheatsheet

This cheatsheet is intended to serve as a quick reference guide for Python programming. It covers some of the most commonly used syntax and features of the language, including data types,...

 
 

Starting with Docker Cheatsheet

This guide aims to provide an overview of the key concepts and features of Docker, along with practical examples and tips for using it effectively. It covers topics such as Docker...

 

domingo, 25 de septiembre de 2022

Sealed classes de Kotlin: definición de jerarquías de clases restringidas


Veamos un ejemplo para entender mejor para que utilizar seald classess. La superclase Expr tiene dos subclases: Num, que representa un número; y Sum, que representa la suma de dos expresiones. Es conveniente manejar todas las subclases posibles en una expresión when. Pero tenemos que escribir el  else para especificar qué debería suceder si no tenemos coincidencias:


interface Expr

    class Num(val value: Int) : Expr

    class Sum(val left: Expr, val right: Expr) : Expr


fun eval(e: Expr): Int =

    when (e) {

        is Num -> e.value

        is Sum -> eval(e.right) + eval(e.left)

        else ->

            throw IllegalArgumentException("Unknown expression")

}


Cuando evalúas una expresión usando la construcción when, el compilador de Kotlin te obliga a especificar todas las opciones, pero nosotros sabemos que no es posible que haya otra opción. 

Tener que agregar siempre el else no es conveniente. Además, si agrega una nueva subclase, el compilador no detectará que algo ha cambiado y va a ir por el else. 

Kotlin proporciona una solución a este problema: Sealed classes  o clases selladas. Marcas una superclase con el modificador sellado, y eso restringe la posibilidad de crear subclases. Todas las subclases directas deben anidarse en la superclase:


sealed class Expr {

    class Num(val value: Int) : Expr()

    class Sum(val left: Expr, val right: Expr) : Expr()

}


fun eval(e: Expr): Int =

    when (e) {

        is Expr.Num -> e.value

        is Expr.Sum -> eval(e.right) + eval(e.left)

    }


Y listo!


El modificador sealed implica que la clase es abierta por lo tanto, no necesitamos agregar modificador open de forma explícita.

Otra cosa importante es que no puede declarar una interfaz sellada. ¿Por qué? Si pudiera, el compilador de Kotlin no podría garantizar que alguien no pueda implementar esta interfaz en el código Java.


lunes, 19 de septiembre de 2022

Clases internas y anidadas en Kotlin


Al igual que en Java, en Kotlin puedes declarar una clase en otra clase. Hacerlo puede ser útil para encapsular una clase auxiliar o colocar el código más cerca de donde se usa. La diferencia es que las clases anidadas de Kotlin no tienen acceso a la instancia de la clase externa, a menos que lo solicite específicamente. Veamos un ejemplo que muestra por qué esto es importante.

Imagine que desea definir un elemento View, cuyo estado se puede serializar. Puede que no sea fácil serializar una vista, pero puede copiar todos los datos necesarios en otra clase auxiliar. Declaras la interfaz State que implementa Serializable. La interfaz View declara los métodos getCurrentState y restoreState que se pueden usar para guardar el estado de una vista.

interface State: Serializable

interface View {

    fun getCurrentState(): State

    fun restoreState(state: State) {}

}

Es útil definir una clase que guarde el estado de un botón en la clase Botón. Veamos cómo se puede hacer en Java (el código Kotlin similar se mostrará en un momento).

/* Java */

public class Button implements View {

    @Override

    public State getCurrentState() {

        return new ButtonState();

    }

    @Override

    public void restoreState(State state) { /*...*/ }

    public class ButtonState implements State { /*...*/ }

}

La clase ButtonState que implementa la interfaz State y contiene información específica para Button. En el método getCurrentState, crea una nueva instancia de esta clase. En un caso real, inicializaría ButtonState con todos los datos necesarios.

¿Qué tiene de malo este código? ¿Por qué se obtiene un java.io.NotSerializableException? Eso puede parecer extraño al principio: ButtonState guarda una referencia a Button que no es Serializable.

Todo queda claro cuando recuerdas que en Java, cuando declaras una clase en otra clase, se convierte en una clase interna por defecto. La clase ButtonState del ejemplo almacena implícitamente una referencia a su clase Button externa. Eso explica por qué ButtonState no se puede serializar: Button no se puede serializar y la referencia a él interrumpe la serialización de ButtonState.

Para solucionar este problema, debe declarar la clase ButtonState como estática. Declarar una clase anidada como estática elimina la referencia implícita de esa clase a su clase adjunta. En Kotlin, el comportamiento predeterminado de las clases internas es lo opuesto a lo que acabamos de describir.

class Button : View {

override fun getCurrentState(): State = ButtonState()

override fun restoreState(state: State) { /*...*/ }

class ButtonState : State { /*...*/ }

}

Una clase anidada en Kotlin sin modificadores explícitos es lo mismo que una clase anidada estática en Java. Para convertirlo en una clase interna para que contenga una referencia a una clase externa se puede usar el modificador inner. 

La sintaxis para hacer referencia a una instancia de una clase externa en Kotlin también difiere de Java. Se escribe this@Outer para acceder a la clase Outer desde la clase Inner:

class Outer {

    inner class Inner {

        fun getOuterReference(): Outer = this@Outer

    }

}



miércoles, 7 de septiembre de 2022

Los modificadores de visibilidad de Kotlin y Java



Los modificadores public, protected, y private en Kotlin se conservan al compilar en el código de bytes de Java. Utiliza tales declaraciones de Kotlin del código Java como si se declararan con la misma visibilidad en Java. La única excepción es una private class: está compilada en una declaración de paquete privado bajo el capó (no puede hacer que una clase sea private en Java).

Pero, puede preguntarse, ¿qué sucede con el modificador internal? No hay un análogo directo en Java. La visibilidad privada del paquete es algo totalmente diferente: un módulo generalmente consta de varios paquetes, y diferentes módulos pueden contener declaraciones del mismo paquete. Por lo tanto, un modificador internal se vuelve public en el código de bytes. 

Esta correspondencia entre las declaraciones de Kotlin y sus análogos de Java (o su representación de código de bytes) explica por qué a veces puedes acceder a algo desde el código de Java a lo que no puedes acceder desde Kotlin. Por ejemplo, puede acceder a una clase internal o una declaración de nivel superior del código Java en otro módulo, o un miembro protected del código Java en el mismo paquete (similar a cómo lo hace en Java).

Pero tenga en cuenta que los nombres de los miembros internal de una clase están alterados. Técnicamente, los miembros internos se pueden usar desde Java, pero se ven feos en el código de Java. Eso ayuda a evitar conflictos inesperados en las anulaciones cuando extiende una clase de otro módulo y evita que use accidentalmente clases internal.

martes, 6 de septiembre de 2022

Modificadores de visibilidad en Kotlin


Al restringir la visibilidad de los detalles de implementación de una clase, nos aseguramos de poder cambiarlos sin el riesgo de romper el código que depende de la clase. Básicamente, los modificadores de visibilidad en Kotlin son similares a los de Java. Tiene los mismos modificadores public, protected y private. Pero la visibilidad predeterminada es diferente: si omite un modificador, la declaración se vuelve pública.

La visibilidad predeterminada en Java, paquete privado, no está presente en Kotlin. Kotlin usa paquetes solo como una forma de organizar el código en espacios de nombres; no los usa para el control de visibilidad. Como alternativa, Kotlin ofrece un nuevo modificador de visibilidad, internal, que significa “visible dentro de un módulo”. Un módulo es un conjunto de archivos Kotlin compilados juntos. Puede ser un módulo IntelliJ IDEA, un proyecto Eclipse, un proyecto Maven o Gradle, o un conjunto de archivos compilados con una invocación de una tarea Ant.

La ventaja de la visibilidad interna es que proporciona una encapsulación real para los detalles de implementación de su módulo. Con Java, la encapsulación se puede romper fácilmente, porque el código externo puede definir clases en los mismos paquetes utilizados por su código y, por lo tanto, obtener acceso a sus declaraciones privadas de paquetes.

Otra diferencia es que Kotlin permite el uso de visibilidad privada para declaraciones de nivel superior, incluidas clases, funciones y propiedades. Dichas declaraciones son visibles solo en el archivo donde se declaran. Esta es otra forma útil de ocultar los detalles de implementación de un subsistema. 

  • public: Por defecto y se puede ver en todos lados
  • internal: Visible en el modulo
  • protected: Visible en las subclases
  • private: Visible solo en la clase y en el archivo. 

Veamos un ejemplo. Cada línea de la función giveSpeech intenta violar las reglas de visibilidad. Se compila con un error.

internal open class TalkativeButton : Focusable {

    private fun yell() = println("Hey!")

    protected fun whisper() = println("Let's talk!")

}

fun TalkativeButton.giveSpeech() {

    yell()

     whisper()

}

Kotlin te prohíbe hacer referencia al tipo menos visible TalkativeButton (interno, en este caso) desde la función pública giveSpeech. Este es un caso de una regla general que requiere que todos los tipos usados ​​en la lista de tipos base y parámetros de tipo de una clase, o la firma de un método, sean tan visibles como la clase o el método mismo. Esta regla garantiza que siempre tenga acceso a todos los tipos que pueda necesitar para invocar la función o ampliar una clase. Para resolver el problema, puede hacer que la función sea interna o que la clase sea pública.

Tenga en cuenta la diferencia de comportamiento del modificador protected en Java y en Kotlin. En Java, puede acceder a un miembro protegido desde el mismo paquete, pero Kotlin no lo permite. En Kotlin, las reglas de visibilidad son simples y un miembro protegido solo es visible en la clase y sus subclases. También tenga en cuenta que las funciones de extensión de una clase no obtienen acceso a sus miembros privados o protegidos.

Una diferencia más en las reglas de visibilidad entre Kotlin y Java es que una clase externa no ve miembros privados de sus clases internas (o anidadas) en Kotlin.


martes, 23 de agosto de 2022

Modificadores en Kotlin


En Kotlin, como en Java, puede declarar una clase abstracta y no se pueden crear instancias de dichas clases. Una clase abstracta generalmente contiene miembros abstractos que no tienen implementaciones y deben anularse en las subclases. Los miembros abstractos siempre están abiertos, por lo que no necesita usar un modificador de apertura explícito:

abstract class Animated {

    abstract fun animate()

    open fun stopAnimating() { }

    fun animateTwice() { }

}

Veamos los diferentes tipos de modificadores: 

  • final: esta por defecto en las clases y no se puede sobreescribir el método
  • open: no esta por defecto las clases salvo que sea un método abstracto o interfaz y se puede sobreescribir
  • abstract: se debe sobreescribir y puede ser usado solo en clases abstractas
  • override: indica que estamos sobreescribiendo un método y es open por defecto salvo que le pongamos final. 

En las interfaces, no se usa final, open o abstract. Un miembro en una interfaz siempre está abierto; no puedes declararlo como definitivo o final. Es abstracto si no tiene cuerpo, pero no se requiere la palabra clave.




lunes, 22 de agosto de 2022

Sobre escribir métodos en Kotlin


Java permite crear subclases de cualquier clase y anular cualquier método, a menos que se haya marcado explícitamente con la palabra clave final. Esto a menudo es conveniente, pero también es problemático.

El llamado problema de la clase base frágil ocurre cuando las modificaciones de una clase base pueden causar un comportamiento incorrecto de las subclases porque el código modificado de la clase base ya no coincide con los supuestos en sus subclases. Si la clase no proporciona reglas exactas sobre cómo debe subclasificarse (qué métodos se supone que deben anularse y cómo), los clientes corren el riesgo de anular los métodos de una manera que el autor de la clase base no esperaba. Debido a que es imposible analizar todas las subclases, la clase base es "frágil" en el sentido de que cualquier cambio en ella puede provocar cambios inesperados en el comportamiento de las subclases.

Kotlin sigue la filosofía "diseñe y documente para herencia o, de lo contrario, prohíbalo". Mientras que las clases y los métodos de Java están abiertos de forma predeterminada, los de Kotlin son definitivos de forma predeterminada. Si desea permitir la creación de subclases de una clase, debe marcar la clase con el modificador open. Además, debe agregar el modificador open a cada propiedad o método que se pueda anular.


open class RichButton : Clickable {

    fun disable() {}

    open fun animate() {}

    override fun click() {}

}


Si anula un miembro de una clase o interfaz base, el miembro anulado también se abrirá de forma predeterminada. Si desea cambiar esto y prohibir que las subclases de su clase anulen su implementación, puede marcar explícitamente el miembro anulado como final:


open class RichButton : Clickable {

    final override fun click() {}

}



sábado, 13 de agosto de 2022

Interfaces en Kotlin


Las interfaces de Kotlin son similares a las de Java 8: pueden contener definiciones de métodos abstractos, así como implementaciones de métodos no abstractos (similares a los métodos predeterminados de Java 8), pero no pueden contener ningún estado. Para declarar una interfaz en Kotlin, se usa la palabra clave de interface en lugar de class.


interface Clickable {

    fun click()

}


Esto declara una interfaz con un solo método abstracto llamado clic. Todas las clases no abstractas que implementan la interfaz deben proporcionar una implementación de este método. 

class Button : Clickable { override fun click() = println("I was clicked") } 

>>> Button().click() 

I was clicked


Kotlin usa los dos puntos después del nombre de la clase para reemplazar las palabras clave extends e implements que se usan en Java. Como en Java, una clase puede implementar tantas interfaces como quiera, pero solo puede extender una clase.

El modificador override, similar a la anotación @Override en Java, se utiliza para marcar métodos y propiedades que anulan los de la superclase o la interfaz. A diferencia de Java, el uso del modificador de anulación es obligatorio en Kotlin. Esto le evita anular accidentalmente un método si se agrega después de escribir su implementación; su código no se compilará a menos que marque explícitamente el método como anulado o le cambie el nombre.

Un método de interfaz puede tener una implementación predeterminada. A diferencia de Java 8, que requiere que marques tales implementaciones con la palabra clave predeterminada, Kotlin no tiene una anotación especial para tales métodos: solo proporcionas un cuerpo de método. Cambiemos la interfaz Clickable agregando un método con una implementación predeterminada


interface Clickable { 

  fun click() 

  fun showOff() = println("I'm clickable!") 

}


Si implementa esta interfaz, debe proporcionar una implementación para hacer clic. Puede redefinir el comportamiento del método showOff o puede omitirlo si está de acuerdo con el comportamiento predeterminado. Supongamos ahora que otra interfaz también define un método showOff y tiene la siguiente implementación para él.


interface Focusable {

    fun setFocus(b: Boolean) = println("I ${if (b) "got" else "lost"} focus.")

    fun showOff() = println("I'm focusable!")

}


¿Qué sucede si necesita implementar ambas interfaces en su clase? Cada uno de ellos contiene un método showOff con una implementación predeterminada; ¿Qué implementación gana? Ninguno de los dos gana. En su lugar, obtiene el siguiente error del compilador si no implementa showOff explícitamente


The class 'Button' must override public open fun showOff() because it inherits many implementations of it


El compilador de Kotlin te obliga a proporcionar tu propia implementación.


class Button : Clickable, Focusable { 

    override fun click() = println("I was clicked")

    override fun showOff() { 

        super<Clickable>.showOff()

        super<Focusable>.showOff() } 

}


La clase Button ahora implementa dos interfaces. Implementa showOff() llamando a ambas implementaciones que heredó de los supertipos. Para invocar una implementación heredada, usa la misma palabra clave que en Java: super. Pero la sintaxis para seleccionar una implementación específica es diferente. Mientras que en Java puede colocar el nombre del tipo base antes de la palabra clave super, como en Clickable.super.showOff(), en Kotlin coloca el nombre del tipo base entre paréntesis angulares: super<Clickable>.showOff().

Si solo necesita invocar una implementación heredada, puede escribir esto:


override fun showOff() = super.showOff()


Puede crear una instancia de esta clase y verificar que se pueden llamar todos los métodos heredados


fun main(args: Array) { 

    val button = Button() 

    button.showOff() 

    button.setFocus(true) 

    button.click() 

}


La implementación de setFocus se declara en la interfaz Focusable y se hereda automáticamente en la clase Button.