Translate

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.