Colecciones de Orden Superior en Java

De Grupo GNU/Linux de la Universidad del Cauca
Revisión a fecha de 05:17 4 dic 2013; LibardoPantoja (Discusión | contribuciones)

(dif) ← Revisión anterior | Revisión actual (dif) | Revisión siguiente → (dif)
Saltar a: navegación, buscar

Autor: Óscar Andrés López
Fecha: Abril 25 de 2.005
Nivel: Avanzado
Categoría: Lenguajes de Programación
Código Fuente: higher-order-collections.tar.bz2
Licencia: Apache 2.0
Versión en PDF: Colecciones de Orden Superior en Java.pdf


Contenido

Abstracto

El vasto mundo de los lenguajes de programación ofrece un sinnúmero de posibilidades. Muchas personas se conforman con aprender un lenguaje, el que les enseñaron, y olvidan que hay miles de alternativas a la hora de elegir la mejor herramienta para solucionar un problema. Cada lenguaje proporciona un conjunto de características; si bien es cierto que muchas de éstas son comunes a varios lenguajes, otras son particulares a ciertas familias de lenguajes, o incluso son únicas.

Algunas características son tan útiles y notorias, que se desearía fuesen comunes a todos los lenguajes. Personalmente, encuentro muy práctico poder tratar las funciones como objetos de primer nivel dentro de un lenguaje: es decir, una función se debe poder asignar a una variable, pasar como parámetro a otra función, retornar como resultado de evaluar otra función y almacenar dentro de una estructura de datos. Este tipo de flexibilidad a la hora de manipular funciones es típico de los lenguajes funcionales (Haskell, Caml, Lisp, etc.), aunque también se encuentra en lenguajes orientados a objetos (Ruby, Self, JavaScript, Smalltalk, etc.). Infortunadamente Java, uno de los lenguajes orientados a objetos más populares de hoy en día, no trata las funciones como objetos de primer nivel. Sin embargo, es posible implementar esta característica usando uno de los elementos más desaprovechados del lenguaje: las clases internas anónimas.

En este artículo voy a mostrar cómo se pueden usar funciones de orden superior para extender las colecciones de Java, de forma tal que ofrezcan la misma funcionalidad de los métodos enumeradores en las colecciones de Smalltalk. Para ello, hago el lanzamiento "oficial" de HigherOrderCollections, un proyecto de código abierto de mi autoría que implementa dicha funcionalidad. Los impacientes pueden bajarlo aquí.

Definiciones

Antes de entrar en materia, debo explicar algunos términos que usaré más adelante. Nótese que esta es una simple introducción a cada término, cada uno de ellos justificaría un libro al respecto. Ofrezco bibliografía para quien desee profundizar en el tema.

Funciones de Orden Superior

Las funciones son los ladrillos con los que construimos programas. Son tan fundamentales, que es posible definir un modelo formal de computación exclusivamente en términos de funciones, como lo demuestra el cálculo lambda de Alonzo Church. También son la base y la razón de ser de todo un paradigma de programación: la programación funcional.

Una función se denomina "de orden superior" [1] cuando recibe otra función como argumento o retorna una función como resultado. Este concepto se origina en las matemáticas y lo encontramos aplicado, por ejemplo, en las derivadas y las integrales.

Podemos definir funciones de orden superior en muchos lenguajes de programación, particular pero no exclusivamente, en los lenguajes funcionales.

Para ilustrar este concepto, miremos un ejemplo en Scheme, un lenguaje funcional.

Ejemplo 1:

