slim
slim

Reputation: 41271

Partitioning a list of Eithers in Java

Largely as an academic exercise, I'm experimenting with my own implementation of Either in Java, inspired by Haskell's either (https://hackage.haskell.org/package/base-4.9.1.0/docs/Data-Either.html)

I have a working implementation which I'm pleased with:

    Either<String, Integer> either = Either.left(A_STRING);
    assertThat(either.left(), is(A_STRING));
    assertThat(either.isLeft(), is(true));
    assertThat(either.isRight(), is(false));
    assertThat(either.map(x -> AN_INTEGER, y -> A_LONG), is(Either.left(AN_INTEGER)));
    assertThat(either.consolidate(x -> A_LONG, y -> ANOTHER_LONG), is(A_LONG));

The equivalent of Haskell's lefts and rights is to filter a stream:

 Stream<String> s = streamOfEithers
     .filter(Either::isLeft)
     .map( x -> x.left())

The problem I'm having is in creating an equivalent of Haskell's partitionEithers.

My first instinct was:

  streamOfEithers.collect(Collectors.groupingBy(Either::isLeft))

However this seems to lose my types -- it returns Map<Boolean, Either<? extends Object, ? extends Object>> rather than (for example) Map<Boolean, Either<String,Integer>.

Of course, ideally the result of partitioning is not two lists of Eithers but two lists, one of each type. Java doesn't have a Pair<T,U> class, but if it did, perhaps:

 List<Either<String,Integer>> listOfEithers = ...
 Pair<List<String>,List<Integer>> partitioned = Either.partitionList(listOfEithers);

Otherwise, the best I have managed is:

 Map<Boolean, Either<String,Integer>> partitioned = Either.partitionList(listOfEithers);

... which is not a very convenient structure for a client.

Other than writing a Pair, or using a Pair from some collections library (Guava, incidentally, refuses to provide Pair as it "does more harm than good"), how can I best provide an equivalent to Haskell's partitionEithers?

Upvotes: 4

Views: 269

Answers (3)

M. Prokhorov
M. Prokhorov

Reputation: 3993

Try this:

Map<Boolean, List<Either<Integer, String>>> collect = list.stream().collect(Collectors.partitioningBy(Either::isLeft));

Assuming that your Either implementation's generic type signature is Either<L,R> (which is what I've tested with example implementation), there should be no problem with type erasure, and it will compile as expected.

After that, you can initialize your Pair like this:

Pair<List<Integer>, List<String>> result =
      pair(
          collect.getOrDefault(true, Collections.emptyList()).stream().map(Either::left).collect(toList()),
          collect.getOrDefault(false, Collections.emptyList()).stream().map(Either::right).collect(toList()));

Upvotes: 1

Jorn Vernee
Jorn Vernee

Reputation: 33895

You could create your own aggregate type for this, in place of Pair, specifically tailored to Either, which has a collector to go along with it. Here is a simplified implementation:

class Aggregate<T, U> {
    List<T> tList = new ArrayList<>();
    List<U> uList = new ArrayList<>();

    public void add(Either<T, U> e) {
        if(e.isLeft()) {
            tList.add(e.left());
        } else {
            uList.add(e.right());
        }
    }

    public Aggregate<T, U> combine(Aggregate<T, U> other) {
        tList.addAll(other.tList);
        uList.addAll(other.uList);
        return this; // could create a new Aggregate here instead...
    }

    public static <T, U> Collector<Either<T,U>, ? , Aggregate<T, U>> partitionEithers() {
        return Collector.of(Aggregate::new, Aggregate::add, Aggregate::combine);
    }
}

Then the result becomes:

Aggregate<String, Integer> result = streamOfEithers.collect(Aggregate.partitionEithers());

Upvotes: 2

AleSod
AleSod

Reputation: 422

I am not familiar with Haskell so I cannot provide a full answer to the question.

I do however know a bit about Java generics. The issue with

streamOfEithers.collect(Collectors.groupingBy(Either::isLeft))

returning Map<Boolean, Either<? extends Object, ? extends Object>>

is because of type erasure. Simply put, java generics are much weaker than the same type of logic in many other languages (eg. templates in C++). Here is a more detailed explanation of what generics can and can't do. If you would like to dig really deep I would suggest looking here.

Upvotes: 0

Related Questions