aol / cyclops

An advanced, but easy to use, platform for writing functional applications in Java 8.
Apache License 2.0
1.31k stars 136 forks source link

How to handle checked exceptions in streams? #1009

Open laurent-thiebaud-gisaia opened 5 years ago

laurent-thiebaud-gisaia commented 5 years ago

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.

johnmcclean commented 5 years ago

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(","));
laurent-thiebaud-gisaia commented 5 years ago

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

johnmcclean commented 5 years ago

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.

ayush-finix commented 5 years ago

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()))

johnmcclean commented 5 years ago

Yes, that is much neater