I have a bunch of these:
Validation<String, Foo> a;
Validation<String, Foo> b;
Validation<String, Foo> c;
Here are some of their methods:
boolean isValid();
boolean isInvalid(); // === !isValid()
String getError();
Now, I was trying to do this:
Stream.of(a, b, c).reduce(
Validation.valid(foo),
(a, b) -> a.isValid() && b.isValid()
? Validation.valid(foo)
: String.join("; ", a.getError(), b.getError())
);
There's the obvious issue that if only one of a
or b
is in error, then there's a needless ;
. But there's a more serious issue: getError()
throws an exception if the validation is valid.
Is there a way I can write this lambda (or use something else in the io.vavr.control.Validation library) without making all 4 cases (a && b
, a && !b
, !a && b
, !a && !b
) explicit?
EDIT
To be clearer, I wanted a result of Validation<String, Foo>
in the end. I think it behaves like a "monad," in that way, but I'm not sure.
I think what you're trying to achieve is easier to solve in Either
domain.
First, convert your stream of Validation
s to a stream of Either
s:
Stream<Either<String, Foo>> eithers = Stream.of(a, b, c)
.map(Validation::toEither);
then combine them:
Either<String, Foo> result = Either.sequence(eithers)
.mapLeft(seq -> seq.collect(Collectors.joining("; ")))
.map(combinator); // fill in with combinator function that converts
// a Seq<Foo> into a single Foo
Since you didn't specify how you want to combine multiple valid Foo
objects into a single one, I left it open for you to fill in the combinator function in the above example.
Either.sequence(...)
will reduce many eithers into a single one by returning an Either.Left
containing the sequence of left values if any of the provided eithers is a left, or an Either.Right
containing a (possibly empty) sequence of all right values, if none of the provided eithers is a left.
There's a Validation.sequence(...)
method that can do it without converting into Either domain (which I somehow missed while creating my original answer -- thanks for pointing out):
Validation<Seq<String>, Seq<Foo>> validations = Validation.sequence(
Stream.of(a, b, c)
.map(v -> v.mapError(List::of))
);
Validation<String, Foo> result = validations
.mapError(errors -> errors.collect(Collectors.joining("; ")))
.map(combinator); // fill in with combinator function that converts
// a Seq<Foo> into a single Foo
You said that the Foo
instances are the same, that means that you could use Seq::head
in place of the combinator function. But you'll need to take care not to use an empty sequence of validations as input as it will cause Seq::head
to throw NoSuchElementException
in that case.