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

jimmykiang
This test is broken right out of the box… — FAIL: TestAgent (7.82s) agent_test.go:77: Error Trace: agent_test.go:77 agent_test.go:...
New
herminiotorres
Hi @Margaret , On page VII the book tells us the example and snippets will be all using Elixir version 1.11 But on page 3 almost the en...
New
AleksandrKudashkin
On the page xv there is an instruction to run bin/setup from the main folder. I downloaded the source code today (12/03/21) and can’t see...
New
rmurray10127
Title: Intuitive Python: docker run… denied error (page 2) Attempted to run the docker command in both CLI and Powershell PS C:\Users\r...
New
jskubick
I think I might have found a problem involving SwitchCompat, thumbTint, and trackTint. As entered, the SwitchCompat changes color to hol...
New
digitalbias
Title: Build a Weather Station with Elixir and Nerves: Problem connecting to Postgres with Grafana on (page 64) If you follow the defau...
New
akraut
The markup used to display the uploaded image results in a Phoenix.LiveView.HTMLTokenizer.ParseError error. lib/pento_web/live/product_l...
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
rainforest
Hi, I’ve got a question about the implementation of PubSub when using a Phoenix.Socket.Transport behaviour rather than channels. Before ...
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

Other popular topics Top

New
dasdom
No chair. I have a standing desk. This post was split into a dedicated thread from our thread about chairs :slight_smile:
New
Exadra37
I am asking for any distro that only has the bare-bones to be able to get a shell in the server and then just install the packages as we ...
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
New
New
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
AstonJ
Curious what kind of results others are getting, I think actually prefer the 7B model to the 32B model, not only is it faster but the qua...
New

Latest in Functional Programming in Java, Second Edition

Functional Programming in Java, Second Edition Portal

Sub Categories: