Cómo configurarar el REPL de SML con rlwrap

El REPL de smlnj no tiene soporte para readline. Es bastante molesto cuando se abre una sesión del REPL desde la consola y ésta no provee un histórico de comandos, edición de líneas y completado de sintaxis. Sin embargo todos estos problemas se pueden aliviar si usamos rlwrap. Este es un pequeño programa que intercepta la entrada del usuario para proveer edición tipo readline, historia persistente de comandos y completado de sintaxis.

Suponiendo que ya tenemos smlnj instalado, lo que hay que hacer primero es instalar el comando rlwrap. Evidentemente eso varía según el sistema operativo. En mi caso lo he configurado tanto en Ubuntu como en MAC OS, en este último usando Homebrew.

Para los usuarios de Ubuntu o Debian

apt-get install rlwrap

Los usuarios de MAC OS

brew install rlwrap

Y ahora solo resta configurar nuestro comando sml para que utilice rlwrap. En mi caso lo hice por medio de definir un alias en mi archivo .bashrc.

alias sml="rlwrap -f /usr/local/share/sml/keywords -t sml /usr/bin/sml"

El archivo llamado keywords no es otra cosa que un archivo de texto que contiene todos los keywords de SML y que rlwrap utilizara para proveer autocompletado de sintaxis. El siguiente es el contenido de mi archivo keywords según mi investigación de las palabras reservadas de SML:

abstype and andalso as case do datatype else
eqtype end exception fn fun functor handle
if in include infix infixr let local nonfix
of op open orelse raise rec sharing sig
signature struct structure then type val
with withtype while

Y ahora sí estamos listos. No hay más que invocar el comando sml desde la consola y entonces tendremos editado de lineas, histórico de comandos y autocompletado de sintaxis para nuestro REPL.

Polución de Interfaces en Java 8

Una pregunta interesante en relación con Java 8 es por qué razón el grupo de expertos no decidió implementar un tipo función en vez del concepto de interfaces funcionales que utiliza actualmente el nuevo JDK 8. Por ejemplo, en un lenguaje como C# existe un conjunto predefinido de tipos de funciones que varían según el número de argumentos que una función acepta (llamado arity en inglés).

Así en C# tenemos los tipos Func que sirven para representar funciones con un tipo de retorno y Action para representar funciones de tipo de retorno vacío. Ambos tipos están representados por delegates que usan un número variable de tipos genéricos T1, T2, T3, …, T16, según el número de argumentos que la función acepta.

En el JDK 8, sin embargo, lo que tenemos es un concepto de interfaces funcionales que tienen diferentes nombres y diferentes métodos. Sus métodos abstractos representan a alguna de las firmas conocidas de funciones (nularia, unaria, binaria, ternaria, etc). Podríamos preguntarnos por qué el diseño del JDK 8 se aleja de soluciones ampliamente probadas con éxito en otros lenguajes similares, como C#. En este artículo vamos a explorar los diferentes problemas de diseño que enfrentó el grupo de expertos y las razones por las que el JDK 8 fue implementado de esta manera.

El Problema del Borrado de Tipos (Type Erasure)

Uno de los principales problemas de los que adolece Java data de los días en que se agregaron los tipos genéricos al lenguaje (alrededor del 2005 con el JDK 5). En Java la información de los tipos genéricos es descartada por el compilador una vez que éste ha verificado la integridad de los tipos del programa. Las razones por las que los tipos genéricos se implementaron de esta forma en Java son diversas pero una razón de peso era la necesidad de mantener compatibilidad hacia atrás con versiones anteriores de Java. A este proceso que realiza el compilador de descartar la información de los tipos genéricos una vez que ya no la necesita se le conoce como borrado de tipos o en inglés type erasure. La principal implicación de este diseño es, básicamente, que en Java no existiría diferencia entre un tipo Function<T1> y un tipo Function<T1,T2>, o Function<T1,T2,T3> o Function<T1,T2,T3, ..., Tn>. Para efectos de como funciona Java todos ellos estarían representados por un solo tipo llamado Function.

Así que el grupo de expertos tuvo que luchar con este problema. Brian Goetz, líder del proyecto, escribió lo siguiente en la lista de distribución del proyecto lambda:

[…] Como ejemplo sencillo consideremos un tipo función. En la propuesta original del proyecto lambda presentada en devoxx se consideró el tipo función. Yo insistí en quitarlo y esto me hizo muy impopular. Pero mi objeción al uso de un tipo función no se debe a que no me guste el tipo función — me encanta el tipo función — pero este tipo no se lleva bien con un aspecto del sistema de tipos de Java: el borrado de tipos. Las funciones con tipos borrados son el peor de los dos mundos. Así que los quitamos de este diseño.

Pero no estoy diciendo que “Java nunca tendrá un tipo función” (aunque reconozco que Java podría nunca tener un tipo función). Creo que para poder tener un tipo función primero tenemos que lidiar con el problema del borrado de tipos. Puede que eso sea posible o no. Pero en un mundo de tipos estructurales materilazados el tipo función empieza a cobrar mucho más sentido […]

¿Cómo influye todo esto en el diseño final de Java 8?

Bueno, el grupo de expertos decidió proponer una solución poco ortodoxa al problema. En vez de definir un tipo función, en su lugar tenemos una infinidad de nuevas interfaces que representan diferentes tipos de funciones. Veamos algunos ejemplos.

Funciones sin Tipo de Retorno (void)

En el mundo de las funciones sin un tipo de retorno tenemos algo como esto:

Tipo de Función Expresión Lambda Interfaces Funcionales Conocidas
Nullaria
() -> doSomething()
Runnable
Unaria
foo -> System.out.println(foo)
Consumer
IntConsumer
LongConsumer
DoubleConsumer
Binaria
(console,text) -> console.print(text)
BiConsumer
ObjIntConsumer
ObjLongConsumer
ObjDoubleConsumer
n-aria
(sender,host,text) -> sender.send(host, text)
Define tu propio tipo

Funciones con Algún Tipo de Retorno T

En el mundo de las funciones con un tipo de retorno T tenemos algunas de las siguientes:

Tipo de Función Expresión Lambda Interfaces Funcionales Conocidas
Nullaria
() -> "Hello World"
Callable
Supplier
BooleanSupplier
IntSupplier
LongSupplier
DoubleSupplier
Unaria
n -> n + 1
n -> n >= 0
Function
IntFunction
LongFunction
DoubleFunction
IntToLongFunction
IntToDoubleFunction
LongToIntFunction
LongToDoubleFunction
DoubleToIntFunction
DoubleToLongFunction
UnaryOperator
IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator
Predicate
IntPredicate
LongPredicate
DoublePredicate
Binaria
(a,b) -> a > b ? 1 : 0
(x,y) -> x + y
(x,y) -> x % y == 0
Comparator
BiFunction
ToIntBiFunction
ToLongBiFunction
ToDoubleBiFunction
BinaryOperator
IntBinaryOperator
LongBinaryOperator
DoubleBinaryOperator
BiPredicate
n-aria
(x,y,z) -> 2 * x + Math.sqrt(y) - z
Define tu propio tipo

Alguien pudiera decir que una ventaja de esta solución es que nos permite definir nuestros propios tipos, definir nuestras interfaces que acepten tantos argumentos como queramos, y entonces las podemos usar para crear expresiones lambda y referencias de métodos según se nos antoje. En otras palabras, podemos contaminar el mundo con incluso más interfaces funcionales. Otra ventaja es que podríamos usar interfaces funcionales ya existentes en Java para implementar expresiones lambda (p.ej. Comparable, Runnable y Callable) e incluso interfaces que nosotros mismos hubiéramos definido en el pasado, siempre y cuando sean interfaces funcionales (es decir, interfaces que solo tiene un método abstracto).

Sin embargo, la desventaja más evidente es esta explosión de interfaces con diferentes nombres y con diferentes métodos. Y ahora debemos memorizar o al menos recordar o reconocer que existen a fin de poder usarlas en nuestro código. Como todas tienen diferentes nombres y como todos nosotros podemos todos los días definir nuevas, la tarea se vuelve un poco más compleja en comparación con aquellos lenguajes en donde solo existe un tipo función. Es interesante resaltar que en el lenguaje de programación Scala este problem se resolvió definiendo interfaces con diferentes nombres como Function0Function1Function2, …, FunctionN. Es una manera de evitar el problema del borrado de tipos y definir un tipo función para el lenguaje. Asumo que esta solución no fue considerada como válida de cara a explotar la ventaja que sí tienen las interfaces funcionales de poder usar expresiones lambda incluso con tipos definidos en librerías anteriores a la liberación de Java 8.

El Problema de la Ausencia de Tipos Valor (Value Types)

Claramente el borrado de tipos es una de las fuerzas mas influyentes en el diseño de Java 8, pero no es suficiente para explicar por qué, aparte de esta explosión de interfaces, tenemos además otras con nombres similares y cuyas firmas de métodos se diferencian por el uso de tipos primitivos (p.ej. Function, IntFunction, LongFunction, DoubleFunction, etc).

La razón detrás de estas interfaces adicionales radica en el hecho de que en Java no tenemos un concepto de tipos valor (en inglés Value Types) como sí sucede en otros lenguajes como C#. Esto significa que los parámetros de tipos que usamos en las clases, interfaces y métodos genéricos de Java solo pueden ser sustituidos por tipos de referencia (reference types) y no por tipos primitivos.

En otras palabras, esto no es posible en Java

List<int> numbers = asList(1,2,3,4,5);

Pero esto otro sí lo es:

List<Integer> numbers = asList(1,2,3,4,5);

Este segundo ejemplo, sin embargo, incurre en el costo de convertir el tipo primitivo int en un objeto Integer antes de poder almacenarlo en la colección. De la misma manera, al recuperar el entero y usarlo en una operación aritmética, incurriríamos en el costo opuesto de extraer el valor entero encapsulado por el objeto. En inglés a esto se le suele llamar boxing y unboxing. Y este proceso se puede volver realmente caro en términos de desempeño cuando estamos lidiando con colecciones de datos primitivos.

Evidentemente el grupo de expertos necesitaba encontrar una manera de mejorar el desempeño de las nuevas APIs de colecciones en Java 8 para esos casos en donde se usan valores primitivos de datos. Así que para solucionar este problema el grupo de expertos decidió crear aún más interfaces para lidiar con cada uno de los posibles escenarios. En vista de que Java tiene alrededor de 8 diferentes tipos primitivos la explosión de interfaces hubiera sido una locura, y por eso los diseñadores decidieron solo lidiar con los tipos básicos int, long, y double, asumiendo que cualquier otro tipo puede ser fácilmente promovido a alguno de estos otros.

Una vez más recurro a una cita de de Brian Goetz en la lista de distribución del proyecto lambda para demostrar que los diseñadores encontraron grandes dificultades para lidiar con este problema:

[…] la filosofía tras el uso de streams especializados para tipos primitivos (p.ej. IntStream) está plagada de horribles compromisos. Por un lado todo esta horrible duplicación de código, polución de interfaces, etc. Por otro lado, cualquier clase de aritmética sobre tipos encajados (boxed types) apesta, y no tener un escenario para reducir valores de enteros sería terrible. Así que estamos contra la espada y la pared, y estamos tratando de no empeorar las cosas.

Truco #1 para no empeorar las cosas es: no vamos a soportar los ocho tipos primitivos. Solo vamos a soportar int, long, double; todos los demás se pueden simular con estos. Podría decirse que también nos podríamos deshacer de int, pero no creo que los desarrolladores de Java estén listos para eso. Sí, habrá peticiones para Character, y la respuesta es “mételo dentro de un entero” […]

Truco #2 es: estamos usando streams de primitivos para exponer cosas que se manejan mejor en el dominio de los primitivos (ordenamiento, reducción) pero no estamos intentando duplicar todo lo que existe en el dominio de tipos de rerencia. Por ejemplo, no existe IntStream.into(). (Si fuera el caso, la siguiente pregunta sería “Y dónde está el IntCollection? IntArrayList?, IntConcurrentSkipListMap?). La intención es que muchos streams puede comenzar con tipos de referencia y terminar como streams de primitivos, pero no a la inversa. Eso está bien y reduce el número de conversiones necesarias (p.ej. no hay sobrecarga de map para int-> T, no hay especialización de Function para int->T, etc).

Creo que pocos de nosotros pensaríamos que esta solución es deseable, pero creo que la mayoría estaría de acuerdo de que fue necesaria y no parece como que otra solución mejor pudiera haber sido desarrollada sin antes resolver el problema de la ausencia de tipos valor en Java. Al menos en estas citas encontramos la justificación para algo que muchos, de buenas a primeras, podrían considerar descabellado.

El Problema de los Checked Exceptions

Existe una tercera fuerza que pudo haber causado que esta polución de interfaces fuera inclusive peor. Se trata del hecho de que Java soporta dos tipos de excepciones llamados checked y unchecked.  El compilador exige que hagamos algo respecto a un método que puede lanzar una excepción de tipo checked, en esos casos debemos manejar la excepción (p.ej. con un try..catch) en el lugar o declarar que nuestro método también arroja la excepción (p.ej. con una cláusula throws en la firma del método). Ejemplos de excepciones checked son SQLException y IOException para mencionar un par de las más conocidas.