<lisp> (map (lambda (elemento) (* elemento elemento)) '(1 2 3 4 5 6)) </lisp>

Aquí estoy usando la función map, que recibe como parámetros otra función y una lista, y retorna una nueva lista con el resultado de aplicar la función sobre cada elemento de la lista. En el ejemplo, elevo al cuadrado los elementos de la lista (1 2 3 4 5 6) y obtengo:

<lisp> (1 4 9 16 25 36) </lisp>

Claramente, map es una función de orden superior: uno de sus parámetros es otra función. Voy a regresar sobre este mismo ejemplo más adelante, demostrando cómo implementarlo en otros lenguajes.

Bloques

Se llama functor [2] o bloque a un objeto que encierra un conjunto de expresiones. En primera instancia, esto suena semejante al concepto de función, entendido en el sentido usual de un lenguaje de programación. Hay, sin embargo, algunos detalles que deben resaltarse.

Los bloques son anónimos en el momento en que se definen -es decir, no puedo referirme a ellos con un nombre-. Si se desea, después se les puede asignar un nombre. Esto difiere de los lenguajes procedurales como C, en donde la definición de una función y la asignación de un nombre a ésta ocurren de manera simultánea.

Los bloques encierran la unidad léxica de código en la que fueron definidos. Dicho de otra forma: en su interior "recuerdan" las variables, constantes, argumentos, campos, etc. presentes en el contexto en el que fueron creados, aún después de la destrucción de dicho contexto. En términos técnicos, esta propiedad se denomina una cerradura.

Un bloque puede pasarse de un lado a otro como cualquier tipo de datos y puede evaluarse dinámicamente en contextos distintos al que se usó para definirlo.

Debe hacerse una aclaración: un bloque no es una función de orden superior. Sin embargo, si una función recibe como parámetro un bloque, la función se considera de orden superior.

Miremos un ejemplo (un poco artificial) en Smalltalk.

Ejemplo 2:

| uno bloqueSuma |
uno := 1.
bloqueSuma := [ uno + uno ].
bloqueSuma value.

En Smalltalk se denomina bloque a todas las expresiones encerradas entre [], una simple suma en este caso. Notar que la expresión encerrada hace referencia a una variable que declaré en el mismo contexto de creación del bloque. Dado que quiero evaluarlo más adelante, lo asigno a la variable bloqueSuma, al hacerlo, le estoy dando un nombre. Para recuperar el valor del bloque, le envío el mensaje value y obtengo:

2

Métodos Enumeradores en Smalltalk

Para muchas personas -incluyéndome-, Smalltalk [3] es el lenguaje orientado a objetos por excelencia. Su simplicidad y elegancia, su poder expresivo, su avanzado protocolo de meta-objetos lo hacen el favorito de investigadores y científicos de computación a nivel mundial. Sus colecciones ejemplifican la madurez y refinamiento presente en todos los aspectos del diseño del lenguaje. Particularmente notorios, son el conjunto de métodos conocidos como enumeradores.

Los métodos enumeradores son una implementación de los patrones Iterator y Visitor [4]. En tan sólo una línea de código, permiten recorrer una colección de comienzo a fin -sin importar la estructura interna de ésta- y aplicar una función a cada uno de sus elementos, en donde la función y el mecanismo usado para aplicarla son independientes y están desacoplados de la colección en sí misma. ¿Cómo se logra esto? De una forma increíblemente simple. Basta con usar funciones de orden superior que reciben un bloque como parámetro. Veamos nuevamente el ejemplo 1, ahora desde el punto de vista de Smalltalk, un lenguaje orientado a objetos:

Ejemplo 3:

#(1 2 3 4 5 6) collect: [ :elemento | elemento * elemento ]

La línea de código anterior toma la colección #(1 2 3 4 5 6) y le envía el mensaje collect: (equivalente a la función map en Scheme) con un bloque como argumento. A su vez, el bloque recibe como argumento cada uno de los elementos de la colección, lo eleva al cuadrado y lo va añadiendo a una nueva colección. Finalmente, el método retorna la nueva colección con el resultado de aplicar el bloque sobre cada uno de los elementos de la colección original, es decir que retorna:

#(1 4 9 16 25 36)

Los métodos enumeradores incluyen -pero no están limitados a- los siguientes: #collect: , #detect: , #do: , #inject: , #reject: y #select: . Más adelante explicaré qué hace cada uno de ellos.

Extendiendo las Colecciones de Java

En este apartado voy a mencionar brevemente las características avanzadas del lenguaje Java que utilicé para escribir mi librería. Nuevamente, se trata de una simple introducción, con referencias bibliográficas para los más curiosos.

Tipos Genéricos

J2SE 1.5 es la versión más reciente de la plataforma de desarrollo de Sun e incluye varias mejoras al lenguaje Java [5]. La más importante es, sin duda alguna, la incorporación de un nuevo sistema de tipos de datos genéricos [6]; entre otras ventajas, permite especificar el tipo de datos que va a almacenar una colección:

Ejemplo 4: <java> Set<String> s = new HashSet<String>(); s.add("hola"); // inserta "hola" en el conjunto s.add(123); </java>

El ejemplo 4 muestra cómo crear un conjunto de cadenas de texto, si se intenta añadir algo distinto a una cadena se produce un error de compilación.

Si quisiera implementar mi librería usando Java 1.4 o anterior, tendría que pasar/retornar el tipo Object en todos los bloques y métodos enumeradores y olvidarme por completo de la seguridad de los tipos de datos, además, los errores de tipos sólo se detectarían en tiempo de ejecución, no en tiempo de compilación. Debido a esto, opté por utilizar J2SE 1.5 y usar extensivamente tipos genéricos en mi librería; esto introduce un nivel más de complejidad, pero garantiza la seguridad de tipos y el chequeo de errores de tipos en tiempo de compilación.

Bloques en Java

En Java podemos crear bloques de código usando clases internas anónimas. La idea no es nueva, ha sido explorada por varios autores y ha dado origen a algunas librerías de código abierto. En particular, me basé en un artículo [7] de Robert Di Falco para escribir mi implementación de bloques. La diferencia más notable respecto a su trabajo, es que uso tipos genéricos.

Cuando se define una clase interna anónima se encapsulan uno o más métodos en una clase sin nombre que extiende una clase abstracta o implementa una interfaz. Una clase interna anónima tienen acceso al contexto en el que fue definida, con algunas restricciones:


  • No puede declarar un constructor, pero si extiende una clase abstracta, en el momento de definirla se le pueden pasar parámetros al constructor de su superclase
  • Puede acceder libremente los campos de instancia o de clase de la clase que la contiene
  • Si está dentro de un método puede acceder las variables locales o los parámetros del método si y sólo si éstos son declarados como final


Notar que cualquier clase interna anónima puede ser usada como un bloque, sin embargo, definí una serie de interfaces genéricas que representan los casos más comunes y permiten evaluar el contenido del bloque de una forma segura respecto a tipos. Las interfaces que representan bloques se encuentran en el paquete util.blocks de mi librería, referirse al código fuente y a la documentación para mayores detalles. Se pueden resumir brevemente de la siguiente manera:


  • Bloques de Procedimientos: Retornan void al evaluarlos
    • Sin parámetros : ProcedureBlock
    • Con un parámetro (unario) : UnaryProcedureBlock
    • Con dos parámetros (binario) : BinaryProcedureBlock
  • Bloques de Predicados: Retornan un valor booleano al evaluarlos
    • Sin parámetros : PredicateBlock
    • Con un parámetro (unario) : UnaryPredicateBlock
    • Con dos parámetros (binario) : BinaryPredicateBlock
  • Bloques de Funciones: Retornan un valor de un tipo arbitrario al evaluarlos
    • Sin parámetros : FunctionBlock
    • Con un parámetro (unario) : UnaryFunctionBlock
    • Con dos parámetros (binario) : BinaryFunctionBlock


Todos los bloques se pueden asignar a una variable, pasar como parámetro a otro método, usar como valor de retorno de una función o almacenar en una estructura de datos; claramente, son elementos de primer nivel. Además, también son serializables siempre y cuando los tipos de datos que encierren también lo sean. Para evaluar un bloque, se le envía el mensaje value con cero, uno ó dos parámetros, ante el cual el bloque retornará void, un valor booleano o un valor de un tipo arbitrario, según corresponda al tipo de bloque.

Voy a implementar nuevamente el ejemplo 2 usando bloques en Java:

Ejemplo 5:

<java> final Integer uno; FunctionBlock<Integer> bloqueSuma;

uno = 1; bloqueSuma = new FunctionBlock<Integer>() {

 public Integer value() {
   return uno + uno; }};

bloqueSuma.value(); </java>

Si imprimiera el resultado de la última expresión obtendría:

2

Mi definición de bloques es muy sencilla, sólo plantea lo necesario para implementar métodos enumeradores sobre las colecciones de Java. Hay varias librerías que ofrecen una funcionalidad más extensa y compleja para manipular bloques, consultar [8], [9], [10], [11] para más información.

Métodos Enumeradores en Java

Cuando describí los métodos enumeradores de Smalltalk, mencioné que la clave para implementarlos reside en disponer de colecciones con funciones de orden superior que reciban un bloque como parámetro. En la sección anterior demostré cómo se pueden construir bloques, sólo queda faltando la segunda mitad del rompecabezas: ¿Cómo puedo definir funciones de orden superior sobre las colecciones de Java? Obviamente, volver a implementar las colecciones ¡no es una opción viable!; los patrones de diseño vienen a nuestro auxilio una vez más. Usando una combinación de Adapter y Factory Method [4], "envolví" las clases que implementan la interfaz Collection en mi interfaz HigherOrderCollection, escribí una implementación genérica de los métodos enumeradores en AbstractHigherOrderCollection y proporcioné un mecanismo estándar para crear nuevas instancias de las colecciones en HOCFactory. Referirse al código fuente y a la documentación del paquete util.higherOrderCollections para más detalles.

La pieza central de la implementación es la interfaz HigherOrderCollection, que representa el contrato que debe cumplir una colección de orden superior. Las operaciones definidas en ella están basadas en los métodos enumeradores de Smalltalk, lo que efectivamente permite contar con la funcionalidad de dichos métodos en Java. Esto es algo muy notable, quien haya trabajado con Smalltalk sabe lo útiles y prácticos que son sus enumeradores, que permiten realizar las operaciones más comunes sobre una colección sin tener que escribir una y otra vez el mismo código para recorrerla y realizar alguna acción sobre cada uno de sus elementos.

La siguiente sección explica cómo instanciar colecciones de orden superior y cómo usar los métodos enumeradores: es un pequeño tutorial de mi librería.

Librería HigherOrderCollections

Naturalmente, el primer paso es bajarse la librería de esta dirección. Al descomprimir el archivo higher-order-collections.tar.bz2 se va a encontrar la siguiente estructura:

doc/
src/
    test/
    util/
         blocks/
         higherOrderCollections/
LICENSE.txt
higher-order-collections.jar

El directorio doc/ contiene la documentación generada a partir del código, el directorio src/ incluye el código fuente de las pruebas y de la librería como tal; el archivo LICENSE.txt contiene una copia de la licencia Apache 2.0 [12], que cobija este trabajo y finalmente el archivo higher-order-collections.jar contiene la versión compilada del código, lista para ser utilizada.

Requerimientos e Instalación

Los usuarios de mi librería deben tener instalado J2SE 1.5.0 ó superior, e incluir en su CLASSPATH el archivo higher-order-collections.jar. Adicionalmente, el código debe importar los siguientes paquetes:

<java> import util.blocks.*; import util.higherOrderCollections.*; import static util.higherOrderCollections.HOCFactory.CollectionEnum.*; </java>

Pruebas

Todo proyecto de código abierto que se respete debe incluir pruebas unitarias. Las mías están bajo el directorio test/ y fueron hechas usando JUnit [13]. Los curiosos pueden encontrar muchos ejemplos de uso en HigherOrderCollectionTest, quien desee modificar mi librería debe asegurarse que las pruebas sigan funcionando después de las modificaciones, ya que ellas verifican exhaustivamente que se cumpla el contrato estipulado en la interfaz HigherOrderCollection.

¡Hola, Mundo!

Siguiendo una antigua tradición que data de 1973, cuando Brian Kernighan escribió un tutorial sobre el lenguaje B, vamos a imprimir "¡Hola, Mundo!" por pantalla usando mi librería. Este es un ejemplo completo, que demuestra todo lo que se debe hacer para utilizar colecciones de orden superior; se puede compilar y ejecutar directamente.

Ejemplo 6:

<java> import util.blocks.*; import util.higherOrderCollections.*; import static util.higherOrderCollections.HOCFactory.CollectionEnum.*;

public class HolaMundo {

   public static void main(String[] args) {
       // declaro una colección de orden superior
       HigherOrderCollection<String> coleccion;
       // instancio un Vector
       coleccion = HOCFactory.newInstance(Vector);
       // añado cadenas de texto
       coleccion.add("¡Hola");
       coleccion.add(", ");
       coleccion.add("Mundo!");
       /*
        * itero sobre los elementos de la colección y evalúo
        * sobre cada uno de ellos  el  bloque  que pasé como
        * parámetro. El bloque escribe por pantalla el valor
        * que recibe como argumento
        */
       coleccion.doBlock(new UnaryProcedureBlock<String>() {
           public void value(String elemento) {
               System.out.print(elemento); }});
   }

} </java>

¡Hola, Mundo!

Instanciación de Colecciones de Orden Superior

Hay dos mecanismos disponibles para instanciar una colección de orden superior. El más sencillo y frecuente, es utilizando la clase HOCFactory para obtener instancias de las colecciones presentes en J2SE 1.5, como se ilustra a continuación.

Ejemplo 7:

<java> HigherOrderCollection<Double> lista; lista = HOCFactory.newInstance(LinkedList); </java>

Detalles a tener en cuenta: La variable lista es declarada de tipo HigherOrderCollection, con un argumento de tipo <Double> indicando que se trata de una lista de elementos de tipo Double. A continuación instanciamos una LinkedList -una lista encadenada- como la colección subyacente a la colección de orden superior. En cualquier momento podemos recuperar la colección que se encuentra "envuelta" bajo la lista, como se mostrará en la siguiente sección (ver método getCollection).

HOCFactory proporciona envoltorios por defecto para las siguientes clases: ArrayList, ConcurrentLinkedQueue, CopyOnWriteArrayList, CopyOnWriteArraySet, HashSet, LinkedBlockingQueue, LinkedHashSet, LinkedList, PriorityBlockingQueue, PriorityQueue, Stack, TreeSet y Vector. En todas ellas, se utiliza el constructor por defecto -sin argumentos- para crear nuevas instancias de colecciones estándar que serán envueltas en una colección de orden superior.

Ahora bien, si no se desea usar el constructor por defecto de una colección o si se necesita envolver una nueva colección que no está disponible en HOCFactory, debo utilizar el segundo mecanismo para instanciar una colección de orden superior: definir una pequeña clase (podría ser local) que extienda AbstractHigherOrderCollection y proporcione un constructor y una implementación para newInstance, su único método abstracto. Supongamos que se necesita instanciar un HashSet<Number> de orden superior, con una capacidad inicial de 100 elementos y un factor de carga de 0.50. Procedemos de la siguiente manera.

Ejemplo 8:

<java> final int capacidadInicial = 100; final float factorCarga = 0.50f;

class HOHashSet<E> extends AbstractHigherOrderCollection<E> {

   private HOHashSet() {
       super(new HashSet<E>(capacidadInicial, factorCarga));
   }
   protected <E> HigherOrderCollection<E> newInstance() {
       return new HOHashSet<E>();
   }

}

HigherOrderCollection<Number> conjunto = new HOHashSet<Number>(); </java>

Uso de Métodos Enumeradores

Finalmente, estudiaremos la interfaz HigherOrderCollection, el núcleo de mi librería. Voy a dar una breve descripción de cada método, junto con un fragmento de código mostrando cómo utilizarlo y el resultado de invocarlo. Consultar la documentación para mayores detalles.

Todos los ejemplos utilizan la misma colección de Integers, definida a continuación:

<java> HigherOrderCollection<Integer> coleccion; coleccion = HOCFactory.newInstance(ArrayList); coleccion.add(1); coleccion.add(2); coleccion.add(3); coleccion.add(4); coleccion.add(5); coleccion.add(6); </java>


Collection<E> getCollection() : Retorna la colección subyacente que fue usada para crear esta colección de orden superior. Puede ser convertida de manera segura al tipo de la colección original.

Ejemplo 9:

<java> ArrayList<Integer> lista = (ArrayList<Integer>)coleccion.getCollection(); </java>

void doBlock(UnaryProcedureBlock<E> aBlock) : Evalúa el bloque sobre cada uno de los elementos de la colección.

Ejemplo 10:

<java> coleccion.doBlock(new UnaryProcedureBlock<Integer>() {

   public void value(Integer elemento) {
     System.out.print(elemento + " "); }});

</java>

1 2 3 4 5 6

int count(UnaryPredicateBlock<E> aBlock) : Retorna el número de elementos que al ser evaluados sobre el bloque hacen que éste retorne true.

Ejemplo 11:

<java> coleccion.count(new UnaryPredicateBlock<Integer>() {

   public boolean value(Integer elemento) {
       return elemento.intValue() % 2 == 0; }});

</java>

3

E detect(UnaryPredicateBlock<E> aBlock) : Retorna el primer elemento que al ser evaluado sobre el bloque hace que éste retorne true, o null si ninguno hace que el bloque retorne true.

Ejemplo 12:

<java> coleccion.detect(new UnaryPredicateBlock<Integer>() {

   public boolean value(Integer elemento) {
       return elemento == 4; }});

</java>

4

E remove(UnaryPredicateBlock<E> aBlock) : Remueve de la colección y retorna el primer elemento que al ser evaluado sobre el bloque hace que éste retorne true, o retorna null si ninguno hace que el bloque retorne true.

Ejemplo 13:

<java> coleccion.remove(new UnaryPredicateBlock<Integer>() {

   public boolean value(Integer elemento) {
       return elemento == 5; }});

</java>

5

HigherOrderCollection<E> removeAll(UnaryPredicateBlock<E> aBlock) : Remueve de la colección todos los elementos que al ser evaluados sobre el bloque hacen que éste retorne true. Retorna una nueva colección del mismo tipo que la colección original con los elementos que fueron removidos, o una colección vacía si no se removió ninguno.

Ejemplo 14:

<java> coleccion.removeAll(new UnaryPredicateBlock<Integer>() {

   public boolean value(Integer elemento) {
       return elemento.intValue() % 2 != 0; }});

</java>

1 3 5

R inject(R thisValue, BinaryFunctionBlock<R,E,R> binaryBlock) : Acumula y retorna el resultado de evaluar el bloque sobre cada uno de los elementos. El primer parámetro sirve como valor inicial del acumulador. Por ejemplo, para sumar todos los elementos de la colección, haría lo siguiente.

Ejemplo 15:

<java> coleccion.inject(0, new BinaryFunctionBlock<Integer,Integer,Integer>() {

   public Integer value(Integer subTotal, Integer elemento) {
       return subTotal + elemento; }});

</java>

21

HigherOrderCollection<R> collect(UnaryFunctionBlock<E,R> aBlock) : Evalúa el bloque sobre todos los elementos de la colección y va añadiendo cada valor retornado por el bloque a una nueva colección, que finalmente es retornada. La nueva colección tiene el mismo tipo que la colección original. Aquí demuestro cómo implementar los ejemplos 1 y 3 con mi librería.

Ejemplo 16:

<java> coleccion.collect(new UnaryFunctionBlock<Integer,Integer>() {

   public Integer value(Integer elemento) {
       return elemento * elemento; }});

</java>

1 4 9 16 25 36

HigherOrderCollection<E> select(UnaryPredicateBlock<E> aBlock) : Añade a una nueva colección (que tiene el mismo tipo que la colección original) los elementos que al ser evaluados sobre el bloque hacen que éste retorne true. Retorna la nueva colección.

Ejemplo 17:

<java> coleccion.select(new UnaryPredicateBlock<Integer>() {

   public boolean value(Integer elemento) {
       return elemento < 4; }});

</java>

1 2 3

HigherOrderCollection<E> reject(UnaryPredicateBlock<E> aBlock) : Añade a una nueva colección (que tiene el mismo tipo que la colección original) los elementos que al ser evaluados sobre el bloque hacen que éste retorne false. Retorna la nueva colección.

Ejemplo 18:

<java> coleccion.reject(new UnaryPredicateBlock<Integer>() {

   public boolean value(Integer elemento) {
       return elemento < 4; }});

</java>

4 5 6

Como nota final, recordar que los métodos enumeradores pueden ser encadenados uno detrás de otro, permitiendo escribir complejas expresiones que actúan como filtros sobre una colección de datos.

Ejemplo 19:

<java> coleccion.collect(new UnaryFunctionBlock<Integer, Integer>(){

 public Integer value(Integer elemento) {
   return elemento * elemento; }})
     .select(new UnaryPredicateBlock<Integer>() {
       public boolean value(Integer elemento) {
         return elemento.intValue() % 2 == 0; }})
           .detect(new UnaryPredicateBlock<Integer>() {
             public boolean value(Integer elemento) {
               return elemento > 20; }});

</java>

36

Trabajo Futuro

El trabajo de un desarrollador nunca termina… Los métodos enumeradores de Smalltalk abarcan todas las colecciones, incluyendo los diccionarios. Una posible extensión para mi trabajo, sería implementar métodos equivalentes a los de la interfaz HigherOrderCollection, pero definidos sobre las clases que implementan la interfaz Map. Otra extensión relevante podría ser agregar nuevos métodos enumeradores, siguiendo la misma filosofía de los que ya están implementados. Es una propuesta abierta, sería interesante que alguien más se le midiera al trabajo. ¡Por algo es código abierto!

Conclusiones

En este artículo cubrimos bastante material. Explicamos qué es una función de orden superior, qué es un bloque y cómo la combinación de ambos aplicada a una colección produce métodos de gran poder expresivo. También vimos la utilidad de los tipos genéricos y las clases internas anónimas en Java, que permiten implementar métodos enumeradores similares a los encontrados en Smalltalk. Por último, vimos cómo utilizar la librería HigherOrderCollections, que extiende las colecciones de Java y las enriquece con métodos enumeradores.

Espero que muchos de mis lectores utilicen HigherOrderCollections en sus proyectos, mi correo (olopez _en_ uniandino.com.co) siempre estará abierto a dudas, correcciones y sugerencias. Confío en que lo expuesto aquí haya sido de utilidad a alguien.

Unas palabras finales de agradecimiento… a Steve Vai, John Petrucci, Marty Friedman y muy especialmente a Uli Jon Roth, por interpretar la increíble música que acompañó este proyecto y sirvió para inspirar unas cuantas buenas ideas.

Bibliografía

[1] http://www-128.ibm.com/developerworks/library/l-highfunc.html?ca=dgr-lnxw07Functions

[2] http://c2.com/cgi/wiki?FunctorObject

[3] Goldberg, Adele; Robson, David. Smalltalk-80: The Language and Its Implementation ("El Libro Azul"). Addison-Wesley, 1985.

[4] E. Gamma, R. Helm, R. Johnson and J. Vlissides. Design Patterns: Elements of Reusable Object Oriented Software. Reading, Mass.: Addison-Wesley, 1995.

[5] http://java.sun.com/j2se/1.5.0/docs/relnotes/features.html

[6] http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf

[7] http://c2.com/cgi/wiki?BlocksInJava

[8] http://jakarta.apache.org/commons/sandbox/functor/

[9] http://jga.sourceforge.net/

[10] http://www.recursionsw.com/

[11] http://www.jezuk.co.uk/cgi-bin/view/mango

[12] http://www.apache.org/licenses/LICENSE-2.0

[13] http://www.junit.org