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

ianwillie
Hello Brian, I have some problems with running the code in your book. I like the style of the book very much and I have learnt a lot as...
New
lirux
Hi Jamis, I think there’s an issue with a test on chapter 6. I own the ebook, version P1.0 Feb. 2019. This test doesn’t pass for me: ...
New
AndyDavis3416
@noelrappin Running the webpack dev server, I receive the following warning: ERROR in tsconfig.json TS18003: No inputs were found in c...
New
fynn
This is as much a suggestion as a question, as a note for others. Locally the SGP30 wasn’t available, so I ordered a SGP40. On page 53, ...
New
adamwoolhether
I’m not quite sure what’s going on here, but I’m unable to have to containers successfully complete the Readiness/Liveness checks. I’m im...
New
Charles
In general, the book isn’t yet updated for Phoenix version 1.6. On page 18 of the book, the authors indicate that an auto generated of ro...
New
brunogirin
When installing Cards as an editable package, I get the following error: ERROR: File “setup.py” not found. Directory cannot be installe...
New
brunogirin
When running tox for the first time, I got the following error: ERROR: InterpreterNotFound: python3.10 I realised that I was running ...
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
a.zampa
@mfazio23 I’m following the indications of the book and arriver ad chapter 10, but the app cannot be compiled due to an error in the Bas...
New

Other popular topics Top

PragmaticBookshelf
Machine learning can be intimidating, with its reliance on math and algorithms that most programmers don't encounter in their regular wor...
New
AstonJ
Continuing the discussion from Thinking about learning Crystal, let’s discuss - I was wondering which languages don’t GC - maybe we can c...
New
AstonJ
Was just curious to see if any were around, found this one: I got 51/100: Not sure if it was meant to buy I am sure at times the b...
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
First poster: AstonJ
Jan | Rethink the Computer. Jan turns your computer into an AI machine by running LLMs locally on your computer. It’s a privacy-focus, l...
New
sir.laksmana_wenk
I’m able to do the “artistic” part of game-development; character designing/modeling, music, environment modeling, etc. However, I don’t...
New
PragmaticBookshelf
Get the comprehensive, insider information you need for Rails 8 with the new edition of this award-winning classic. Sam Ruby @rubys ...
New
AstonJ
This is a very quick guide, you just need to: Download LM Studio: https://lmstudio.ai/ Click on search Type DeepSeek, then select the o...
New
NewsBot
Node.js v22.14.0 has been released. Link: Release 2025-02-11, Version 22.14.0 'Jod' (LTS), @aduh95 · nodejs/node · GitHub
New
PragmaticBookshelf
Quickly turn complex problems into simple, working solutions using functional programming, safe concurrency, and the expressive tools of ...
New

Latest in Functional Programming in Java, Second Edition

Functional Programming in Java, Second Edition Portal

Sub Categories: