DDT
DDT

Reputation: 145

Transform list of Either into list of left and list of right

Vavr's Either seems to solve one of my problems were some method does a lot of checks and returns either CalculationError or CalculationResult.

Either<CalculationError, CalculationResult> calculate (CalculationData calculationData) {
// either returns Either.left(new CalculationError()) or Either.right(new CalculationResult())

}

I have a wrapper which stores both errors and results

class Calculation {
 List<CalculationResult> calculationResults;
 List<CalculationError> calculationErrors;
}

Is there any neat solution to transform stream from Collection<CalculationData> data to Calculation?

Upvotes: 1

Views: 1000

Answers (3)

DBear
DBear

Reputation: 491

Since you are using VAVr, you may consider using Traversable instead of Collection. This will give you the method partition, which can be used to classify your list of Eithers into two groups like so:

Traversable<Either<CalculationError, CalculationResult>> calculations = ...;
var partitionedCalcs = calculations.partition(Either::isRight);
var results = partitionedCalcs._1.map(Either::getRight);
var errors = partitionedCalcs._2.map(Either::getLeft);
Calculation calcs = new Calculation(results, errors);

If you don't want to change your existing use of Collection to use a Traversable, then you can easily convert between them by using, for example, List.ofAll(Iterator) and Value.toJavaCollection(Function).

Upvotes: 0

rzwitserloot
rzwitserloot

Reputation: 103648

Well, you're using vavr, so 'neat' is right out. Tends to happen when you use tools that are hostile to the idiomatic form of the language. But, then again, 'neat' is a nebulous term with no clear defined meaning, so, I guess, whatever you think is 'neat', is therefore 'neat'. Neat, huh?

Either itself has the sequence method - but both of them work the way Either is supposed to work: They are left-biased in the sense that any Lefts present is treated as erroneous conditions, and that means all the Right values are discarded if even one of your Eithers is a Left. Thus, you cannot use either of the sequence methods to let Either itself bake you a list of the Right values. Even sequenceRight won't do this for you (it stops on the first Left in the list and returns that instead). The filter stuff similarly doesn't work like that - Either very much isn't really an Either in the sense of what that word means if you open a dictionary: It does not mean: A homogenous mix of 2 types. It's solely a non-java-like take on exception management: Right contains the 'answer', left contains the 'error' (you're using it correctly), but as a consequence there's nothing in the Either API to help with this task - which in effect involves 'please filter out the errors and then do something' ("Silently ignore errors" is rarely the right move. It is what is needed here, but it makes sense that the Either API isn't going to hand you a footgun. Even if you need it here).

Thus, we just write it plain jane java:

var calculation = new Calculation();
for (var e : mix) {
  if (e.isLeft()) calculation.calculationErrors.add(e.getLeft());
  if (e.isRight()) calculation.calculationResult.add(e.getRight());
}

(This presumes your Calculation constructor at least initializes those lists to empty mutables).

NB: Rob Spoor's answer also assumes this and is much, much longer. Sometimes the functional way is the silly, slow, unwieldy, hard to read, way.

NB2: Either.sequence(mix).orElseRun(s -> calculation.errors = s.asJava()); is a rather 'neat' way (perhaps - it's in the eye of the beholder) of setting up the errors field of your Calculation class. No joy for such a 'neat' trick to fill the 'results' part of it all, however. That's what the bulk of my answer is trying to explain: There is no nice API for that in Either, and it's probably by design, as that involves intentionally ignoring the errors in the list of Eithers.

Upvotes: 0

Rob Spoor
Rob Spoor

Reputation: 9175

This can be easily done using a custom collector. With a bit of pseudo code representing the Either:

Collector<Either<CalculationError, CalculationResult>, ?, Calculation> collector = Collector.of(
        Calculation::new,
        (calc, either) -> {
            if (either has error) {
                calc.calculationErrors.add(either.error);
            } else {
                calc.calculationResults.add(either.result);
            }
        },
        (calc1, calc2) -> {
            calc1.calculationErrors.addAll(calc2.calculationErrors);
            calc1.calculationResults.addAll(calc2.calculationResults);
            return calc1;
        }
);

Calculation calc = data.stream()
        .map(this::calculate)
        .collect(collector);

Note that Calculation should initialize its two lists (in the declaration or a new constructor).

Upvotes: 1

Related Questions