Reputation: 41271
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
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
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
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