Cadenas Infinitas con Java 8

En este artículo presento ejemplos diversos del uso de las nuevas características en Java 8 para implementar el concepto de una cadena infinita de datos (t.c.c. stream): una estructura de datos clásica similar a una lista y que usa evaluación perezosa. El concepto desarrollado en este artículo se basa en la idea de una cadena de datos tal como la desarrollaron Abelson, Gerard y Julie Sussman usando Lisp en su libro Estructura e Interpretación de Programas de Computadora.

Los ejemplos desarrollados aquí representan un ejercicio interesante para comprender mejor la creación de clausuras a través de expresiones lambda y el valor de algunas de las nuevas características de Java 8, como lo son los métodos predeterminados y los métodos estáticos en interfaces. A lo largo del artículo se desarrollarán, progresivamente, varios ejemplos de cadenas infinitas de datos con Java, particularmente una cadena infinita de números naturales, y encima de esta, una cadena infinita de números primos usando la criba de Eratóstenes y, finalmente, una cadena infinita de números Fibonacci.

Definiendo el Tipo de la Cadena

Una lista suele ser definida en función de un sólo eslabón compuesto por dos elementos: el primero es el dato que el eslabón almacena, y el segundo es una referencia al siguiente eslabón de la cadena. Cuando una lista no tiene más eslabones, este segundo elemento puede ser nulo o hacer referencia a otro objeto que represente la ausencia de valor. Una cadena infinita de datos se basa en estos mismos principios, con la única diferencia de que el siguiente eslabón en la cadena es evaluado de forma perezosa, es decir, hasta el momento en que realmente se necesita conocer su contenido. Una forma clásica de conseguir este efecto es por medio de definir la referencia al siguiente eslabón como una clausura sin parámetros (t.c.c. thunk) que al ser evaluada genera, al punto, un nuevo eslabón de la cadena, que a su vez puede generar el siguiente eslabón cuando se necesite, y así por estilo. En otras palabras, es como si tuviésemos una cadena mágica de un solo eslabón que quisiéramos sacar de un agujero y cada vez que halamos ese único eslabón para sacarlo del agujero, un nuevo eslabón se le añade a la cadena. A esto también se le conoce como una corriente o stream, y si dicha corriente no tiene fin pues es, evidentemente, una corriente infinita.

Consideremos ahora la definición de la siguiente interfaz para representar este concepto de ese único eslabón de nuestra mágica cadena:

public interface Stream<T> {
    public T head();
    public Stream<T> tail();
    public boolean isEmpty();
}

Se puede ver que al invocar el método head() obtenemos acceso a un valor de un tipo paramétrico contenido en el eslabón, y al invocar el método tail() podemos solicitar la generación del siguiente eslabón que, como vemos, es del mismo tipo que el primero y por tanto tiene similares capacidades. Ahora bien, la comprensión de que una corriente no es nada más que este simple eslabón con dos valores asociados es de vital importancia para poder asimilar los conceptos presentados en los siguientes párrafos.

Implementación de la Cadena

Pasemos ahora a la implementación de los tipos de eslabones. Para esto nos basaremos en la definición de un tipo de dato algebraico para listas. Bajo este modelo, tenemos dos tipos de eslabones, uno de ellos puede almacenar un valor, y el otro representa un eslabón vacío. Para comenzar definamos el más sencillo de los dos: el eslabón vacío (t.c.c. Null o Nil).

public class Empty<T> implements Stream<T> {
   public T head() {
      throw new UnsupportedOperationException("Empty stream");
   }
   public Stream<T> tail() {
      throw new UnsupportedOperationException("Empty stream");
   }
   public boolean isEmpty() { return true; }
}

Un eslabón de este tipo tan solo representa la ausencia de valor y se utiliza para marcar el final de la estructura de datos cuando es finita, como veremos más adelante. Nótese como el método isEmpty() retorna verdadero.

En contraste veamos ahora la definición de un eslabón diseñado para contener un valor paramétrico cualquiera.

public class Cons<T> implements Stream<T>{

        private final T head;
        //clausura/thunk
        private final Supplier<Stream<T>> tail;

        public Cons(T head, Supplier<Stream<T>> tail) {
            this.head = head;
            this.tail = tail;
        }

        public T head() {
            return this.head;
        }

        public Stream<T> tail() {
            //desencadena evaluación de la clausura
            return this.tail.get();
        }

        public boolean isEmpty() {
            return false;
        }
}

Nótese como se provee un valor en el constructor para que el eslabón lo almacene. Sin embargo, el punto importante aquí es que en el constructor no se provee una referencia al siguiente eslabón, sino más bien se provee un objeto de tipo Supplier. Esta es una de las nuevas interfaces en Java 8 y contiene un solo método llamado get() que puede retornar cualquier valor paramétrico que queramos. Este argumento representa una clausura sin parámetros que podemos utilizar para generar el siguiente eslabón de la cadena al invocar su método get(). En Java 8 esta interfaz puede ser implementada mediante una expresión lambda (o una referencia de método), lo cual simplifica significativamente su definición. Se puede ver como el método tail(), más abajo, desencadena la invocación del método get() en la cola, evaluando mediante esto la clausura y causando como resultado la creación de un nuevo eslabón.

La fortaleza de este modelo es que el siguiente eslabón de la cadena no será generado sino hasta que se solicite mediante invocar el método tail(), que a su vez desencadena la evaluación del método get() en la clausura o thunk. Esto quiere decir, también, que cuando un eslabón se crea, no se está en la obligación de definir el siguiente eslabón de la cadena de inmediato, sino solo de proveer un medio para crearlo cuando se necesite.

Una Cadena Infinita de Números

Veamos ahora cómo podemos utilizar todo esto para escribir una pequeña pieza de código que represente una corriente infinita de números enteros comenzando desde un número n:

public static Stream<Integer> from(int n) {
   return new Cons<>(n, () -> from(n+1));
}

Como lo prometimos el eslabón está compuesto por dos valores: el primero es el valor de n, y el segundo es una expresión lamba que es la implementación del método get de la clausura (un objeto de tipo Supplier). Esta expresión lambda contiene suficiente información sobre cómo generar el siguiente eslabón de la cadena, en este caso mediante volver a invocar al método from, pero esta vez comenzando en n+1. Dicha invocación solo ocurrirá si se invoca al método tail del eslabón eslabón original, lo cual desencadenará la generación de este nuevo eslabón con el valor de n+1 y una nueva clausura capaz, a su vez, de calcular el siguiente eslabón si se necesita, y así por el estilo, infinitamente. Entonces, lo más importante es reconocer que la función de la clausura no será evaluada en el mismo instante en que se invoca el método from, sino hasta el momento en que se invoque el método tail del eslabón y por eso la clausura es el mecanismo ideal para implementar la evaluación perezosa.

Conceptualmente hablando, la corriente de datos definida arriba es infinita. Si creamos un ciclo para extraer el primer elemento de cada eslabón podríamos correr este ciclo por la eternidad (si no fuera por las limitaciones en los recursos de memoria de la computadora y por el hecho de que los enteros de Java son de 32 bits y se reinician en el extremo opuesto cuando ocurre un desbordamiento).

Una Cadena Finita Basada en un Predicado

Ahora bien, dado que la cadena es infinita y no podemos recorrerla sin entrar en un ciclo infinito necesitamos un mecanismo para limitar el tamaño de la cadena a un número discreto de elementos. Una manera de conseguir esto es por medio de crear otra cadena basada en la primera, pero esta segunda será una cadena finita. Para definir el número de elementos que la cadena finita debe contener podemos usar un predicado, es decir una función booleana que nos dice si un eslabón dado satisface nuestro criterio de selección.

Para efectos de implementar esta funcionalidad podemos usar la nueva interfaz funcional de Java 8 apropiadamente llamada Predicate. También, convenientemente, ahora Java soporta métodos predeterminados y métodos estáticos en  las interfaces lo cual nos permite añadir comportamiento directamente en la interfaz Stream original y así garantizar que este comportamiento será heredado por todas las implementaciones de forma predeterminada. Usando todas estas nuevas características podemos agregar los siguientes métodos a nuestra interfaz original:

public interface Stream<T> {
   //…
   public default Stream<T> takeWhile(Predicate<? super T>) {
       return takeWhile(this, predicate);
   }

   public static <T>  Stream<T> takeWhile(Stream<? extends T> source,
                                          Predicate<? super T> predicate) {
       if(source.isEmpty() || !predicate.test(source.head())) {
           return new Empty<>();
       }
       //un nuevo eslabón y una clausura para buscar el siguiente
       return new Cons<>(source.head(),
                         () -> takeWhile(source.tail(), predicate));
   }
}

Nótese como el método takeWhile verifica si un el valor dentro del eslabón pasado como argumento satisface el predicado o criterio de búsqueda, y si es así, lo pone en un nuevo eslabón cuya clausura contiene una función para buscar el siguiente eslabón que satisfaga el mismo predicado dentro de la corriente original.  El punto de mayor relevancia es que cuando se encuentre un eslabón que no satisface el criterio de búsqueda, en ese momento se retorna un eslabón vacío para marcar el final de la cadena y así definir una cadena finita. Cabe rescatar que, de nuevo, al invocar este método sólo se crea el primer eslabón de la cadena, y el siguiente eslabón será creado a solicitud mediante invocar al método tail en este primer eslabón creado.

Podemos ahora crear una cadena finita a partir de la cadena infinita como la del ejemplo siguiente en donde se crea una cadena que contiene los primeros 10 números naturales:

Stream<Integer> tenNats = from(0).takeWhile(n -> n < 10);

Consumiendo la Cadena

El siguiente paso consiste en implementar un método que nos permita consumir el contenido de una cadena finita. Para este propósito agregaremos un método forEach a nuestra interfaz y le proveeremos como argumento un Consumer, una de las nuevas interfaces funcionales en Java 8, que será el objeto que finalmente consumirá el valor en cada eslabón, por ejemplo, imprimiendo su valor a la salida del sistema.

public interface Stream<T> {
   //…
   public default void forEach(Consumer<? super T> consumer) {
      forEach(this, consumer);
   }

