A rose by any other name…

Similar iteration patterns, different naming patterns.

A Rosetta Stone for Smalltalk, Eclipse Collections and Java 8 Streams

I used this slide at JavaOne 2017 in a talk titled API Deep Dive: Designing Eclipse Collections [CON6133] which I presented with Nikhil Nanivadekar. This is a translation guide for iteration pattern names between Smalltalk, Eclipse Collections and Java 8 Streams.

As I pointed out in the talk, I chose Smalltalk as the source of inspiration for the names of the basic iteration patterns in Eclipse Collections.

If you know Smalltalk, learning Eclipse Collections (EC) just requires you to learn the syntax and types of the Java language. You will already be familiar with the iteration patterns. If you know Java 8 Streams, learning Eclipse Collections will require you to add nine synonyms to your vocabulary for common iteration patterns. Three of these patterns start with the same prefix (any/all/none), and differ only in the suffix (Satisfy vs. Match). That’s an easy place to get started. Now you only have to learn six new names to become as fluent in the basic iteration patterns of Eclipse Collections as you are in Java 8 Streams. Eclipse Collections types allow you to use the API directly on collections without having to call the “bun” operations (e.g. collection.stream().iterationPattern().collect()). You can also use the Streams API if you want to with Eclipse Collections types. You get to choose whichever option you like best.

Let’s learn the iteration pattern synonyms by example. The following examples will be based on the Eclipse Collections Pet Kata domain.

Select / Filter

If you want to include items of a collection that satisfy a given condition (Predicate) you use select (EC) or filter (Streams). Eclipse Collections also has “With” versions of the iteration patterns that allow for more usages of method references.

// Eclipse Collections (Select)
MutableList<Person> peopleWithCats =
this.people.select(person -> person.hasPet(PetType.CAT));
// Eclipse Collections (SelectWith) 
MutableList<Person> peopleWithCats =
this.people.selectWith(Person::hasPet, PetType.CAT);
// Streams (Filter)
List<Person> peopleWithCats =
this.people.stream()
.filter(person -> person.hasPet(PetType.CAT))
.collect(Collectors.toList());

Reject / Filter (!)

If you want to exclude items of a collection that satisfy a given condition (Predicate) you use reject (EC) or filter (Streams) with a negation of the predicate.

// Eclipse Collections (Reject)
MutableList<Person> peopleWithoutCats =
this.people.reject(person -> person.hasPet(PetType.CAT));
// Eclipse Collections (RejectWith) 
MutableList<Person> peopleWithoutCats =
this.people.rejectWith(Person::hasPet, PetType.CAT);
// Streams (Filter !)
List<Person> peopleWithoutCats =
this.people.stream()
.filter(person -> !person.hasPet(PetType.CAT))
.collect(Collectors.toList());

Collect / Map

If you want to transform a collection of one type to a collection of another type, you use collect (EC) or map (Streams) with a Function. There is a method named collect on Streams as well, but it means something different. It is referred to as a mutable reduction. What it reduces to is up to you, so the return type of the Stream version of collect is defined by the Collector you give it. I wish the name of the method was different on Streams (e.g. mutableReduce). C’est la vie.

// Eclipse Collections
MutableList<String> firstNames =
this.people.collect(Person::getFirstName);
// Streams
MutableList<String> firstNames =
this.people.stream()
.map(Person::getFirstName)
.collect(Collectors2.toList());

I used Collectors2 in the above example to show you that you can also return Eclipse Collections types using Streams if you want to as well.

FlatCollect / FlatMap

If you want to transform a collection of collections to a single collection of another type, you use flatCollect (EC) or flatMap (Streams) with a Function that either returns an Iterable (EC) or a Stream (Streams).

// Eclipse Collections (FlatCollect - Eager)
MutableSet<PetType> petTypes =
this.people.flatCollect(Person::getPetTypes)
.toSet();
// Eclipse Collections (FlatCollect - Lazy)
MutableSet<PetType> petTypes =
this.people.asLazy()
.flatCollect(Person::getPetTypes)
.toSet();
// Streams
MutableSet<PetType> petTypes =
this.people.stream()
.flatMap(person -> person.getPetTypes().stream())
.collect(Collectors2.toSet());

In this example, I also show that you can choose either Eclipse Collections eager API (EC is eager by default), or you can save creating a temporary collection by using the lazy API. The asLazy() call will return a LazyIterable which has the same protocols available. The return type for flatCollect will change from MutableList in the first example to LazyIterable in the second. A LazyIterable is reusable (it is an Iterable), unlike a Stream which can only be used once (a Stream is like an Iterator).

Detect / Filter + FindFirst + Get

If you want to return the first element of a collection which matches a given condition (Predicate), you use detect (EC) or filter + findFirst + get (Streams). If you want to return an optional value representing either the first item that matches the condition or null, you can use detectOptional (EC) or filter + findFirst (Streams).

// Eclipse Collections (DetectWith)
Person firstPersonWithCat =
this.people.detectWith(Person::hasPet, PetType.CAT);
// Eclipse Collection (DetectWithOptional)
Optional<Person> optionalFirstPerson =
this.people.detectWithOptional(Person::hasPet, PetType.CAT);
// Streams (Filter + FindFirst + Get)
Person firstPersonWithCat =
this.people.stream()
.filter(person -> person.hasPet(PetType.CAT))
.findFirst();
.get();
// Streams (Filter + FindFirst)
Optional<Person> personOptional =
this.people.stream()
.filter(person -> person.hasPet(PetType.CAT))
.findFirst();

InjectInto / Reduce

If you want to inject an initial value into a function that then injects the result of each application of the function into the next iteration, you can use injectInto (EC) or reduce (Streams). This is probably the hardest method to understand, but has the easiest examples. You can use this method to calculate sum, min, max, product, etc.

// Eclipse Collections (InjectInto)
int numberOfPets =
this.people.collectInt(Person::getNumberOfPets)
.injectInto(0, Integer::sum);
// Streams (Reduce)
int numberOfPets =
this.people.stream()
.mapToInt(Person::getNumberOfPets)
.reduce(0, Integer::sum);

I hope this translation guide of basic iteration patterns will be helpful for developers who want to learn Eclipse Collections and are either familiar with the names used with Java 8 Streams or the names used with Smalltalk.

*Special thanks to my lovely wife for the photo of the rose I used in this post.*