El asunto es que este comportamiento de Java crea un escenario interesante de cara a las interfaces funcionales provistas en Java 8. Todas estas interfaces funcionales tienen un métodos que no declara ninguna excepción. Eso quiere decir que estas interfaces funcionales no se pueden implementar con operaciones que están sujetas al uso de excepciones de tipo checked (a menos que manejemos la excepción directament el cuerpo del método, y en el caso de las expresiones lambda, en el cuerpo de la expresión misma). Por ejemplo, esto no es posible en Java 8:

Writer out = new StringWriter();
Consumer<String> printer = s -> out.write(s); //error de compilación

No se puede hacer esto porque la operación write declara que puede arrojar una excepción de tipo checked, un IOException en este caso, pero la firma del método en la interfaz Consumer que está siendo implementado por la expresión lambda no declara que podría arrojar ningún tipo de excepción.

Como se podrán imaginar, la solución a este problema habría sido crear aún más interfaces funcionales, algunas declarando excepciones y otras no (o implementar alguna otra solución a nivel del lenguaje que permitiera soportar transparencia de excepciones). Y una vez más, para tratar de “no empeorar” las cosas el grupo de expertos decidió no hacer nada en este caso.

Brian Goetz escribió en la lista de distribución del proyecto lambda:

Sí, tendrás que implementar tus propios SAM types excepcionales. Pero entonces la conversión lambda funcionará con ellos.

El grupo de expertos discutió sobre agregar soporte adicional al lenguaje y librerías para solucionar este problema, pero al final sentimos que no se justificaba el costo/beneficio.

Soluciones de librería causaría una explosión de 2x en los tipos SAM (excepcional vs no excepcional), lo cual no interactúa bien con las explosiones de combinaciones existentes para especializaciones de primitivos.

Las soluciones a nivel de lenguaje perdieron valor de cara a su complejidad/valor. Aunque hay algunas soluciones alternativas que vamos a continuar explorando – aunque claramente no para la versión 8 y probablemente tampoco para la 9.

De momento, tienen las herramientas que necesitan. Comprendo que ustedes preferirían que hiciéramos es milla extra por ustedes (y, además su solicitud sería una mampara para decir “por qué no se rinden de una vez con las excepciones tipo checked”), pero creo que el estado actual les permite hacer su trabajo.

Así las cosas, somos nosotros los desarrolladores los que debemos crear nuestras propias explosiones de interfaces para lidiar con este problema caso por caso:

interface IOConsumer<T> {
   void accept(T t) throws IOException;
}

static<T> Consumer<T> exceptionWrappingBlock(IOConsumer<T> b) {
   return e -> {
	try { b.accept(e); }
	catch (Exception ex) { throw new RuntimeException(ex); }
   };
}

A fin de lograr algo como:

Writer out = new StringWriter();
Consumer<String> printer = exceptionWrappingBlock(s -> out.write(s));

Probablemente en el futuro (quizás en el JDK 9) cuando tengamos Soporte para Tipos Valor en Java, y cuando se haya corregido el problema del borrado de tipos, entonces quizás nos podremos deshacernos también (o al menos no necesitar ya más) de toda esta polución de interfaces.

En resumen, podemos ver que el grupo de expertos tuvo que tomar difíciles decisiones de diseño para lidiar con diferentes tipos de problemas en Java a fin de poder implementar muchas de las nuevas características en Java 8 hoy. Aspectos como la carencia de tipos valor, el borrado de tipos y las excepciones tipo checked influenciaron poderosamente el diseño y si Java no hubiera tenido ninguna de estos problemas, la solución final habría sido, probablemente, muy diferente, quizás más parecida a la de otros lenguajes como C#. Pero el grupo de expertos tenía que pintar la raya en en algún lugar. Para bien o para mal, este es el diseño actual, y el tiempo se encargará de demostrar si estas decisiones de diseño fueron buenas o malas o si algo más podría haberse hecho al respecto.

Lectura Adicional

Cómo Declarar Módulos de Node.js

Creo que una de las cosas que me causó un poco de confusión cuando comencé a trabajar con Node.js fue entender exactamente cómo se pueden exponer diferentes tipos objetos utilizando módulos. Al principio pareciera intuitivamente sencillo, pero más tarde se da uno cuenta de que puede exponer funciones, constructores, objetos, o simplemente nuevas propiedades dentro del módulo y para cada caso existen diferentes formas de hacerlo. Veamos unos cuantos ejemplos.

Sobre los Módulos, Importar y Exportar

Empecemos por lo más obvio y sencillo y lo que probablemente todo el mundo entiende desde el primer día de trabajo con Node.js. En node un archivo de código se considera un módulo. Las variables, propiedades, funciones o constructores que se declaren en él son privadas al módulo y otros módulos no pueden verlas ni usarlas a no ser que el programador explícitamente las exponga al público; es decir todo lo que declaramos dentro de un módulo está encapsulado y oculto al mundo exterior de forma predeterminada a no ser que el programador exponga algo explícitamente. Para este propósito cada módulo tiene acceso a un objeto especial llamado module, y este objeto module, a su vez, tiene una propiedad llamada exports. Todo lo que el programador coloque en el objeto exports se vuelve público para ese módulo. Por ejemplo, el módulo foo.js expone una propiedad llamada bar que contiene una cadena de caracteres 'Hello World'. Cualquier otra declaración dentro del módulo sería privada y solo accesible al módulo mismo. Por ejemplo, la variable pi declarada abajo es inaccessible para cualquier otro módulo diferente de foo.js, mientras que la propiedad bar es públicamente accessible para otros módulos que importen el módulo foo.js.

//foo.js
var pi = 3.14; 
module.exports.bar = 'Hello World';

Así las cosas, un segundo módulo baz.js podría “importar” el módulo foo.js y tener acceso a la propiedad bar. En node, conseguimos tal efecto por medio de usar la función require. Por ejemplo de la siguiente manera:

//baz.js
var foo = require('./foo');
console.log(foo.bar); //imprime Hello World

Técnica 1 – Extender exports con Funcionalidad Adicional

Así las cosas, una técnica para exponer la funcionalidad de un módulo consiste en agregar nuevas funciones y propiedades a module.exports. Cuando este es el caso, node.js provee un acceso directo al objeto exports y no es necesario acceder a él a través de la forma un poco más larga module.exports. Por ejemplo:

//foo.js
exports.serviceOne = function(){ };
exports.serviceTwo = function(){ };
exports.serviceThree = function(){ };

Y como se podrán imaginar los usuarios de este módulo, al importarlo, obtendrán una referencia al mismo objeto exports y entonces podrán consumir toda esta misma funcionalidad.

//bar.js
var foo = require('./foo');
foo.serviceOne();
foo.serviceTwo();
foo.serviceThree();

Técnica 2 – Sustituir el Objeto exports Predeterminado por Otro Objeto

Probablemente ustedes podrán intuir, llegados a este punto, que dado que module.exports es el objeto que se expone públicamente para un módulo, entonces perfectamente podríamos sustituir el objeto que nos da node.js de forma predeterminada por nuestro propio objeto. Por ejemplo:

//foo.js
var service = {
   serviceOne: function(){ },
   serviceTwo: function(){ },
   serviceThree = function(){ }
};

module.exports = service;

El código en este último ejemplo se comporta exactamente igual que el código del ejemplo anterior. Es solo que esta vez hemos definido nuestro propio objeto a exportar.

Técnica 3 – Sustituir el Objeto exports Predeterminado por un Constructor

En todos los ejemplos hasta el momento, siempre exponemos una instancia de un objeto, pero nada nos impide sustituir al objeto module.exports por otros tipos de objetos como funciones y constructores. En el ejemplo siguiente exponemos un constructor, que el usuario del módulo puede utilizar más tarde para crear una instancia de este tipo de objeto.

//Foo.js
function Foo(name){
   this.name = name;
}

Foo.prototype.serviceOne = function(){ };
Foo.prototype.serviceTwo = function(){ };
Foo.prototype.serviceThree = function(){ };

module.exports = Foo;

Así las cosas, el usuario que importa este módulo recibe un constructor de Foo que puede usar para crear tantas instancias de este tipo como considere apropiado.

//bar.js
var Foo = require('./Foo');
var foo = new Foo('Obi-wan');
foo.serviceOne();
foo.serviceTwo();
foo.serviceThree();

Técnica 4 – Sustituir el Objeto exports Predeterminado por una Función

Es fácil imaginar ahora que, si podemos exponer un constructor, entonces también podemos usar una función común y corriente como el objeto que se expone a través de module.exports. El siguiente ejemplo expone una función que nos permit obtener acceso a un objeto privado del módulo, en este caso un objeto de servicio.

//foo.js
var serviceA = {};
serviceA.serviceOne = function(){ };
serviceA.serviceTwo = function(){ };
serviceA.serviceThree = function(){ };

var serviceB = {};
serviceB.serviceOne = function(){ };
serviceB.serviceTwo = function(){ };
serviceB.serviceThree = function(){ };

module.exports = function(name){
   switch(name){
      case 'A': return serviceA;
      case 'B': return serviceB;
      default: throw new Error('Unknown service name: ' + name);
   }
};

Y ahora el usuario del módulo, al importarlo, recibirá una referencia a nuestra función anónima, y al invocarla pues termina obteniendo acceso al objeto encapsulado de servicio. Por ejemplo:

//bar.js
var foo = require('./foo');
var obj = foo('A');
obj.serviceOne();
obj.serviceTwo();
obj.serviceThree();

Algunos programadores acostumbran invocar la función de inmediato en vez de almacenarla en una referencia e invocarla después. Por ejemplo:

//bar.js
var foo = require('./foo')('Obi-wan');
foo.serviceOne();
foo.serviceTwo();
foo.serviceThree();

Así que como podemos ver, todo lo que exponemos en module.exports es lo que se hace disponible al usuario que importa el módulo directamente y como vimos podemos exponer toda clase de objetos, funciones y constructores usando diferentes técnicas.

Sobre los Módulos y el Uso de Estado Global

Un aspecto significativo de los módulos en node.js es que todo lo que declaramos dentro del módulo es privado a dicho módulo. Esto marca una diferencia significativa de JavaScript en node en comparación con JavaScript en el browser en donde existe un objeto global (p.ej. window) que es el receptáculo final de todo lo que declaramos dentro de un archivo de JavaScript (a menos que lo coloquemos dentro de un alcance de función). En node existe también un objeto global pero nada se coloca en este objeto de manera automática como en el caso de JavaScript en el browser.

Sin embargo, es raro que sea necesario colocar algo en el objeto global en node.js dado que los módulos de Node.js se evalúan una sola vez y después esto se retorna el mismo objeto que module.exports exponga. Por eso es mejor utilizar un módulo para compartir estado global que colocar datos en el objeto global. Por ejemplo, el siguiente código expone un objeto con la configuración de una conexión a una base de datos de Mongo.

//config.js

dbConfig = {
  url:'mongodb://foo',
  user: 'anakin',
  password: '*******'
}

module.exports = dbConfig;

Ahora cada vez que necesitemos tener acceso a nuestra configuración de la base de datos podemos importar el módulo config.js. El código del módulo se evaluará sola la primera vez, así que podemos tener la garantía de que todos los demás módulos que usen nuestro módulo de configuración obtendrán una referencia al mismo objeto que se publico en module.exports la primera vez que este módulo sea requerido.

//foo.js
var dbConfig1 = require('./config');
var dbConfig2 = require('./config');
var assert = require('assert');
assert(dbConfig1==dbConfi2);

10 Apps de Apple para Programadores y Alfa Geeks

 

Lisping

Si te gusta la programación funcional y particularmente los lenguajes de programación de la familia Lisp, entonces te va a encantar Lisping. Esta aplicación te permite crear programas para los lenguajes de programación Scheme y Clojure y está disponible tanto para iPhone como para iPad. A fin de facilitar la edición de código entre paréntesis (como es común en esta familia de lenguajes) este editor provee un mecanismo intuitivo de edición que permite expandir  o colapsar un context dado o editar el contenido de un contexto por separado. Ahora sí que puedes probar tus ejemplos de Estructura e Interpretación de Programas de Computadora mientras miras por quinta vez la edición extendida de El Señor de los Anillos.

 

 

Raskell

Entonces ¿no eres aficionado de los lenguajes funcionales dinámicamente tipificados? Bueno, si prefieres la seguridad de la tipificación estática entonces seguramente apreciarás un editor como Raskell. Puedes editar y ejecutar tu código para el lenguaje de programación Haskell. Al igual que la aplicación anterior está disponible para iPhone y iPad. Ahora sí que puedes divertirte probando esos ejemplos de código de Aprenda Haskell Por el Bien de Todos mientras lees confortablemente desde tu cama tratando de ver si esta vez, de una vez por todas, terminas de entender que carajos es un monad.

 

Pythonista