   public static <T>  void forEach(Stream<? extends T> source,
                                   Consumer<? super T> consumer) {
      while(!source.isEmpty()) {
         consumer.accept(source.head());
         //desencadena evaluación de la clausura
         source = source.tail();
      }
  }
}

Podemos ver como acá sacamos provecho que los eslabones vacíos retornan verdadero en el método isEmpty. Ahora si queremos imprimir esos primeros 10 números naturales todo lo que hay que hacer es:

from(0).takeWhile(n –> n < 10)
       .forEach(System.out::println);

Esto significa que de la cadena infinita creada por el método from deseamos crear una nueva cadena finita que contenga solo aquellos elementos que sean menores a 10, y de esta nueva cadena finita, deseamos imprimir el valor de cada eslabón a la consola del sistema.

Filtrando Elementos

Otro método de gran utilidad es uno que nos permita filtrar elementos de la cadena basándose en un predicado. Este método se distingue del método takeWhile en que este último extrae los primeros elementos de una cadena hasta encontrar uno que ya no satisface el predicado, mientras que filter debería extraer todos los elementos del cadena de que satisfacen el un predicado, independientemente de su ubicación o de que entre dos elementos candidatos haya uno que no satisface el predicado. Es decir, filter está en la obligación de recorrer la cadena completa a fin de encontrar los elementos filtrables, y por lo tanto, en el caso de cadenas infinitas, el método se debe implementar con evaluación perezosa, o de lo contrario su ejecución no tendría fin.

public interface Stream<T> {
   //…
   public default Stream<T> filter(Predicate<? super T> predicate) {
      return filter(this, predicate);
   }

   public static <T> Stream<T> filter(Stream<? extends T> source,
                                      Predicate<? super T> predicate) {
      if(source.isEmpty()) {
         return new Empty<>();
      }
       if(predicate.test(source.head())) {
          return new Cons<>(source.head(),
                            () -> filter(source.tail(), predicate));
       }
      return filter(source.tail(), predicate);
   }
}

Este enfoque tiene la desventaja de que nos podemos topar con un StackoverflowError en la última línea, pero dejando esto de lado, podemos ver como se itera sobre la cadena original hasta encontrar el primer eslabón que satisface el predicado, en cuyo caso se crea y retorna un nuevo eslabón conteniendo el valor encontrado y mediante una clausura se define la manera de buscar el siguiente eslabón del filtro, de forma perezosa. Ahora podemos hacer algo interesante como imprimir sólo los números impares entre un rango de 0 a 9 a partir de nuestra cadena infinita, algo así:

from(0).takeWhile(n -> n < 10)
       .filter(n -> n % 2!= 0)
       .forEach(System.out::println);

Claro que podemos trabajar con cualquier otro número de elementos, no solo 10, porque nuestra cadena original es infinita y es solo limitada por las condiciones de nuestro predicado. Así que de la misma sencilla manera podríamos extraer los primeros 100 números y definir un consumidor que los envíe por la red a través de un socket.

La Criba de Eratóstenes

Llegados a este punto estamos listos para implementar algo realmente interesante como la criba de Eratóstenes, un algoritmo ejemplar para crear una cadena infinita de números primos.

La criba de Eratóstenes consiste en algo como lo siguiente: supongamos que tenemos una cadena infinita de números, comenzando con el número 2. Tomamos el primer elemento (2 en este caso) y removemos del resto de la cadena (su cola) todos los divisores de 2, mediante esto, creando una nueva cadena infinita que no contiene los divisores de 2. A continuación, de esta nueva cadena infinita tomamos el siguiente eslabón, es decir 3, y removemos de la cola de la cadena todos los elementos divisibles por 3, de nuevo, generando una nueva cadena infinita que no contiene los divisores de tres. Ahora tomamos el siguiente eslabón, en este caso 5 (puesto que 4 ya había sido removido al ser divisible entre 2), y continuamos con este proceso infinito. Así, esta cadena infinita que se genera mediante este proceso solo contiene número primos.

public static Stream<Integer> sieve(Stream<Integer> s) {
    return new Cons<>(s.head(),
                 ()-> sieve(s.tail().filter(n -> n % s.head() != 0)));
}

Nótese que el método recibe una cadena original s, que podemos asumir es una cadena infinita de número naturales comenzando con el número 2. De ahí partimos para crear una nueva cadena (la criba) que solo contiene este primer elemento, y el siguiente elemento es una clausura que al ser evaluada genera un nuevo eslabón basado en volver a invocar el método sieve pero esta vez usando como cadena de origen la cola de la cadena anterior, solo que esta vez se han filtrado todos los eslabones divisibles entre el valor del eslabón anterior.

Usando todo lo que hemos definido hasta ahora podemos hacer algo interesante como imprimir a la consola los números primos menores que 100:

sieve(from(2)).takeWhile(n -> n < 100)
              .forEach(System.out::println);

Acá usamos el método from para generar la cadena de números naturales inicial comenzando con el primer número primo (es decir 2) y el método sieve usa este primer argumento para filtrar los elementos de su cola, que la clausura usará como argumento para generar el siguiente eslabón en la cadena infinita.

