Search code examples
javatry-catchvavr

Javaslang - how do I join two successful Trys?


I'm still learning Javaslang/Vavr, so excuse the ignorance. The project I'm working on is stuck on Javaslang 2.1.0.

My question: is there a more "Functional" way (as opposed to imperative style) to structure my code to join multiple Trys only once they are successful?

I want to Try each input independently, the idea being to get as much as possible error information - I do not want to stop on the first error encountered (so orElse() etc. won't do the trick). But once no errors are found any more, I want to do something further involving all of the inputs.

My current code looks like this (suitably anonymized):

Try<BigDecimal> amountTry = Try.of(this::readNumber)
    .map(BigDecimal::valueOf)
    .onFailure(this::collectError);

Try<Currency> currencyTry = Try.of(this::readString)
    .map(currency -> currencyLookup(Currency.class, currency))
    .onFailure(this::collectError);

if (amountTry.isSuccess() && currencyTry.isSuccess()) {
    sale.setAmount(Amount.of(amountTry.get(), currencyTry.get()));
}

Can you suggest a pattern to replace the if() with something more in the functional style of programming?


Solution

  • The Javaslang/Vavr construct that you are looking for is the for comprehension construct, which is accessible through the API.For methods.

    import javaslang.control.Try;
    import static javaslang.API.For;
    ...
    
    For(amountTry, currencyTry)
        .yield(Amount::of)
        .forEach(sale::setAmount);
    

    That is, if both amountTry and currencyTry are non-empty, it creates an Iterable by yielding a result value on the cross-product of the two iterables, and performing an action on each of the resulting elements by invoking a Consumer. Here is the same in lambda form with explicit input types, if it helps you better understand it:

    For(amountTry, currencyTry)
        .yield((BigDecimal amount, Currency currency) -> Amount.of(amount, currency))
        .forEach((Amount amount) -> sale.setAmount(amount));
    

    Later versions of the library have overloads of the for comprehension for Try which will return a Try instance instead of Iterable, which makes the API a little bit nicer if you want to stay in Try domain.