A Guard Clause Is Maybe a Tiny Parser - The Code Whisperer

December 20, 2023 Simple Design, Refactoring, Microtechniques


This is a companion discussion topic for the original entry at https://blog.thecodewhisperer.com/permalink/a-guard-clause-is-maybe-a-tiny-parser

Thank you for this.

Once, I was alerted to the “perhaps your collected validation rules suggest a Policy class”, and extracted such a thing in RubyGems, to try it. (Oh, that was six years ago.)

By poking around the codebase for the longest class, I found it, and then recalled that microtechnique. Now, you’ve made me see guard clauses in that light.

1 Like

Inspiring post! The functional and value type stuff is new to me, but I think it’s pretty neat. What are your thoughts on switching over types like this?

Summary
static void onBarcode(String text) {
    var barcode = Barcode.parse(text);

    switch (barcode) {
        case ValidBarcode v -> handleValidBarcode(v);
        case EmptyBarcode ignored -> System.out.println("Barcode is empty");
    }
}

static void handleValidBarcode(ValidBarcode validBarcode) {
    System.out.printf("Valid barcode: %s%n", validBarcode.text());
}

sealed interface Barcode permits EmptyBarcode, ValidBarcode {
    static Barcode parse(String text) {
        return !text.isEmpty()
               ? new ValidBarcode(text)
               : EmptyBarcode.INSTANCE;
    }
}

record ValidBarcode(String text) implements Barcode { }

enum EmptyBarcode implements Barcode { INSTANCE }

Your Barcode type effectively reimplements a small part of Maybe using the “OO way” to design Sum/Union types. Your switch statement reimplements Maybe.map() (the Valid branch) and Maybe.withDefault() (the Empty branch). I’m reminded of the joke that every program eventually contains a partial and buggy implementation of Lisp. :slight_smile: Your code contains a partial (but not buggy!) implementation of Maybe.

I think some people intend that as an insult, but I don’t. Your code sample here seems clear to me, and already moving in the direction of Maybe, so it wouldn’t be difficult at all to apply the refactoring of renaming Barcode to Maybe and then renaming ValidBarcode to Barcode. Then parse() would return Maybe<Barcode> and the switch statement would be transformed into Barcode.parse(text).map(handleValidBarcode).orElse(() -> println("Barcode is empty")).

The only complication in Java (and specifically the Vavr library) is that map() wants a function and not a consumer (void function), but there are at least two ways to handle that problem.

Meantime, I really like the clarity of the pattern matching while we wait for a signal that we need heavier FP machinery.

Very nice!

1 Like