Una Cadena Infinita de Números Fibonacci

Con la misma simplicidad podemos definir ahora una cadena infinita de números Fibonacci, así:

public static Stream<Integer> fibonacci() {
   //first two fibonacci and a closure to get the rest.
   return new Cons<>(0,() -> new Cons<>(1, () -> nextFibPair(0,1)));
}

private static Stream<Integer> nextFibPair(int a, int b) {
   int fib = a + b, prev = b;
   return new Cons<>(fib, () -> nextFibPair(prev, fib));
}

Podemos ver que la cadena comienza con los eslabones conteniendo los valores 0 y 1, que son los dos primeros números Fibonacci, y de ahí definimos una clausura capas de generar el siguiente número Fibonacci basado en los dos anteriores.

Claramente el hecho de que Java ahora soporta expresiones lambda y métodos predeterminados ha simplificado la programación de estos ejemplos. Aunque se puede reconocer también que esto ya se podía hacer con Java antes de esta versión, es solo que se requería el uso de clases anónimas y mucho código repetitivo.

Se puede descargar implementación completa de este ejemplo desde este Gist.

Objetos Opcionales con Java 8

SurpriseBoxEn el presente artículo se presentan varios ejemplos sobre cómo usar los nuevos objetos opcionales ahora disponibles en Java 8 y se hacen comparaciones con enfoques similares en otros lenguajes de programación, particularmente el lenguaje programación funcional SML y el lenguaje de programación basado en el JVM llamado Ceylon, este último actualmente en desarrollo por Red Hat.

Es importante resaltar que la introducción de los objetos opcionales ha sido sujeto de debate. En este artículo se presenta una perspectiva del problema que éstos pretenden resolver y se hace un esfuerzo por mostrar argumentos a favor y en contra de su uso. El artículo se inclina ligeramente hacia la opinión de que los objetos opcionales son de gran valor en ciertos escenarios, sin embargo, cada quien tiene derecho a una opinión e idealmente este artículo provee suficiente información para que sus lectores se formen una opinión bien informada sobre este asunto. Sobre todo las comparaciones con las estrategias que algunos lenguajes de programación (modernos y no tan modernos) han adoptado para lidiar con el problema proveen una base sólida para considerar que el tema es digno de profunda consideración.

Sobre el Tipo Null

En Java usamos referencias para obtener acceso a los objetos, y cuando no tenemos un objeto específico al que nuestra referencia deba apuntar entonces le asignamos un valor nulo para implicar la ausencia de valor.

Es interesante que en Java null es en realidad un tipo, uno especial: no tiene nombre, no podemos declarar variables de su tipo, o convertir referencias de su tipo a otros tipos (casting). De hecho existe un solo valor con el que se puede asociar (la literal null), y a diferencia de otros tipos en Java, una referencia de tipo null puede ser con seguridad asignada a cualquier otro tipo de referencia (Véase JLS 3.10.7 y 4.1).

EL uso de null es tan común que raras veces meditamos en cómo afecta la manera como programamos. Por ejemplo, null es el valor predeterminado de las referencias que son variables miembro en nuestros objetos, y los programadores, en general, inicializamos a null toda referencia para la cual no tenemos otro valor inicial que darle. Las usamos en todas partes para implicar que, habiendo llegado a cierto punto, no sabemos o no tenemos un valor que dar a una referencia.

El Problema de las Referencias Nulas

Ahora bien, el problema con las referencias nulas es que si tratamos de utilizarlas pensando que en realidad apuntan a algún objeto entonces nos ocurre el bien conocido y siempre temido NullPointerException.

Cuando trabajamos con una referencia proveniente de un contexto diferente de aquel en el que estamos escribiendo nuestro código (por ejemplo, porque la recibimos como argumento del método en el que trabajamos o la recibimos como resultado de la invocación de un método cuyo valor de resultado queremos usar), todos quisiéramos evitar este error que tiene el potencial de hacer que nuestras aplicaciones se caigan. Sin embargo, con frecuencia, el problema pasa desapercibido y encuentra la manera de colarse y llegar hasta nuestros críticos sistemas de producción en donde espera el momento propicio para causar un fallo (lo que suele suceder un viernes a eso de las 5 p.m. y justo cuando estamos por dejar la oficina para ir al cine con la familia o ir a tomar unas cervezas con los amigos).

Para complicar las cosas, el lugar en donde se produce el fallo raras veces es el mismo lugar en donde se originó el problema, ya que nuestra problemática referencia podría haber sido inicializada a null en un lugar bastante lejano de aquél en donde hemos intentado utilizarla. Así que es mejor cancelar esos planes de viernes por la noche…

Sir Antony Hoare

Cabe señalar que este concepto potencialmente problemático de las referencias nulas fue introducido originalmente por Antony Hoare, el creador de Algol, allá por el año de 1965. En aquel entonces las consecuencias de su diseño no era evidentes, y el mismo Hoare dice que a él le pareció una buena idea. Sin embargo, más tarde lamentó haberla concebido y la llamó “un error de un billón de dólares“, precisamente refiriéndose a las incontables horas que muchos de nosotros hemos pasado, desde entonces, tratando de arreglar estos problemas de referencias nulas que han sido mal utilizadas.

¿Por qué No Mejorar el Sistema de Tipos?

¿No sería estupendo si el sistema de tipos pudiera ver la diferencia entre una referencia que, en un contexto específico, pudiera ser potencialmente nula de una que no lo es? Esto podría mejorar mucho las cosas en términos de la seguridad de tipos porque el compilador entonces podría exigir que el programador realice alguna verificación para aquellas referencias que pueden ser nulas a la vez que permite el uso directo de aquellas que no lo son.

Entonces hemos identificado aquí una oportunidad de mejora en el sistema de tipos. Esta teórica mejora podría ser particularmente útil en la definición de las interfaces públicas de nuestras APIs porque incrementaría el poder expresivo del lenguaje dándonos una herramienta, aparte de la documentación, para informar a nuestros usuarios sobre si un método dado podría retornar o no un valor.

Antes de que profundicemos más en este tema, es importante aclarar que este es probablemente un ideal que lenguajes de programación modernos perseguirán (ya hablaremos de Ceylon y Kotlin más adelante), pero no es una tarea sencilla tratar de cerrar este agujero en el sistema de tipos de un lenguaje como Java, menos cuando se intenta hacer como una idea de último momento. Así que en los párrafos siguientes se presentan algunos escenarios en los cuales el uso de objetos opcionales puede (cuestionablemente) aliviar esta carga un poco. Aún así, el daño está hecho, y no parece que haya alguna forma sencilla y eficiente de deshacerse de las referencias nulas en el futuro previsible. Así que lo mejor es aprender a lidiar con ellas. Comprender el problema es un primer paso, y en la cuestionable opinión de este escritor, los objetos opcionales son sólo otro mecanismo para lidiar con él, particularmente bajo ciertos escenarios en donde nos gustaría expresar la ausencia de valor, como veremos a continuación.

Búsqueda de Elementos

Existe un conjunto de modismos de programación en lo cuales el uso de referencias nulas es común, pero potencialmente problemático. Uno de estos ejemplos es cuando buscamos un elemento dentro de una colección que finalmente no podemos encontrar. Consideremos ahora la siguiente pieza de código usada para encontrar la primera fruta de cierto nombre dentro de una lista de frutas:

public static Fruit find(String name, List<Fruit> fruits) {
   for(Fruit fruit : fruits) {
      if(fruit.getName().equals(name)) {
         return fruit;
      }
   }
   return null;
}

Como vemos el creador de este código ha hecho el uso de null parte de su diseño para indicar la ausencia de valor: cuando no se encuentre un elemento que satisfaga el criterio de búsqueda este método retorna null (7). Es desafortunado, sin embargo, que no es evidente en la firma de este método que el mismo podría no retornar un valor.

Consideremos ahora un segundo programador que es el consumidor de este método. En el siguiente ejemplo dicho programador intenta utilizar el resultado de una invocación al método anterior:

List<Fruit> fruits = asList(new Fruit("apple"),
                            new Fruit("grape"),
                            new Fruit("orange"));

Fruit found = find("lemon", fruits);
//algún código de por medio y más tarde (posiblemente en otro lugar)...
String name = found.getName(); //uh oh!

Tal simple pieza de código tiene un error que no puede ser detectado por el compilador, ni siquiera por simple observación por parte del programador (quien pudiera no tener acceso al código original del método find). Nuestro programador en este caso ha fallado ingenuamente en reconocer el escenario en el cual el método find de arriba podría retornar un valor nulo para indicar la ausencia de un valor que pueda satisfacer su predicado de búsqueda. Este código está esperando a ser ejecutado para simplemente fallar y ninguna cantidad de documentación va a evitar que este error ocurra, y el compilador ni siquiera se dará por enterado de que existen un problema potencial aquí.

Es de especial atención que la línea en donde la referencia se inicializa a null (5) es diferente de la línea problemática (7). En este caso están lo suficientemente cerca como para corregir el problema fácilmente, pero en la práctica podría no ser tan sencillo.

Para evitar este problema lo que típicamente hacemos es que verificamos si la referencia dada es nula antes de utilizarla. En ciertos casos esta verificación se repite tantas veces para una misma referencia que Martin Fowler (reconocido autor de un libro sobre refactorización de código) sugirió que para este tipo de escenario es posible mitigar la necesidad de realizar estas verificaciones por medio de utilizar lo que él ha llamado un Objeto Nulo. En nuestro ejemplo de arriba, el creador del método find, en vez de retornar null podría haber retornado un objeto NullFruit que no es otra cosa que un tipo de Fruit que está vacío por dentro pero que, a diferencia de una referencia null, puede responder a la misma interfaz pública que Fruit evitando, esta manera, la necesidad de realizar verificaciones de nulidad.

Mínimos y Máximos

Otro lugar en donde este modismo se presenta es cuando se intenta reducir una colección a un valor, por ejemplo a la hora de buscar el valor mínimo o máximo dentro de una colección es un buen ejemplo de esto. Consideremos la siguiente pieza de código en donde se intenta determinar cual es la cadena de caracteres más larga de una colección:

