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

jimschubert
In Chapter 3, the source for index introduces Config on page 31, followed by more code including tests; Config isn’t introduced until pag...
New
JohnS
I can’t setup the Rails source code. This happens in a working directory containing multiple (postgres) Rails apps. With: ruby-3.0.0 s...
New
herminiotorres
Hi! I know not the intentions behind this narrative when called, on page XI: mount() |&gt; handle_event() |&gt; render() but the correc...
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
brian-m-ops
#book-python-testing-with-pytest-second-edition Hi. Thanks for writing the book. I am just learning so this might just of been an issue ...
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 trying to run tox in parallel as explained on page 151, I got the following error: tox: error: argument -p/–parallel: expected one...
New
New
AufHe
I’m a newbie to Rails 7 and have hit an issue with the bin/Dev script mentioned on pages 112-113. Iteration A1 - Seeing the list of prod...
New
New

Other popular topics Top

PragmaticBookshelf
Take your Go skills to the next level by learning how to design, develop, and deploy a distributed service. Start from the bare essential...
New
PragmaticBookshelf
Write Elixir tests that you can be proud of. Dive into Elixir’s test philosophy and gain mastery over the terminology and concepts that u...
New
siddhant3030
I’m thinking of buying a monitor that I can rotate to use as a vertical monitor? Also, I want to know if someone is using it for program...
New
AstonJ
Thanks to @foxtrottwist’s and @Tomas’s posts in this thread: Poll: Which code editor do you use? I bought Onivim! :nerd_face: https://on...
New
PragmaticBookshelf
Tailwind CSS is an exciting new CSS framework that allows you to design your site by composing simple utility classes to create complex e...
New
Exadra37
Oh just spent so much time on this to discover now that RancherOS is in end of life but Rancher is refusing to mark the Github repo as su...
New
PragmaticBookshelf
Learn different ways of writing concurrent code in Elixir and increase your application's performance, without sacrificing scalability or...
New
PragmaticBookshelf
Use WebRTC to build web applications that stream media and data in real time directly from one user to another, all in the browser. ...
New
mafinar
This is going to be a long an frequently posted thread. While talking to a friend of mine who has taken data structure and algorithm cou...
New
AstonJ
If you get Can't find emacs in your PATH when trying to install Doom Emacs on your Mac you… just… need to install Emacs first! :lol: bre...
New

Latest in Functional Programming in Java, Second Edition

Functional Programming in Java, Second Edition Portal

Sub Categories: