Functional programming is infectious

You learn about clever purely-functional abstractions and you think, neat, but not really relevant to everyday programming; then the next thing you know, you’re implementing a fairly boring bit of business logic and you think, dammit, what I really need here are profunctor optics.

Mainstream programming languages now generally include some support for functional idioms: first-class functions, map, maybe fold. The idea seems to be to introduce functional features to be used when they are convenient, but to avoid the more complicated functional abstractions. This usually turns out to mean writing functional code mostly at a small scale. For instance, this is nice to see in a Java code base:

var revenue = orders.stream()
    .mapToInt(Order::getPrice)
    .sum()

but I’d be a bit more dubious about something like:

var discountedOrders = orders.stream()
    .filter(order -> {
        var discount = discountRepository.find(order.getDiscount());
        return discountValidator.discountIsValid(discount, order);
    }).map(order -> {
        var discount = discountRepository.find(order.getDiscount());
        return order.toBuilder()
            .setPrice(discount.calculateDiscountedPrice())
            .build();
    }).collect(Collectors.toList());

When either the pipeline or the elements get more complicated, functional programming constructs in Java can become uwieldy. Clearly there are ways to improve the code above (leaving aside the fact that it’s kind of a weird example because I thought it up in two minutes), but it might well be the clearer to refactor to a non-functional style

for(Order order: orders) {
    var discount = discountRepository.find(order.getDiscount());
    
    if (!discountValidator.discountIsValid(discount, order)) {
        continue;
    }

    order.setPrice(discount.calculateDiscountedPrice());
}

But if you initially wrote a nice short functional version, to which you gradually add complexity, it’s not necessarily obvious at what point to refactor to an imperative style, and by the time it’s obvious it can be quite a big change (especially if you go the whole way from immutability to mutability, as in the example above, although to be fair you probably shouldn’t do that).

The thing is, functional programming is infectious, in the sense that once you begin to write in a functional style, it’s generally convenient to continue doing so, and that’s when the more complicated functional abstractions become useful. Case in point: I have struggled to understand traversable like many others; but when I eventually figured it out (with the help of that linked blog post), I began to see uses for it all over the place. I was recently writing something which used a fairly straightforward result type (Result<T, E>, which can contain either the value of a successful operation, or information about an error that occurred), and, unsurprisingly, I had to do some bulk operations and ended up with a list of these results; just the job for traversables, but unfortunately there is no traverse in the Java standard library.

So my initial thought was to write a traverse function for lists and results, (or, actually, sequence, which is the related fuction you need when you already have a list of results; you would use traverse when you have a list and a function that returns a result). But then it occurred to me that there’s already a Java idiom to specify how to put together a collection: Collector. So I came up with a Flatteners class (I thought that was a more obvious name than “sequencer” if you’re not familiar with its traversable origin) that provides Collectors for Results, and allows you to write things like:

 // Produces Ok([1, 2, 3])
Stream.of(Result.ok(1), Result.ok(2), Result.ok(3))
        .collect(Flatteners.result());

// Produces Error(2)
Stream.of(Result.ok(1), Result.error(2), Result.error(3))
        .collect(Flatteners.result());

This isn’t actually an implementation of traversable. The key feature of traversable is that it is structure-preserving. This is a sufficiently obvious property with lists that it’s easy not to notice - you would naturally expect that sequencing a list would give you the result back in the same order as the list. A full implementation of traversable would also work with trees or any other data structure, and preserve their structure as well. Collectors work on Streams which have at most a linear structure (they might have no inherent structure at all), so you can’t use them to preserve a structure more complicated than a list (although my Flatteners implementation does let you specify a further collector so you can specify a Set or something else instead of a List).

But I think the flattening collector is still reasonably useful, despite not being a full traversable implementation. This did get me thinking about how far one can venture into the world of functional abstraction in Java while still staying reasonably idiomatic, so I started a little library where I’ll put some Java experiments along these lines.