public static String longest(Collection<String> items) {
   if(items.isEmpty()){
      return null;
   }
   Iterator<String> iter = items.iterator();
   String result = iter.next();
   while(iter.hasNext()) {
       String item = iter.next();
       if(item.length() > result.length()){
          result = item;
       }
   }
   return result;
}

En este caso la cuestión radica en el problema de qué se debe retornar si la colección estuviera vacía. En este caso se retorna un valor nulo, una vez más, abriendo con esto la puerta para un potencial problema de utilización de una referencia nula.

Estrategia del Mundo Funcional

Es interesante que en el mundo de la programación funcional, los lenguajes estáticamente tipificados evolucionaron en una dirección muy diferente y representan un contraste valiosísimo ante la deficiente aproximación del paradigma imperativo. En lenguajes como Standard ML o Haskell no existe tal cosa como un valor nulo que cause excepciones cuando se intenta usar. Estos lenguajes, en su lugar, proveen un tipo de datos especial que es capaz de contener valores opcionales y que puede ser utilizando convenientemente para expresar también la potencial ausencia de valor. La siguiente pieza de código muestra la definición del tipo option en SML:

datatype 'a option = NONE | SOME of 'a

Como podemos ver option es un tipo de datos con dos constructores (o lo que se suele conocer como un tipo de unión o suma), uno de ellos no almacena nada (p.ej. NONE) mientras que el otro es capaz de almacenar un valor paramétrico del algún tipo 'a (donde 'a representa el tipo del valor almacenado).

Bajo este modelo, la pieza de código escrita anteriormente en Java para buscar una fruta por nombre puede ser reescrita en SML de la siguiente manera:

fun find(name, fruits) =
   case fruits of
        [] => NONE
      | (Fruit s)::fs => if s = name
                         then SOME (Fruit s)
                         else find(name,fs)

Hay múltiples formas de escribir esto en SML, este ejemplo sólo muestra una forma de hacerlo. Lo importante aquí es que no existe tal cosa como un valor nulo. En su lugar el valor NONE es retornado cuando no se encuentra lo buscado (3) o en el caso contrario un valor SOME f (5) .

Cuando un programador usa esta función find sabe que retorna un tipo option y por lo tanto se ve obligado a verificar la naturaleza del valor obtenido para determinar si es NONE (6) o SOME f (7), más o menos algo así:

let
   val fruits = [Fruit("apple"), Fruit("grape"), Fruit("orange")]
   val found = find("grape", fruits)
in
   case found of
       NONE => print("Nothing found")
     | SOME(Fruit f) => print("Found fruit: " ^ f)
end

El verse en la obligación de verificar la naturaleza del valor retornado hace imposible malinterpretar el resultado.

Tipos Opcionales en Java

Es una alegría que en Java 8 finalmente tendremos una clase llamada Optional que nos permite implementar un modismo similar a este del mundo funcional. Tal como en el caso de SML, este tipo opcional es polimórfico y puede contener un valor o estar vacío. Así las cosas, podemos reescribir nuestro ejemplo anterior de la siguiente manera:

public static Optional<Fruit> find(String name, List<Fruit> fruits) {
   for(Fruit fruit : fruits) {
      if(fruit.getName().equals(name)) {
         return Optional.of(fruit);
      }
   }
   return Optional.empty();
}

Como vemos el método ahora retorna una referencia de tipo Optional (1). Si se llega  encontrar un valor el objeto Optional se construye con el valor encontrado (4), de otra manera se construye un objeto Optional vacío (7).

Ahora nuestro segundo programador y consumidor de esta API escribiría su código de la siguiente manera:

List<Fruit> fruits = asList(new Fruit("apple"),
                            new Fruit("grape"),
                            new Fruit("orange"));

Optional<Fruit> found = find("lemon", fruits);
if(found.isPresent()) {
   Fruit fruit = found.get();
   String name = fruit.getName();
}

Ahora que es evidente en la firma de método find que éste retorna un valor opcional nuestro segundo programador se ve mejor orientado para escribir su código correspondientemente para extraer el valor opcional, siempre que esté presente (6-7).

Vemos que la adopción de este modismo del mundo funcional tiene el potencial de hacer nuestro código más legible, menos propenso al uso de referencias nulas y como resultado más robusto y seguro.

El lector avezado seguramente se apresurará a apuntar que esta solución no resuelve para nada el problema puesto que Optional no es más que una referencia en sí misma que puede ser erróneamente inicializada a null. Más adelante hablaremos en detalle de esto. De momento solo resta decir que se esperaría que los programadores de APIs (como la contiene el método find de arriba) se apeguen a la convención de nunca proveer una referencia nula en donde se espera un Optional mas o menos de la misma manera como es una buena práctica no utilizar una referencia nula en donde se espera una colección o un arreglo; es costumbre, mas bien,  proveer una colección vacía o un arreglo vacío en estos casos.

El punto a resaltar aquí es que existe ahora un mecanismo en la API que podemos utilizar para hacer explícito que en cierto caso debemos lidiar con la posible ausencia de valor y los usuarios de esta API se ven en la obligación de utilizarla correspondientemente. En un artículo sobre el uso de objetos opcionales en el framework de colecciones conocido como Guava se ofrece una explicación magistral:

Además del incremento en la legibilidad que acompaña al hecho de darle a null un nombre, la mayor ventaja del uso de Optional es que es a prueba de idiotas. Simplemente nos fuerza a pensar de forma activa en el caso de ausencia de valor si deseamos que nuestro programa compile del todo, ya que somos forzados a desenvolver el Optional y a atender ese caso.

Otros Métodos Convenientes

Al día de hoy, además de los métodos estáticos of y empty demostrados arriba, la clase Optional contiene los siguientes métodos de conveniencia:

ifPresent() Retorna verdadero si hay un valor presente en el objeto opcional.
get() Retorna el valor contenido dentro del objeto opcional, si existe, de otra manera arroja una excepción  NoSuchElementException.
ifPresent(Consumer<T> consumer) Le proporciona como argumento al consumidor provisto el valor del objeto opcional, si existe. El consumidor se puede implementar mediante una expresión lambda o mediante una referencia de método.
orElse(T other) Retorna el valor del objeto opcional, si existe, de otra manera retorna el otro valor provisto.
orElseGet(Supplier<T> other) Retorna el valor del objeto opcional, si existe, de lo contrario utiliza el suplidor provisto para generar un valor. El suplidor se puede implementar mediante una expresión lambda o una referencia de método.
orElseThrow(Supplier<T> exceptionSupplier) Retorna el valor de objeto opcional, si existe, de otra manera utiliza el suplidor provisto para generar y arrojar una excepción. El suplidor se puede implementar mediante una expresión lambda o un método de referencia.

Cómo Evitar Repetitivas Verificaciones de Presencia

Podemos utilizar algunos de los métodos de conveniencia arriba citados para evitar la necesidad de verificar si el valor del objeto opcional está presente o no. Por ejemplo, una alternativa es utilizar un valor predeterminado en caso de que el objeto opcional este vacío.

Optional<Fruit> found = find("lemon", fruits);
String name = found.orElse(new Fruit("Kiwi")).getName();

En este otro ejemplo, se imprime el nombre de la fruta a la salida del sistema media proveer un consumidor, en este caso implementado mediante una expresión lambda.

Optional<Fruit> found = find("lemon", fruits);
found.ifPresent(f -> { System.out.println(f.getName()); });

En el siguiente ejemplo se utiliza una expresión lambda para implementar un suplidor que puede finalmente proveer un valor predeterminado para el objeto opcional si este estuviera vacío.

Optional<Fruit> found = find("lemon", fruits);
Fruit fruit = found.orElseGet(() -> new Fruit("Lemon"));

Claramente estos métodos de conveniencia simplifican mucho el tener que trabajar con objetos opcionales.

¿Que Hay de Malo con los Objetos Opcionales de Java?

La principal pregunta a la que nos enfrentamos es: ¿se deshacen los objetos opcionales de Java de las referencias nulas? La respuesta es, por supuesto, un enfático ¡no!. Así que los detractores de este enfoque inmediatamente lo cuestionan preguntando: entonces ¿qué de bueno tienen que no pudiéramos hacer ya por otros medios?

A diferencia de los lenguajes funcionales como SML o Haskell que nunca tuvieron el conceptos de referencias nulas, en Java simplemente no podemos deshacernos de ellas como por arte de magia, como si históricamente nunca hubieran existido. Estas continuarán existiendo, y cuestionablemente tienen sus usos apropiados (solo para mencionar un ejemplo, la lógica de tres valores).

Es poco probable que la intención de la clase Optional sea reemplazar el uso de las referencias nulas. Es más probable que su propósito sea ayudar en la creación de APIs más legibles y robustas como se demostró anteriormente. Sin embargo, como se mencionó antes, al final Optional es sólo otra referencia sujeta a las mismas debilidades que todas las demás, así que es claro que Optional no va a salvar el día.

Cómo se deben usar estos tipos opcionales o sí de verdad serán de valor en Java ha sido un tema de agitado debate en la lista de distribución del Proyecto Lambda, que es donde esta clase vio la luz del día por primera vez. De los detractores de esta estrategia escuchamos algunos otros argumentos interesantes que a continuación se enumeran (con toda seguridad no de manera exhaustiva).

  • Existen otras alternativas para lidiar con este problema. Por ejemplo tenemos la JSR-305 sobre la detección de defectos de software que incorpora anotaciones como @Nullable y @NonNull. El entorno integrado de desarrollo Eclipse tiene un conjunto anotaciones propietarias que se pueden usar en el código para realizar análisis estático del código y determinar si una referencia puede ser nula o no.
  • Parte del debate gira en torno al deseo de hacer de los tipos opcionales en Java algo similar a lo que son en el paradigma funcional, lo cual no es simple de implementar pues Java carece de otros importantes mecanismos de programación funcional como la coincidencia de patrones y un sistema de tipos estructural.
  • Una importante queja es la imposibilidad de redefinir el código preexistente para utilizar este modismo. Por ejemplo, List.get(Object) continuará retornando null en los casos en donde no se encuentre el valor buscado. En este caso particular y probablemente en muchos otros en las misma librerías de Java es imposible redefinir el método sin romper la compatibilidad hacia atrás.
  • En esta misma línea de pensamiento, algunos argumentan que la falta de soporte para tipos opcionales al nivel del lenguaje crea un escenario en donde Optional podría ser utilizado de manera inconsistente en las APIs y como consecuencia de esto, creando incompatibilidades, muy similares a las que experimentaremos al combinar código de las  APIs de Java que no usan Optional con otras partes de la API que sí lo hacen.
  • Finalmente, un argumento de bastante peso es que al invocar al método get en un objeto opcional vacío se produce una excepción de NoSuchElementException que es precisamente el mismo problema que tenemos con las referencias nulas, solo que ahora con una excepción diferente. Así que si alguien usa un objeto opcional sin verificar si está vacío podría experimentar el mismo problema de quien usa una referencia nula sin verificar primero si es nula.

