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

iPaul
page 37 ANTLRInputStream input = new ANTLRInputStream(is); as of ANTLR 4 .8 should be: CharStream stream = CharStreams.fromStream(i...
New
telemachus
Python Testing With Pytest - Chapter 2, warnings for “unregistered custom marks” While running the smoke tests in Chapter 2, I get these...
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
HarryDeveloper
Hi @venkats, It has been mentioned in the description of ‘Supervisory Job’ title that 2 things as mentioned below result in the same eff...
New
joepstender
The generated iex result below should list products instead of product for the metadata. (page 67) iex&gt; product = %Product{} %Pento....
New
taguniversalmachine
It seems the second code snippet is missing the code to set the current_user: current_user: Accounts.get_user_by_session_token(session["...
New
kolossal
Hi, I need some help, I’m new to rust and was learning through your book. but I got stuck at the last stage of distribution. Whenever I t...
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
bjnord
Hello @herbert ! Trying to get the very first “Hello, Bracket Terminal!" example to run (p. 53). I develop on an Amazon EC2 instance runn...
New
redconfetti
Docker-Machine became part of the Docker Toolbox, which was deprecated in 2020, long after Docker Desktop supported Docker Engine nativel...
New

Other popular topics Top

Devtalk
Reading something? Working on something? Planning something? Changing jobs even!? If you’re up for sharing, please let us know what you’...
1045 20892 392
New
AstonJ
A thread that every forum needs! Simply post a link to a track on YouTube (or SoundCloud or Vimeo amongst others!) on a separate line an...
New
wolf4earth
@AstonJ prompted me to open this topic after I mentioned in the lockdown thread how I started to do a lot more for my fitness. https://f...
New
Rainer
My first contact with Erlang was about 2 years ago when I used RabbitMQ, which is written in Erlang, for my job. This made me curious and...
New
New
AstonJ
In case anyone else is wondering why Ruby 3 doesn’t show when you do asdf list-all ruby :man_facepalming: do this first: asdf plugin-upd...
New
New
AstonJ
If you want a quick and easy way to block any website on your Mac using Little Snitch simply… File &gt; New Rule: And select Deny, O...
New
PragmaticBookshelf
Fight complexity and reclaim the original spirit of agility by learning to simplify how you develop software. The result: a more humane a...
New
PragmaticBookshelf
A concise guide to MySQL 9 database administration, covering fundamental concepts, techniques, and best practices. Neil Smyth MySQL...
New

Latest in Functional Programming in Java, Second Edition

Functional Programming in Java, Second Edition Portal

Sub Categories: