dtonhofer

dtonhofer

Functional Programming in Java, Second Edition: Chapter 10: (Much?) smoother Try<T>

It turns out that Try<T> can be ameliorated by judicious application of subclassing: no need to distinguish by actual type in the subclass inside an overridden method, the subclass knows what it is and it doesn need the overridden method at all.

Here we go, I have packed the three classes into a common “module” class called Exceptional but this can of course be dropped with no effect.

A more particular changes is that we only carre Exception not Throwable. Carrying (and catching in the stream pipeline of) Throwable is too much! We do not want to catch OutOfMemoryError. We should NOT catch anything in the Error sub-hierarchy, only Exception, which includes unchecked and checked exceptions, but more importantly those are the exceptions we can do something about.

The type variables have been renamed for clarity:

  • OUT is in type of the output of the “code” function.
  • HELD is the type of the successfully computed output of the “code” function (so the same as OUT)
  • NEXT is the type of output of “mapper” function.

The comments include further notes.

I will post these along with example code, but a skeleton example has been added.

import java.util.concurrent.Callable;
import java.util.function.Function;

public abstract class Exceptional {

    // Modified "exceptionhandling/fpij/Try.java" on page 175

    public sealed interface Try<HELD> permits Success, Failure {

        static <OUT> Try<OUT> of(Callable<OUT> code) {
            try {
                return new Success<>(code.call());
            } catch(Exception ex) {
                return new Failure<>(ex);
            }
        }

        <NEXT> Try<NEXT> map(Function<HELD, NEXT> mapper);
    }

    // Modified "exceptionhandling/fpij/Success.java" on page 176

    static final class Success<HELD> implements Try<HELD> {

        private final HELD result;

        public Success(HELD result) { this.result = result; }

        public HELD getResult() { return result; }

        @Override
        public <NEXT> Try<NEXT> map(Function<HELD, NEXT> mapper) {
            return Try.of(() -> mapper.apply(getResult()));
        }

    }

    // Modified "exceptionhandling/fpij/Failure.java" on 176

    static final class Failure<HELD> implements Try<HELD> {

        private final Exception ex;

        public Failure(Exception ex) { this.ex = ex; }

        public Exception getException() { return ex; }

        // Note that the parametrized type changes here!!
        // Failure<HELD>.map(HELD->NEXT) returns Failure<NEXT>.
        // So we cannot just return "this", even though in
        // the compiled and type-erased code, it would make no difference.

        @Override
        public <NEXT> Try<NEXT> map(Function<HELD, NEXT> mapper) {
            return new Failure<NEXT>(ex);
        }

    }


}

As an application as JUnit test, getting the airport names:

Note that the “pattern matching switch” is still “preview” in Java 18 (I actually thought it been released in Java 17? Apparently not!). However, “Java 18” is supposed to be the “Java of the book”, so can we really use it?

    // Airports to retrieve. "IHA" is an invalid code, so gives rise to an error during fetch.
    private final List<String> iataCodes = List.of("AUS", "DFW", "HOU", "IHA", "SAT");
    @Test
    void retrieveMultipleAirportNames_streamPipelineMonadicAccordingToBook() {
        final List<String> results =
                iataCodes.stream()
                        .map(iataCode -> Try.of(() -> getNameOfAirport(iataCode)))
                        .map(nameTry -> nameTry.map(String::toUpperCase))
                        .map(nameTry -> {
                            if (nameTry instanceof Success<String> succ) {
                                return succ.getResult();
                            } else {
                                return "Error: " + ((Failure<String>) nameTry).getException().getMessage();
                            }
                        })
                        .toList();
        System.out.println(String.join("\n", results));
    }
}

Another application, where we switch the type from Integer to String, and back to Integer. More fun than above. For symmetry reasons, we immediately switch to a Stream<Try<Integer>> without doing anything at the very start of the pipline.

   // An example using "Try" which changes the carried type from Integer to String to Integer
    // and has two places in the stream where exceptions can be thrown.
    // 4
    // Error: We don't serve bravo here!
    // 4
    // Error: Index -1 out of bounds for length 4
    // Error: Index 6 out of bounds for length 4
    // 7
    // Error: We don't serve bravo here!

    @Test
    void retrieveFromArray() {
        final List<Integer> indexes = List.of(0, 1, 0, -1, 6, 2, 1);
        final String[] array = new String[]{"alfa", "bravo", "charlie", "delta"};
        final List<String> results =
                indexes.stream()
                        .map(it -> Try.of(() -> it))
                        .map(intTry -> intTry.map(i -> array[i]))
                        .map(strTry -> strTry.map(str -> {
                            if ("bravo".equals(str)) {
                                throw new IllegalArgumentException("We don't serve " + str + " here!");
                            } else {
                                return str.length();
                            }
                        }))
                        .map(intTry -> {
                            if (intTry instanceof Success<Integer> succ) {
                                return Integer.toString(succ.getResult()); // make a String
                            } else {
                                return "Error: " + ((Failure<Integer>) intTry).getException().getMessage();
                            }
                        })
                        .toList();
        System.out.println(String.join("\n", results));
    }

The IDE (here IntelliJ IDEA) is extremely helpful in showing the stream types. Text-only editors are no longer adequate…

If we want to break off at the first occurrence of an exception (but we lose information about the exception here)

    @Test
    void retrieveFromArrayBreakOffAtFirstException() {
        final List<Integer> indexes = List.of(0, 1, 0, -1, 6, 2, 1);
        final String[] array = new String[]{"alfa", "bravo", "charlie", "delta"};
        final List<String> results =
                indexes.stream()
                        .map(it -> Try.of(() -> it))
                        .map(intTry -> intTry.map(i -> array[i]))
                        .takeWhile(xTry -> xTry instanceof Success<?>)
                        .map(strTry -> ((Success<String>) strTry).getResult()) // make a String
                        .toList();
        System.out.println(String.join("\n", results));
    }

If we want to break off AND retain the exception, it seems easiest to squirrel it away into a “global variable” stash.

    @Test
    void retrieveFromArrayBreakOffAtFirstExceptionButKeepException() {
        final List<Exception> stash = Collections.synchronizedList(new ArrayList<>(1));
        final List<Integer> indexes = List.of(0, 1, 0, -1, 6, 2, 1);
        final String[] array = new String[]{"alfa", "bravo", "charlie", "delta"};
        final List<String> results =
                indexes.stream()
                        .map(it -> Try.of(() -> it))
                        .map(intTry -> intTry.map(i -> array[i]))
                        .takeWhile(xTry -> {
                            if (xTry instanceof Success<String>) {
                                return true;
                            }
                            else {
                                stash.add(((Failure<String>)xTry).getException());
                                return false;
                            }
                        })
                        .map(strTry -> ((Success<String>) strTry).getResult()) // make a String
                        .toList();
        System.out.println(String.join("\n", results));
        if (!stash.isEmpty()) {
            System.out.println("Final exception: " + stash.get(0));
        }
    }

Where Next?

Popular Pragmatic Bookshelf topics Top

yulkin
your book suggests to use Image.toByteData() to convert image to bytes, however I get the following error: "the getter ‘toByteData’ isn’t...
New
Mmm
Hi, build fails on: bracket-lib = “~0.8.1” when running on Mac Mini M1 Rust version 1.5.0: Compiling winit v0.22.2 error[E0308]: mi...
New
conradwt
First, the code resources: Page 237: rumbl_umbrella/apps/rumbl/mix.exs Note: That this file is missing. Page 238: rumbl_umbrella/app...
New
dsmith42
Hey there, I’m enjoying this book and have learned a few things alredayd. However, in Chapter 4 I believe we are meant to see the “&gt;...
New
mert
AWDWR 7, page 152, page 153: Hello everyone, I’m a little bit lost on the hotwire part. I didn’t fully understand it. On page 152 @rub...
New
EdBorn
Title: Agile Web Development with Rails 7: (page 70) I am running windows 11 pro with rails 7.0.3 and ruby 3.1.2p20 (2022-04-12 revision...
New
andreheijstek
After running /bin/setup, the first error was: The foreman' command exists in these Ruby versions: That was easy to fix: gem install fore...
New
New
dachristenson
@mfazio23 Android Studio will not accept anything I do when trying to use the Transformations class, as described on pp. 140-141. Googl...
New
roadbike
From page 13: On Python 3.7, you can install the libraries with pip by running these commands inside a Python venv using Visual Studio ...
New

Other popular topics Top

Exadra37
I am thinking in building or buy a desktop computer for programing, both professionally and on my free time, and my choice of OS is Linux...
New
New
dimitarvp
Small essay with thoughts on macOS vs. Linux: I know @Exadra37 is just waiting around the corner to scream at me “I TOLD YOU SO!!!” but I...
New
PragmaticBookshelf
Build highly interactive applications without ever leaving Elixir, the way the experts do. Let LiveView take care of performance, scalabi...
New
Maartz
Hi folks, I don’t know if I saw this here but, here’s a new programming language, called Roc Reminds me a bit of Elm and thus Haskell. ...
New
AstonJ
We’ve talked about his book briefly here but it is quickly becoming obsolete - so he’s decided to create a series of 7 podcasts, the firs...
New
New
PragmaticBookshelf
Programming Ruby is the most complete book on Ruby, covering both the language itself and the standard library as well as commonly used t...
New
DevotionGeo
I have always used antique keyboards like Cherry MX 1800 or Cherry MX 8100 and almost always have modified the switches in some way, like...
New
PragmaticBookshelf
Explore the power of Ash Framework by modeling and building the domain for a real-world web application. Rebecca Le @sevenseacat and ...
New

Latest in Functional Programming in Java, Second Edition

Functional Programming in Java, Second Edition Portal

Sub Categories: