Jaggregate version 3.3
What is Jaggregate?
Jaggregate is a collections library that is modeled after the ANSI Smalltalk collection protocols, as described here [PDF].
Why write another collections library when Sun's Java already has one?
The author of Jaggregate has been ruined by Smalltalk and Ruby. In those languages and environments, everything really is an object, and (at least in Smalltalk, and mostly in Ruby) a program does work by sending messages to objects. Smalltalk in fact has no native syntax for such concepts as:
- conditionals (
if
statements): instead messages such as#ifTrue:
and#ifFalse:
are sent to a boolean value that is the result of evaluating expressions that answer booleans. The arguments to these messages are "blocks" of code that are executed if the appropriate condition holds.|aName| aName size > 20 ifTrue: [Transcript show: 'Long name!'; cr].
- looping (
while
statements): instead messages such as#timesRepeat:
are sent to an integer value, with an argument that is the block of code to be executed the appropriate number of times. Another way of looping is to send the message#to:do:
to an integer value, where the arguments are a terminating integer value and the block of code to execute, which itself accepts the next integer as its argument.3 timesRepeat: [Transcript show: 'Hey!'; cr]. 1 to: 3 do: [:each | Transcript show: 'And-a ', each; cr]. 3.times do |i| puts 'Hey!' end (1 .. 3) do |i| puts "And-a #{i}" end
So the way to perform common enumerative tasks on collections is to send
messages such as #select:
, #detect:
,
#collect:
, #reject:
, like so:
|numbers evenFinder evens odds firstEven nearestEvens|
numbers := OrderedCollection with: 1 with: 2 with: 3.
evenFinder := [:anInteger | anInteger \\ 2 = 0].
evens := numbers select: evenFinder.
odds := numbers reject: evenFinder.
firstEven := numbers detect: evenFinder.
nearestEvens := numbers collect:
[:each | each \\ 2 = 0 ifTrue: [each] ifFalse: [each + 1]].
numbers = (1 .. 3)
evenFinder = proc { |i| i % 2 == 0 }
evens = numbers.select &evenFinder
odds = numbers.reject &evenFinder
firstEven = numbers.detect &evenFinder
nearestEvens = numbers.collect { |i| i % 2 == 0 ? i : i + 1 }
Neat, huh? Nice and clean.
Now consider typical Java Collections Framework (JCF) idioms for accomplishing these tasks (being generous and collapsing loops where possible):
List<Integer> numbers = new ArrayList<Integer>();
numbers.add( 1 );
numbers.add( 2 );
numbers.add( 3 );
List<Integer> evens = new ArrayList<Integer>();
List<Integer> odds = new ArrayList<Integer>();
int firstEven;
boolean foundAnEven = false;
List<Integer> nearestEvens = new ArrayList<Integer>();
for ( Integer next : numbers ) {
if ( next % 2 == 0 ) {
evens.add( next );
if ( !foundAnEven ) {
firstEven = next;
foundAnEven = true;
}
nearestEvens.add( next );
}
else {
odds.add( next );
nearestEvens.add( next + 1 );
}
}
The JCF filled a void in the core of the library that previously only commercial software vendors had filled. Nevertheless, the JCF sports some bothersome features:
- External iteration. To enumerate a collection, you ask it
for another object that knows how to do the iteration: an
Iterator
. What's wrong with the collection just being able to maintain an iterative state itself? You can make a case for wanting to be able to pick objects off a bit at a time rather than going over all the elements in whatever order, but it seems the far more common case is to do something with all the elements, filter out those which meet certain criteria, etc. - Verbosity. The construction and adding-elements code, and
the external iteration requires more, repetitive code than you'd hope for.
J2SE 5.0 introduced the
Iterable
interface which works in tandem with some new syntactic sugar:List<String> names; // ... for ( String each : names ) { // ... }
- Implementers of certain collection interfaces whose contracts specify
that certain methods on the API are optional can choose to have
their implementations raise
UnsupportedOperationException
. So if I have a reference of typeList
, referring to an object whose concrete class is that of the objects returned byCollections.unmodifiableList()
, I'll get a runtime error if I calladd(Object)
through that reference. So effectively I cannot substitute any type ofList
into this reference. This is a violation of the Liskov Substitution Principle (LSP).
I'm not here to knock the JCF; but it does have some limitations.
Jaggregate in Contrast to JCF
Jaggregate benefits from the following:
- Not stuck with JCF design decisions (e.g.
Collection#toArray(Object[])
) - Fully generics-aware. Where sensible and helpful, Jaggregate collections and their methods establish type parameters and bounds to help ensure that no stray items get into the collections.
- Collection constructors allow easy conversion of any
Iterable
or array to a Jaggregate collection; for example:List<String> names = new ArrayList<String>(); // ... ReadOnlySequence<String> sortedNames = new NaturallySortedCollection<String>( names );
- Leverages J2SE 5 features where helpful: varargs, enums...
- Comparable performance to JCF
- Algorithms are methods on the collections. I'm sorry, I understand
how the C++ STL is about separating containers from algorithms, but I
don't like having to dig and figure out which
CollectionsUtil
method will shuffle myList
s or filter mySet
s. But that's just me. - Interfaces that support the Smalltalk/Ruby "block" concept. Hopefully Java will get closures sometime soon, so that the "anonymous inner class" method of specifying implementers of this interface won't be so odious. In the future, if Java supports native syntax for closures, this library will convert to using it.
- Interfaces that support the Smalltalk collection protocols. Smalltalk and Ruby do "duck typing"; but as Java is a statically typed language, Jaggregate must use something like interfaces to declare common protocol for collections.
Let's be honest here: It's a reasonably safe bet that Jaggregate will not overtake the JCF in popularity or mindshare. You can probably live with its limitations, and fill whatever gaps you need to with Jakarta Commons Collections and the like. This project is partly an experiment in Java 5, partly a pining for more of a Smalltalk-ish "objects do it themselves" model. But hopefully, you'll be interested enough to give it a try. Perhaps you'll learn something about Java generics, or be motivated to try out "everything is an object" languages such as Smalltalk or Ruby.
I'd love to hear your feedback!
© Copyright 2004-2008 Paul R. Holser, Jr. All rights reserved.
Last modified: $Id: index.html,v 1.53 2008/07/10 16:22:08 pholser Exp $