Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A better approach to handling exceptions in a functional way

Exceptions, especially checked ones, can severely interrupt the flow of program logic when the FP idiom is used in Java 8. Here is an arbitrary example:

String s1 = "oeu", s2 = "2"; Stream.of(s1, s2).forEach(s ->      System.out.println(Optional.of(s).map(Integer::parseInt).get())); 

The above code breaks when there's an exception for an unparseable string. But say I just want to replace that with a default value, much like I can with Optional:

Stream.of(s1, s2).forEach(s ->     System.out.println(Optional.of(s)                               .map(Integer::parseInt)                               .orElse(-1))); 

Of course, this still fails because Optional only handles nulls. I would like something as follows:

Stream.of(s1, s2).forEach(s ->     System.out.println(         Exceptional.of(s)                    .map(Integer::parseInt)                    .handle(NumberFormatException.class, swallow())                    .orElse(-1))); 

Note: this is a self-answered question.

like image 432
Marko Topolnik Avatar asked Jul 07 '15 14:07

Marko Topolnik


People also ask

Is it a good approach to throw an exception?

The short answer is NO. You would throw an exception if the application can't continue executing with the bad data. In your example, the logic is to display an error message on the front end and Option 2 is the cleaner method for achieving this requirement.

What is exception handling Handling?

Exception handling is the process of responding to unwanted or unexpected events when a computer program runs. Exception handling deals with these events to avoid the program or system crashing, and without this process, exceptions would disrupt the normal operation of a program.


2 Answers

Presented below is the full code of the Exceptional class. It has a quite large API which is a pure extension of the Optional API so it can be a drop-in replacement for it in any existing code—except that it isn't a subtype of the final Optional class. The class can be seen as being in the same relationship with the Try monad as Optional is with the Maybe monad: it draws inspiration from it, but is adapted to the Java idiom (such as actually throwing exceptions, even from non-terminal operations).

These are some key guidelines followed by the class:

  • as opposed to the monadic approach, doesn't ignore Java's exception mechanism;

  • instead it relieves the impedance mismatch between exceptions and higher-order functions;

  • exception handling not statically typesafe (due to sneaky throwing), but always safe at runtime (never swallows an exception except on explicit request).

The class tries to cover all the typical ways to handle an exception:

  • recover with some handling code which provides a substitute value;
  • flatRecover which, analogous to flatMap, allows to return a new Exceptional instance which will be unwrapped and the state of the current instance suitably updated;
  • propagate an exception, throwing it from the Exceptional expression and making the propagate call declare this exception type;
  • propagate it after wrapping into another exception (translate it);
  • handle it, resulting in an empty Exceptional;
  • as a special case of handling, swallow it with an empty handler block.

The propagate approach allows one to selectively pick which checked exceptions he wants to expose from his code. Exceptions which remain unhandled at the time a terminal operation is called (like get) will be sneakily thrown without declaration. This is often considered as an advanced and dangerous approach, but is nevertheless often employed as a way to somewhat alleviate the nuisance of checked exceptions in combination with lambda shapes which do not declare them. The Exceptional class hopes to offer a cleaner and more selective alternative to sneaky throw.