Si lo tuyo son los lenguajes de programación multiparadigma, y particularmente si eres aficionado del lenguaje de programación Python entonces Pythonista es la aplicación que has estado buscando. Como es de esperarse, esta aplicación permite editar y ejecutar programas escritos con el lenguaje de programación Python, pero no solo eso, sino que ademas provee soporte para multi-touch, animación y sonido. Así que tus programas le pueden sacar el mejor provecho a tu dispositivo móvil. Al igual que las anteriores, está disponible tanto para tu iPhone como para iPad. Deja tu espíritu Pythonico correr libremente mientras esperas que terminen esos cinco minutos de comerciales durante la transmisión del último episodio de The Big Bang Theory.

 

Textastic

La verdad es que no todos nuestros lenguajes de programación favoritos tienen un editor especializado. Para aquellos de nosotros que somos aficionados a otros lenguajes de programación tenemos este gran editor llamado Textastic el cual provee resaltado de sintaxis para más 80 diferentes lenguajes de programación y con algunas características de edición realmente sobresalientes, particularmente que al ser usado desde un dispositivo móvil como el iPhone o el iPad el teclado se extiende con un conjunto de teclas comúnmente usadas por programadores. Con esta aplicación podrás editar tu código open source mientras tu novia te cuenta todo sobre su día durante la cena en ese lujoso restaurante.

Penultimate

¿Qué hay del diseño de software? No he tenido la suerte de encontrarme con una aplicación decente para hacer diagramas UML o diagramas de flujo. Sin embargo, si tienes un stylus, puedes crear diagramas y diseño a mano utilizando Penultimate. De hecho yo mismo he definido una buena colección de diagramas de flujo y diagramas UML de esta manera. Es una herramienta genial para discutir ideas de diseño, algoritmos o simplemente para registrar tus ideas y mantenerlas apropiadamente ordenadas.

 

Dash (Docs & Snippets)

¿Te has casando de buscar los JavaDocs para cierta clase? ¿Olvidaste los métodos de uno de los servicios de Angular? ¿Cuáles eran los parámetros de ese comando de git? Caray que cada vez que quiero leer los JavaDocs para una clase particular tengo que comenzar por Google para encontrar el enlace. Bueno, con Dash esos días se terminaron. Es como el Google de los programadores. Ofrece acceso fuera de línea a la documentación de decenas de APIs. Simplemente escribe el término que deseas y Dash encontrará la respuesta en un abrir y cerrar de ojos.

 

Code Runner

¿Te sucede que a veces quisieras tan solo correr un pequeño ejemplo de código Java o C# y te vez en la necesidad de crear todo un proyecto en tu IDE tan solo para probar una idea? Bueno, con Code Runner puedes evitar todas estas complicaciones. Esta herramienta provee un mecanismo para correr, de forma instantánea pequeños ejemplos de código en cualquier lenguaje de programación. La ejecución de ese programa está a un solo click de distancia. La herramienta viene con un conjunto de lenguajes de programación predefinidos, pero se puede extender la configuración con muchos más, a gusto del usuario.

Instapaper

Es difícil mantenerse al día con las últimas noticias en tecnología. Cada semana encuentro decenas de artículos interesantes que me gustaría leer y a menudo me veo ante la necesidad de recorder algunos que había leído en el pasado. A fin de conservar todas esa información útil a mano para mí no hay herramienta como Instapaper. Guardo los artículo que me interesan y los puedo leer mas tarde, fuera de línea, y cuando tengo más tiempo. Así que esta herramienta es perfecta para esos días en que tu novia te dice que tiene dolor de cabeza. Te ayudará a concentrarte en algo más y créeme serás el geek más informado de tu comunidad.

Evernote

Finalmente tengo una herramienta para mantener todas mis ideas organizadas y disponibles en todo momento. He estado en decenas de proyectos y siempre pierdo información valiosa con el paso del tiempo. Apenas si recuerdo detalles de requerimientos del pasado, or la dirección IP de cierto servidor de pruebas, o la ubicación de todos los repositorios en los que he trabajado, o el FQDN de aquel servidor de desarrollo al que tengo que ingresar vía SSH. Además es importante registrar todas esas ideas geniales para proyectos, investigaciones e incluso artículos por escribir en tu blog. Todo esto se facilita ahora con Evernote. Y si se lo combina con Evernote Web Clipper sirve un propósito similar que Instapaper, mencionado arriba.

SSH Term Pro

Estás viendo la versión remasterizada de Star Wars cuando sientes curiosidad de saber si ese script que dejaste corriendo el viernes en la noche antes de salir de la oficina terminó exitosamente. Bueno, ya no tienes que levantarte de tu asiendo, con SSH Term Pro puede acceder a ese servidor via SSH y volverte loco en la terminal. A tu jefe le va a encartar que descargues esta herramienta.

Bueno, eso es todo lo que tengo de momento. Si alguno sabe de otras aplicaciones de este tipo no dude en dejar un comentario y la incluiremos en esta grandiosa lista de aplicaciones para alfa geeks.

Covarianza y Contravarianza en Java

He descubierto que para comprender la covarianza y la contravarianza me ayuda primero examinar algunos ejemplos con arreglos de Java y luego comparar el comportamiento con clases genéricas de colecciones. Es importante destacar, sin embargo, que las mismas arreglas presentadas acá aplican a cualquier tipo genérico en Java, y no únicamente a las colecciones.

Arreglos Covariantes

Se dice que los arreglos son covariantes en Java, lo que básicamente significa que, dadas las reglas de subtipos en Java, un arreglo de tipo T[] puede contener elementos de tipo T o de cualquier subtipo de T. Por ejemplo:

Number[] numbers = new Number[3];
numbers[0] = new Integer(10);
numbers[1] = new Double(3.14);
numbers[2] = new Byte(0);

Pero no solo eso, la reglas de subtipos establece que un arreglo de tipo S[] es un subtipo de un arreglo de tipo T[] si S es un subtipo de T. Por lo tanto algo como esto también es válido:

Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;

Es decir, de acuerdo con las reglas de subtipos en Java, un arreglo de Integer[] es un subtipo de un arreglo de Number[] porque Integer es un subtipo de Number.

Pero estas reglas de subtipos nos pueden llevar a preguntas interesantes, por ejemplo, ¿qué pasaría en este caso?

myNumber[0] = 3.14; //intento de contaminación de la pila

Esta última línea compila perfectamente, pero si ejecutamos este código obtendremos un ArrayStoreException porque estamos intentando poner un valor tipo Double en un arreglo de Integer. El hecho de que estemos accediendo al arreglo a través de una referencia de tipo Number es irrelevante aquí. Lo que realmente importa es que el arreglo es un arreglo de enteros.

Esto quiere decir que podemos engañar al compilador, pero no podemos engañar al sistema de tipos en tiempo de ejecución. Decimos entonces que los arreglos son tipos materializables (reifiable type). En otras palabras, durante la ejecución del programa, Java sabe cual es la verdadera naturaleza del arreglo independientemente del tipo de la referencia que usemos para acceder a él.

Podemos ver claramente que una cosa es el tipo intrínseco de un objeto y otro cosa diferente la referencia que usamos para acceder a él. Un mismo objeto podría ser accedido desde referencias de múltiples tipos compatibles, pero eso no altera el tipo intrínseco del objecto en sí mismo.

Problema con los Tipos Genéricos de Java

Ahora bien, el problema con los tipos genéricos de Java es que la información de los parámetros genéricos es descartada por el compilador tras completar su trabajo. Eso quiere decir que la información sobre el tipo de un parámetro genérico no está disponible en tiempo de ejecución. Este proceso que lleva a cabo el compilador de descartar la información de los tipos genéricos se conoce como borrado de tipos (type erasure).

Existen buenas razones para haber implementado los tipos genéricos de esta forma en Java, pero eso es una larga historia que podemos considerar en otro artículo aparte. De momento baste decir que esto tiene que ver con razones de compatibilidad con el código preexistente de versiones anteriores de Java.

El punto realmente importante aquí es que ya que no hay información de los tipos de los parámetros genéricos en tiempo de ejecución entonces no existe una manera de asegurar que no se está incurriendo en una contaminación de la pila (heap pollution). Es decir, es posible que una variable de un tipo paramétrico se refiera a un objeto que no es de ese tipo paramétrico.

Consideremos el siguiente código inseguro:

List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts; //error de compilación
myNums.add(3.14); //contaminación de la pila

Si el compilador de Java no evita que hagamos esto, el sistema de tipos en tiempo de ejecución tampoco podría detenernos porque no existe forma, en tiempo de ejecución, de saber que la lista arriba mostrada es una lista exclusivamente de números enteros. Esto significa que en tiempo de ejecución Java nos permitiría colocar cualquier tipo de objeto en esta lista, aunque de acuerdo con el código en tiempo de compilación, la lista solo debería aceptar objetos de tipo Integer. Es por esta razón que el compilador rechaza la línea número 4, porque es insegura, y si esto se permite romperá los supuestos del sistema de tipos. Claramente una lista de enteros no es lo mismo que una lista de números flotantes.

Basándose en este principio los diseñadores del sistema de tipos de Java se aseguraron que el compilador no pueda ser engañado. Si el compilador no puede ser engañado (como lo hicimos en el caso de los arreglos) entonces podemos esperar que en tiempo de ejecución los tipos serán consistentes con lo esperado y no habrá contaminaciones indeseadas de la pila.

Decimos entonces que los tipos genéricos son no materializables (non-reifiable), ya que no tienen existencia material en tiempo de ejecución y por ende no podemos determinar su verdadera naturaleza, ni los podemos validar.

Evidentemente esta propiedad de los tipos genéricos tendría un impacto negativo en el polimorfismo. Consideremos el siguiente ejemplo:

static long sum(Number[] numbers) {
   long summation = 0;
   for(Number number : numbers) {
      summation += number.longValue();
   }
   return summation;
}

Este método podríamos usarlo de alguna de las siguientes formas:

Integer[] myInts = {1,2,3,4,5};
Long[] myLongs = {1L, 2L, 3L, 4L, 5L};
Double[] myDoubles = {1.0, 2.0, 3.0, 4.0, 5.0};

System.out.println(sum(myInts));
System.out.println(sum(myLongs));
System.out.println(sum(myDoubles));

Sin embargo, sin intentamos implementar la misma funcionalidad, usando colecciones genéricas de Java, no tendremos éxito al hacerlo de la siguiente manera:

static long sum(List<Number> numbers) {
   long summation = 0;
   for(Number number : numbers) {
      summation += number.longValue();
   }
   return summation;
}

Aunque el código arriba mostrado compila, obtendremos errores de tiempo de compilación al intentar utilizarlo de la siguiente manera:

List<Integer> myInts = asList(1,2,3,4,5);
List<Long> myLongs = asList(1L, 2L, 3L, 4L, 5L);
List<Double> myDoubles = asList(1.0, 2.0, 3.0, 4.0, 5.0);

System.out.println(sum(myInts)); //error de compilación
System.out.println(sum(myLongs)); //error de compilación
System.out.println(sum(myDoubles)); //error de compilación

Básicamente el problema es que ahora no podemos considerar que una lista de Integer es un subtipo de una lista de Number, como ya habíamos visto anteriormente. Tal cosa se consideraría inseguro para el sistema de tipos y el compilador lo rechaza inmediatamente.

Claramente esto está afectando el poder del polimorfismo y necesitamos corregirlo. La solución consiste en utilizar dos poderosas características de los tipos genéricos de Java conocidas como covarianza y contravarianza.

Covarianza

Para este caso, en vez de usar un tipo T como el argumento para un parámetro genérico mas bien usamos un comodín (wildcard) declarado como ? extends T, donde T es un tipo base conocido. Utilizando esta técnica de covarianza podemos leer datos de un tipo paramétrico de una estructura de datos, pero no podemos escribir datos paramétricos de vuelta en la estructura porque sería inseguro, como veremos pronto.

Los siguientes ejemplos ilustran este concepto:

List<? extends Number> myNums = new ArrayList<Integer>();
List<? extends Number> myNums = new ArrayList<Float>();
List<? extends Number> myNums = new ArrayList<Double>();

Y podemos leer datos de nuestra estructura genérica myNums haciendo algo como:

Number n = myNums.get(0);

Esto es posible debido a que podemos estar seguros de que, cualquiera que sea la verdadera naturaleza de los objetos que la estructura genérica contiene, todo elemento genérico de la estructura puede ser, de forma segura, considerado un Number.

No obstante, no se nos permite hacer algo como lo siguiente:

myNums.add(45L); //error de compilación

Esto no se permite porque el compilador no pude determinar cual es el verdadero tipo del argumento genérico. Dada la declaración podría ser cualquier cosa que extiende Number. En otras palabras la verdadera naturaleza de la estructura podría ser Integer, Float o Double, etc., pero el compilador no puede estar seguro y por lo tanto rechaza a priori cualquier intento de modificar un valor genérico en la estructura. Así pues en una estructura covariante podemos leer, pero no escribir.

Contravarianza

Como se podría esperar la contravarianza es el concepto diametralmente opuesto; mediante este podemos escribir datos en una estructura genérica, pero no podemos leerlos de vuelta de forma segura. Para este caso usamos un comodín de la forma ? super T, donde T es nuestro tipo base.

Por ejemplo:

List<Object> myObjs = new List<Object();
myObjs.add("Luke");
myObjs.add("Obi-wan");
List<? super Number> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);

