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
December 20, 2023 Simple Design, Refactoring, Microtechniques
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.
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?
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. 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!