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 (
ifstatements): 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 (
whilestatements): 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
Iterableinterface which works in tandem with some new syntactic sugar:However, doesn't it seem kind of funny to add new syntax to support a library that maybe just needs a different model? (I guess you could make the argument: since the JCF is already here, why write a collections library that doesn't fit the existing model?)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 ofListinto 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
Iterableor 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
CollectionsUtilmethod will shuffle myLists or filter mySets. 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 $