En este caso, la verdadera naturaleza de la estructura de datos es una lista de Object, y gracias a la contravarianza se nos permite colocar un objeto de tipo Number en esta esctructura, básicamente porque todo Number es un subtipo de Object; así todos los números son también objetos y por lo tanto esta modificación es válida.

No obstante, no podemos leer un dato de forma segura de la estructura de datos genérica y asumir que dicho dato será un número.

Number myNum = myNums.get(0); //error de compilación

Como podemos ver, si el compilador permitiera esto, en tiempo de ejecución obtendríamos una exception de tipo ClassCastException puesto que el objeto 0 de la lista es, en realidad, una cadena de caracteres.

El Principio Get/Put

En resumen, usamos covarianza cuando sólo nos interesa leer datos de la estructura genérica y usamos contravarianza cuando solo nos interesa escribir datos en la estructura genérica. Y usamos invarianza cuando deseamos hacer ambas cosas.

El mejor ejemplo que he encontrado para ilustrar esto es la siguiente pieza de código que copia el contenido de una lista genérica a otra. En este caso sólo leemos de la estructura fuente, y sólo escribimos en al estructura destino.

public static void copy(List<? extends Number> source,
                        List<? super Number> destiny) {
   for(Number number : source) {
      destiny.add(number);
   }
}

Gracias al poder de la covarianza y la contravarianza podemos hacer que esto funcione:

List<Integer> myInts = asList(1,2,3,4);
List<Double> myDoubles = asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();
copy(myInts, myObjs);
copy(myDoubles, myObjs);

Lectura Adicional

La mayoría de mis reflexiones sobre este tópico se derivan de mi lectura de un excelente libro:

Ensayo sobre la Importancia del Encapsulamiento

El encapsulamiento consiste en más que simplemente definir métodos de acceso y de mutación para una clase. Es un concepto mucho más amplio de programación (no exclusivamente vinculando con la orientación a objectos) que consiste en minimizar la interdependencia entre los módulos y típicamente se implementa mediante el ocultamiento de información. Para comprender el concepto de encapsulamiento es de primordial importancia darse cuenta de que tiene dos objetivos fundamentales: ocultar la complejidad y ocultar el origen de los cambios.

Sobre Ocultar la Complejidad

El encapsulamiento está inherentemente relacionado con los conceptos de modularidad y abstracción y es por eso que, en opinión de este escritor, es necesario primero comprender estos dos conceptos.

Consideremos, como ejemplo, el nivel de abstracción en el concepto de un automóvil. Un automóvil es complejo en su funcionamiento interno; posee varios subsistemas, como el sistema de transmisión, el sistema de frenos, el sistema de abastecimiento de combustible, etcétera.

No obstante hemos llegado a simplificar este concepto dentro de una simple abstracción,  e interactuamos con todos los automóviles en el mundo a través de lo que denominaremos su interfaz pública: por ejemplo, sabemos que todos los automóviles tienen un rueda de volante a través de la cual controlamos la dirección del movimiento del vehículo, tenemos varios pedales, uno de los cuales sirve para acelerar y controlar la velocidad y otro con el que controlamos el frenado, y tenemos una palanca de cambios con la que controlamos si deseamos ir hacia adelante o hacia a atrás. Todas estas características constituyen la interfaz pública de la abstracción de un automóvil. Una mañana podemos conducir un sedán y por la tarde conducir un automóvil deportivo como si fueran exactamente la misma cosa. Incluso si son de diferentes fabricantes.

demaged carSin embargo, pocos de nosotros conocemos los detalles del funcionamiento de todos los sistemas bajo el capó del vehículo. Esta simple analogía demuestra que los seres humanos lidiamos con la complejidad de los objetos del mundo real mediante definir abstracciones con interfaces públicas que usamos para interactuar con ellos y ocultar todos los detalles innecesarios bajo el capó de estas abstracciones. Y es importante enfatizar la palabra “innecesarios” en esta última oración, porque la belleza de las abstracción es no tener que comprender todos los detalles para ser capaz de interactuar con un objeto internamente complejo, sino que basta con entender un concepto abstracto general, una idea simple de cómo funciona y cómo utilizarlo para realizar una tarea.  Es por ese motivo que la mayoría de nosotros no sabemos, o no nos interesa saber a detalle, cómo un automóvil acelera o frena o cambia de velocidad, pero el desconocimiento de estos detalles no nos impide conducir uno.

De una forma similar el software a menudo se construye con decenas de otros componentes más pequeños y especializados. Estas piezas se agrupan para formar componentes cada vez más complejos y a menudo no tenemos necesidad de comprender el funcionamiento interno de todas las piezas para interactuar con ellas. Basta con comprender cuál es el estímulo o entrada que acepta un objeto y la respuesta esperada del mismo. Es decir, cada componente se convierte en una caja negra para su usuario.

En su libro Code Complete, Steve McConnell usa una analogía de un iceberg: solo una pequeña porción de un iceberg es visible en la superficie del océano, la mayor parte de su verdadero tamaño esta oculto bajo el agua. De manera similar, en nuestros diseños de software las partes visibles de nuestros clases y módulos son su interfaz pública, y ésta está expuesta al mundo exterior, el resto debería estar oculta a los ojos del usuario. En palabras del mismo McConell

“La interfaz de una clase debería revelar tan poco como sea posible sobre su funcionamiento interno”.

Claramente, si nos basamos en nuestra analogía del automóvil podemos ver que el encapsulamiento es algo bueno puesto que oculta detalles de complejidad innecesaria para los usuarios. Hace que los automóviles sean simples de utilizar y de entender.

Sobre Ocultar el Origen de los Cambios

Ahora bien,  continuando con la analogía, pensemos en aquellos días en la que los automóviles no tenían un sistema hidráulico de dirección. Un día los fabricantes de autos finalmente lo inventaron y decidieron que, a partir de ese día, los vehículos debería tenerlo. Aun así esto no cambio en nada la manera como los conductores debían interactuar con sus vehículos. A lo sumo los conductores llegaron a disfrutar una mejor experiencia de manejo. El punto es que un cambio como este fue posible porque la implementación interna del automóvil estaba encapsulada, es decir, oculta a los ojos de los usuarios. En otras palabras, se podían realizar cambios de forma segura sin afectar la interfaz pública de esta abstracción.

De forma similar, si alcanzamos niveles apropiados de encapsulamiento en nuestros diseños de software podemos fomentar de forma segura el cambio y la evolución de nuestas APIs sin afectar a los usuarios de las mismas. Al hacer esto minimizamos el impacto de los cambios y fomentamos la independencia de los módulos. Así las cosas, el encapsulamiento es un medio para conseguir implementar otros importante atributo de un buen diseño de software: el acoplamiento débil.

En su libro Effective Java, Joshua Bloch resalta el poder del ocultamiento de información y el acoplamiento débil cuando dice:

“El ocultamiento de la información es importante por muchas razones, la mayoría de las cuales se derivan del hecho que mediante ésta se logra el desacoplamiento de los módulos que comprometen el sistema, permitiendo que éstos puedan ser desarrollados, probados, optimizados, utilizados, comprendidos y modificados de manera independiente. Esto acelera el desarrollo de sistemas porque los módulos pueden ser desarrollados de forma paralela. Alivia el costo del mantenimiento porque los módulos pueden ser comprendidos más fácilmente y depurados sin el temor de dañar otros módulos […] facilita el afinamiento del desempeño [ya que] estos módulos pueden ser optimizados sin afectar la correctitud de otros módulos [e] incrementa la reutilización del software porque los módulos que no están fuertemente acoplados suelen resultar útiles en otros contextos además de aquellos para los cuales fueron desarrollados”

Así que una vez más podemos ver que el encapsulamiento es un atributo muy deseable que facilita la introducción de cambios y que fomentan la evolución del software. Siempre que respetemos las interfaz pública de nuestras abstracciones tenemos la libertad de cambiar lo que queramos de su funcionamiento encapsulado.

Cuando se Rompe la Interfaz Pública

Entonces ¿qué sucede cuando no alcanzamos los niveles apropiados de encapsulamiento en nuestros diseños?

Bueno, imaginemos que un día los fabricantes de autos deciden poner la tapilla del combustible debajo del automóvil en vez de en uno de sus costados. Digamos que compramos uno de estos nuevos automóviles modificados y cuando se nos termina el combustible vamos a la gasolinera más cercana y entonces nos damos cuenta de que no sabemos en donde está la tapilla del combustible. Tras buscar un rato finalmente descubrimos que está debajo del auto, pero la manguera del combustible no llega hasta ella y la boquilla de la manguera no se corresponde con la nueva interfaz de la tapilla. En ese momento el mundo entero se cae a pedazos, todo porque hemos desobedecido el contrato de la interfaz pública de nuestra abstracción. Un cambio como este costaría millones. Necesitaríamos cambiar todas las bombas de combustible del mundo, sin mencionar talleres mecánicos y repuestos.

Cuando no alcanzamos niveles apropiados de encapsulamiento terminamos rompiendo la interfaz pública de nuestras abstracciones y entonces hay que pagar un precio: el costo del cambio. Asimismo, esta última parte de nuestra analogía revela que el no definir abstracciones con niveles apropiados de encapsulamiento terminará causando dificultades cuando los cambios ocurran.

Es digno de mencionar que los cambios siempre ocurrirán, lo importante es poder minimizar el impacto de esos cambios. Cuando los cambios ocurren en la interfaz pública de una abstracción el cambio es sumamente caro, pues afecta a todos sus usuarios.

Entonces el objetivo del encapsulamiento es reducir la complejidad de las abstracciones por medio de proveer un mecanismo para ocultar detalles de implementación. También ayuda a minimizar la interdependencia y facilitar el cambio. Maximizamos el encapsulamiento por medio de minimizar la exposición de los detalles de implementación.

Sin embargo, el encapsulamiento no servirá de nada si no definimos abstracciones apropiadas. Puesto de manera sencilla, no hay forma de cambiar la interfaz pública de una abstracción sin afectar y probablemente interrumpir a los usuarios. Así, el diseño de abstracciones apropiadas es de primordial importancia para facilitar la evolución del software y el encapsulamiento es solo una de muchas herramientas que contribuyen a la creación de dichas abstracciones. No obstante, ningún nivel de encapsulamiento va a hacer que una mala abstracción funcione bien.

Encapsulamiento con Java

Una de esas cosas que siempre queremos tener encpsuladas es el estado de una clase. El estado de una clase solo debería ser accesible a través de su interfaz pública.

En un lenguaje orientado a objetos, como Java, conseguimos esto por medio de los modificadores de accesibilidad (public, protected, private, etc). Con estos niveles de accesibilidad controlamos el nivel de encapsulamiento. Entre menos restrictivo el nivel, más caro será el cambio cuando este ocurra y más acoplada está la clase con otras clases dependientes (clases de usuario, subclases, etc).

En los lenguajes que soportan la herencia existen dos interfaces públicas: la interfaz pública compartida con los usuarios de la clase, y la interfaz protegida compartida con sus subclases, es decir, las subclases son un tipo especial de usuario.  De ahí que cuando se introduce la herencia en el diseño de software entonces se vuelve de mayor relevancia que diseñemos niveles apropiados de encapsulamiento para las interfaces públicas y protegidas a fin de facilitar los cambios futuros y la evolución de las APIs.

¿Por qué Getters y Setters?

Muchas gente se pregunta por qué necesitamos métodos de acceso y de mutación en Java (popularmente conocidos como getters y setters). Muchos dicen, ¿por qué no podemos simplemente acceder a los datos directamente?

El propósito del encapsulamiento aquí no es ocultar los datos mismos, sino los detalles de implementación de éstos y la manera en cómo son manipulados para presentarlos al usuario. Así que el propósito de los getters y setters es ofrecer una interfaz pública a través de la cual podemos obtener acceso a la información encapsulada en un objeto. Más tarde podemos alterar la representación interna de los datos sin comprometer la interfaz pública de la clase. Por el contrario, si se exponen los datos directamente comprometemos el encapsulamiento y, por lo tanto, la capacidad de cambiar la forma de manipularlos en el futuro sin afectar a sus usuarios. Estaríamos creando una dependencia con los datos directamente y no con la interfaz pública de la clase. Es un coctél perfecto de problemas y nos lo tendremos que beber cuando el cambio finalmente no encuentre.

Hay varias razones de peso por las cuales queremos encapsular acceso a nuestros campos. El mejor compendio de razones que he encontrado fue formulado, una vez más, por Joshua Bloch en Effective Java.

  • Se pueden limitar los valores almacenados en un campo(p.ej. género debe ser F o M).
  • Se pueden tomar acciones cuando el campo es modificado (desencadenar un evento, validar, etc).
  • Se puede proveer seguridad de hilos por medio de sincronizar los métodos.
  • Se puede cambiar la representación interna de los datos (p.ej. campos calculados, tipos de datos diferentes).
  • Se puede hacer que un campo sea de solo lectura.

Sin embargo, una vez más, queremos apuntar a que el encapsulamiento es más que solo ocultar campos. En Java, como ejemplo, podemos ocultar clases completas, y en el futuro inclusive módulos completos.

Lectura Adicionales

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.