Habiendo considerado todo esto, pareciera que los beneficios de Optional se limitan a mejorar la legibilidad y reforzar el uso de los contratos públicos de las interfaces.

Objetos Opcionales en la Nueva API de Streams

Independientemente del debate, los objetos opcionales están aquí para quedarse y ya se están usando en diversos métodos de la nueva API de streams que sera parte del JDK 8, particularmente en métodos como findFirst, findAny, max y min.

Por ejemplo, consideremos la siguiente pieza de código en donde buscamos la última fruta de una colección de frutas en donde buscamos comparando los nombres de las frutas alfabéticamente:

Stream<Fruit> fruits = asList(new Fruit("apple"),
                              new Fruit("grape")).stream();
Optional<Fruit> max = fruits.max(comparing(Fruit::getName));
if(max.isPresent()) {
   String fruitName = max.get().getName(); //grape
}

O este ejemplo en donde extraemos la primera fruta de una colección:

Stream<Fruit> fruits = asList(new Fruit("apple"),
                              new Fruit("grape")).stream();
Optional<Fruit> first = fruits.findFirst();
if(first.isPresent()) {
   String fruitName = first.get().getName(); //apple
}

Tipos Opcionales en el Lenguaje de Programación Ceylon

Recientemente comencé a jugar un poco con el lenguaje de programación Ceylon ya que estoy haciendo una investigación para otro artículo que espero escribir pronto en este blog. Debo admitir que no soy un fanático de Ceylon, de hecho le guardo pocas esperanzas a su popularización. Sin embargo, me pareció muy interesante que en Ceylon este concepto de los valores opcionales se lleva un poco mas lejos y el lenguaje ofrece azúcar sintáctico para este tipo de modismos. En este lenguaje es posible marcar cualquier tipo con un signo de pregunta (?) para convertirlo en un tipo opcional.

Por ejemplo, nuestra función find sería muy similar a la de nuestra versión de Java original, pero esta vez retornando un Fruit? (1), es decir un valor opcional de tipo Fruit. Nótese que aun se utiliza null, pero éste es compatible con la referencia de tipo Fruit? (7).

Fruit? find(String name, List<Fruit> fruits){
   for(Fruit fruit in fruits) {
      if(fruit.name == name) {
         return fruit;
      }
   }
   return null;
}

Y podríamos consumir este método o función con un código como luce a continuación:

List<Fruit> fruits = [Fruit("apple"),Fruit("grape"),Fruit("orange")];
Fruit? fruit = find("lemon", fruits);
print((fruit else Fruit("Kiwi")).name);

Nótese que el uso de la palabra reservada else es muy similar al método orElse de la clase Optional de Java.

Alternativamente podríamos haberlo desarrollado de la siguiente manera:

List<Fruit> fruits = [Fruit("apple"),Fruit("grape"),Fruit("orange")];
Fruit? fruit = find("apple", fruits);
if(exists fruit){
   String fruitName = fruit.name;
   print("The found fruit is: " + fruitName);
} //else...

Nótese que el uso de la palabra reservada exists (3) sirve el mismo propósito que el método isPresent de la clase Optional de Java.

Es digno de mención asimismo que esta sintaxis es muy similar a la de los tipos anulables de C# (nullable types), aunque la semántica es totalmente diferente en el caso Ceylon como hemos podido ver. Asimismo en el lenguaje de programación Kotlin, bajo desarrollo por JetBrains, existe una característica similar a esta relacionada con la seguridad de los valores nulos. Así que podríamos estar frente a una nueva tendencia para lidiar con este problema en lenguajes de programación modernos).

Ahora bien, la gran ventaja de Ceylon y Kotlin sobre Java es que ellos pueden incorporar estos conceptos en el lenguaje y las APIs desde el comienzo liberándose así de las incompatibilidades que plagarán desde ahora las APIs de Java.

Quien sabe, tal vez en futuras entregas de Java se agregue azúcar sintáctico para poder utilizar tipos opcionales como se hace en Ceylon y Kotlin, quizás usando, bajo el capo, la nueva clase Optional de Java 8.