Вы находитесь на странице: 1из 13

COLECCIONES DE ORDEN SUPERIOR EN JAVA

Por: Óscar Andrés López P.

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:

(map (lambda (elemento) (* elemento elemento)) '(1 2 3 4 5 6))

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:

(1 4 9 16 25 36)

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 sí 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:

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:

Set<String> s = new HashSet<String>();


s.add(“hola”); // inserta “hola” en el conjunto
s.add(123); // causa un error de compilación

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


o Sin parámetros : ProcedureBlock
o Con un parámetro (unario) : UnaryProcedureBlock
o Con dos parámetros (binario) : BinaryProcedureBlock
• Bloques de Predicados: Retornan un valor booleano al evaluarlos
o Sin parámetros : PredicateBlock
o Con un parámetro (unario) : UnaryPredicateBlock
o Con dos parámetros (binario) : BinaryPredicateBlock
• Bloques de Funciones: Retornan un valor de un tipo arbitrario al evaluarlos
o Sin parámetros : FunctionBlock
o Con un parámetro (unario) : UnaryFunctionBlock
o 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:

final Integer uno;


FunctionBlock<Integer> bloqueSuma;

uno = 1;
bloqueSuma = new FunctionBlock<Integer>() {
public Integer value() {
return uno + uno; }};

bloqueSuma.value();

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

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:

import util.blocks.*;
import util.higherOrderCollections.*;
import static util.higherOrderCollections.HOCFactory.CollectionEnum.*;

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:

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); }});

¡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:

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

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:

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>();

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:

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);
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:

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

void doBlock(UnaryProcedureBlock<E> aBlock) : Evalúa el bloque sobre cada uno de


los elementos de la colección.

Ejemplo 10:

coleccion.doBlock(new UnaryProcedureBlock<Integer>() {
public void value(Integer elemento) {
System.out.print(elemento + “ ”); }});

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:

coleccion.count(new UnaryPredicateBlock<Integer>() {
public boolean value(Integer elemento) {
return elemento.intValue() % 2 == 0; }});

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:

coleccion.detect(new UnaryPredicateBlock<Integer>() {
public boolean value(Integer elemento) {
return elemento == 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:

coleccion.remove(new UnaryPredicateBlock<Integer>() {
public boolean value(Integer elemento) {
return elemento == 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:

coleccion.removeAll(new UnaryPredicateBlock<Integer>() {
public boolean value(Integer elemento) {
return elemento.intValue() % 2 != 0; }});

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:

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


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

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:

coleccion.collect(new UnaryFunctionBlock<Integer,Integer>() {
public Integer value(Integer elemento) {
return elemento * elemento; }});

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:

coleccion.select(new UnaryPredicateBlock<Integer>() {
public boolean value(Integer elemento) {
return elemento < 4; }});

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:

coleccion.reject(new UnaryPredicateBlock<Integer>() {
public boolean value(Integer elemento) {
return elemento < 4; }});

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:

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; }});

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

Вам также может понравиться