/*  * Copyright (c) 2015, Marko Topolnik. All Rights Reserved.  *  * Licensed under the Apache License, Version 2.0 (the "License");  * you may not use this file except in compliance with the License.  * You may obtain a copy of the License at  *  * http://www.apache.org/licenses/LICENSE-2.0  *  * Unless required by applicable law or agreed to in writing, software  * distributed under the License is distributed on an "AS IS" BASIS,  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  * See the License for the specific language governing permissions and  * limitations under the License.  */ import java.util.NoSuchElementException; import java.util.Objects; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier;  public final class Exceptional<T> {   private final T value;   private final Throwable exception;    private Exceptional(T value, Throwable exc) {     this.value = value;     this.exception = exc;   }    public static <T> Exceptional<T> empty() {     return new Exceptional<>(null, null);   }    public static <T> Exceptional<T> ofNullable(T value) {     return value != null ? of(value) : empty();   }    public static <T> Exceptional<T> of(T value) {     return new Exceptional<>(Objects.requireNonNull(value), null);   }    public static <T> Exceptional<T> ofNullableException(Throwable exception) {     return exception != null? new Exceptional<>(null, exception) : empty();   }    public static <T> Exceptional<T> ofException(Throwable exception) {     return new Exceptional<>(null, Objects.requireNonNull(exception));   }    public static <T> Exceptional<T> from(TrySupplier<T> supplier) {     try {       return ofNullable(supplier.tryGet());     } catch (Throwable t) {       return new Exceptional<>(null, t);     }   }    public static Exceptional<Void> fromVoid(TryRunnable task) {     try {       task.run();       return new Exceptional<>(null, null);     } catch (Throwable t) {       return new Exceptional<>(null, t);     }   }    public static <E extends Throwable> Consumer<? super E> swallow() {     return e -> {};   }    public T get() {     if (value != null) return value;     if (exception != null) sneakyThrow(exception);     throw new NoSuchElementException("No value present");   }    public T orElse(T other) {     if (value != null) return value;     if (exception != null) sneakyThrow(exception);     return other;   }    public T orElseGet(Supplier<? extends T> other) {     if (value != null) return value;     if (exception != null) sneakyThrow(exception);     return other.get();   }    public Stream<T> stream() {        return value == null ? Stream.empty() : Stream.of(value);    }    public<U> Exceptional<U> map(Function<? super T, ? extends U> mapper) {     Objects.requireNonNull(mapper);     if (value == null) return new Exceptional<>(null, exception);     final U u;     try {       u = mapper.apply(value);     } catch (Throwable exc) {       return new Exceptional<>(null, exc);     }     return ofNullable(u);   }    public<U> Exceptional<U> flatMap(Function<? super T, Exceptional<U>> mapper) {     Objects.requireNonNull(mapper);     return value != null ? Objects.requireNonNull(mapper.apply(value)) : empty();   }    public Exceptional<T> filter(Predicate<? super T> predicate) {     Objects.requireNonNull(predicate);     if (value == null) return this;     final boolean b;     try {       b = predicate.test(value);     } catch (Throwable t) {       return ofException(t);     }     return b ? this : empty();   }    public <X extends Throwable> Exceptional<T> recover(       Class<? extends X> excType, Function<? super X, T> mapper)   {     Objects.requireNonNull(mapper);     return excType.isInstance(exception) ? ofNullable(mapper.apply(excType.cast(exception))) : this;   }    public <X extends Throwable> Exceptional<T> recover(       Iterable<Class<? extends X>> excTypes, Function<? super X, T> mapper)   {     Objects.requireNonNull(mapper);     for (Class<? extends X> excType : excTypes)       if (excType.isInstance(exception))         return ofNullable(mapper.apply(excType.cast(exception)));     return this;   }    public <X extends Throwable> Exceptional<T> flatRecover(       Class<? extends X> excType, Function<? super X, Exceptional<T>> mapper)   {     Objects.requireNonNull(mapper);     return excType.isInstance(exception) ? Objects.requireNonNull(mapper.apply(excType.cast(exception))) : this;   }    public <X extends Throwable> Exceptional<T> flatRecover(       Iterable<Class<? extends X>> excTypes, Function<? super X, Exceptional<T>> mapper)   {     Objects.requireNonNull(mapper);     for (Class<? extends X> c : excTypes)       if (c.isInstance(exception))         return Objects.requireNonNull(mapper.apply(c.cast(exception)));     return this;   }    public <E extends Throwable> Exceptional<T> propagate(Class<E> excType) throws E {     if (excType.isInstance(exception))       throw excType.cast(exception);     return this;   }    public <E extends Throwable> Exceptional<T> propagate(Iterable<Class<? extends E>> excTypes) throws E {     for (Class<? extends E> excType : excTypes)       if (excType.isInstance(exception))         throw excType.cast(exception);     return this;   }    public <E extends Throwable, F extends Throwable> Exceptional<T> propagate(       Class<E> excType, Function<? super E, ? extends F> translator)   throws F   {     if (excType.isInstance(exception))       throw translator.apply(excType.cast(exception));     return this;   }    public <E extends Throwable, F extends Throwable> Exceptional<T> propagate(       Iterable<Class<E>> excTypes, Function<? super E, ? extends F> translator)   throws F   {     for (Class<? extends E> excType : excTypes)       if (excType.isInstance(exception))         throw translator.apply(excType.cast(exception));     return this;   }    public <E extends Throwable> Exceptional<T> handle(Class<E> excType, Consumer<? super E> action) {     if (excType.isInstance(exception)) {       action.accept(excType.cast(exception));       return empty();     }     return this;   }    public <E extends Throwable> Exceptional<T> handle(Iterable<Class<E>> excTypes, Consumer<? super E> action) {     for (Class<? extends E> excType : excTypes)       if (excType.isInstance(exception)) {         action.accept(excType.cast(exception));         return empty();       }     return this;   }    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {     if (value != null) return value;     if (exception != null) sneakyThrow(exception);     throw exceptionSupplier.get();   }    public boolean isPresent() {     return value != null;   }    public void ifPresent(Consumer<? super T> consumer) {     if (value != null)       consumer.accept(value);     if (exception != null) sneakyThrow(exception);   }    public boolean isException() {     return exception != null;   }    @Override   public boolean equals(Object obj) {     if (this == obj) return true;     return obj instanceof Exceptional && Objects.equals(value, ((Exceptional)obj).value);   }    @Override   public int hashCode() {     return Objects.hashCode(value);   }    @SuppressWarnings("unchecked")   private static <T extends Throwable> void sneakyThrow(Throwable t) throws T {     throw (T) t;   } } 

