cyclops icon indicating copy to clipboard operation
cyclops copied to clipboard

How to handle checked exceptions in streams?

Open laurent-thiebaud-gisaia opened this issue 7 years ago • 5 comments

Suppose this example of code:

aStream.foreach(object -> consumeObjectMayThrowCheckedException())

This will not compile because of the checked exception. I think that the state of art, in functional programming, is to process the stream, then get the list of thrown exception to handle it (let's say in order to log it). I've been using the following code, that I don't like:

String errorMessage = mylist.stream().map(my ->

                Try.runWithCatch(() -> {
                    my.mayThrowCheckedException();

                }, CheckedException.class)
        )
                .filter(Try::isFailure)
                .map(t -> t.failureGet().orElse(new CheckedException("No exception")).getMessage())
                .collect(Collectors.joining(", "));

What would be the proper to achieve it? What is the state of the art using Cyclops?

Thanks.

laurent-thiebaud-gisaia avatar Dec 18 '18 12:12 laurent-thiebaud-gisaia

Hi @laurent-thiebaud-gisaia ,

The IO monad in cyclops provides some support for checked exceptions. If you just wanted to log errors you could write something like this

IO.sync(myList)
  .checkedMap(My::mayThrowCheckedException)
  .forEach(System.out::println,System.err::println,()->System.out.println("Complete"));

In the forEach method we can provide consumers for each processed element, each error and the complete signal.

There is also a mapTry operator which can do the mapping directly to a Try, if we don't want to handle errors separately. mapTry doesn't directly support checked exceptions, but we can plugin the ExceptionSoftener for that :

import static com.oath.cyclops.util.ExceptionSoftener.softenFunction;

IO.sync(myList)
    .mapTry(softenFunction(My::mayThrowCheckedException))

Rather than use failureGet, if we convert our Try to en Either type we can call map on the 'left' side of the Either.

IO.sync(myList)
   .mapTry(softenFunction(My::mayThrowCheckedException))
   .map(Try::toEither)
   .map(e->e.mapLeft(Throwable::getMessage))

And to finish convert to a String

IO.sync(myList)
   .mapTry(softenFunction(My::mayThrowCheckedException))
   .map(Try::toEither)
   .map(e->e.mapLeft(Throwable::getMessage))
   .map(e->e.leftOrElse("No Exception"))
   .stream()
   .collect(Collectors.joining(","));

johnmcclean avatar Dec 18 '18 22:12 johnmcclean

Hi,

I was able to make my code clearer using the either, thank you. However the following:

IO.sync(myList)
    .mapTry(softenFunction(My::mayThrowCheckedException))

doesn't compile because

non static method cannot be referenced from a static context java

laurent-thiebaud-gisaia avatar Dec 19 '18 08:12 laurent-thiebaud-gisaia

It may depend on your classes. Is my a variable or a class? I created a simple class called My.

static class My{
      public String mayThrowCheckedException() throws Exception{
            throw new RuntimeException("Erorr thrown");
      }
 }

List<My> myList = Arrays.asList(new My(), new My(), new My());

String errorMessage = IO.sync(myList)
                             .mapTry(softenFunction(My::mayThrowCheckedException))
                             .map(Try::toEither)
                             .map(e->e.mapLeft(Throwable::getMessage))
                             .map(e->e.leftOrElse("No Exception"))
                             .stream()
                             .collect(Collectors.joining(","));

This compiles, but probably doesn't quite match your classes.

johnmcclean avatar Dec 20 '18 13:12 johnmcclean

Does it not make sense to convert

.map(Try::toEither)
.map(e->e.mapLeft(Throwable::getMessage))
.map(e->e.leftOrElse("No Exception"))

to .map(t -> t.fold(s -> "No Exception", e->e.getMessage()))

ayush-finix avatar Mar 15 '19 19:03 ayush-finix

Yes, that is much neater

johnmcclean avatar Mar 25 '19 21:03 johnmcclean