@FunctionalInterface public interface TrySupplier<T> {   T tryGet() throws Throwable; } 

@FunctionalInterface public interface TryRunnable {   void run() throws Throwable; } 
like image 76
Marko Topolnik Avatar answered Oct 10 '22 19:10

Marko Topolnik


What if every functional interface provided by java.util.function was allowed to throw an exception?

public interface ThrowingSupplier<R, X extends Throwable> {     public R get() throws X; } 

We could use some default methods to provide the behavior you want.

  • You could fallback to some default value or action
  • Or you could try to perform another action which may throw an exception

I've written a library which redefines most of the interfaces in java.util.function this way. I even provide a ThrowingStream which let's you use these new interfaces with the same API as a regular Stream.

@FunctionalInterface public interface ThrowingSupplier<R, X extends Throwable> {     public R get() throws X;      default public Supplier<R> fallbackTo(Supplier<? extends R> supplier) {         ThrowingSupplier<R, Nothing> t = supplier::get;         return orTry(t)::get;     }      default public <Y extends Throwable> ThrowingSupplier<R, Y> orTry(             ThrowingSupplier<? extends R, ? extends Y> supplier) {         Objects.requireNonNull(supplier, "supplier");         return () -> {             try {                 return get();             } catch (Throwable x) {                 try {                     return supplier.get();                 } catch (Throwable y) {                     y.addSuppressed(x);                     throw y;                 }             }         };     } } 

(Nothing is a RuntimeException that can never be thrown.)


Your original example would become

ThrowingFunction<String, Integer, NumberFormatException> parse = Integer::parseInt; Function<String, Optional<Integer>> safeParse = parse.fallbackTo(s -> null)     .andThen(Optional::ofNullable); Stream.of(s1, s2)     .map(safeParse)     .map(i -> i.orElse(-1))     .forEach(System.out::println); 
like image 33
Jeffrey Avatar answered Oct 10 '22 18:10

Jeffrey