Computational Expressions in Java

F# is an excellent language for writing type-safe, statically typed code. One of its best features is computational expressions: a syntactic sugar that makes code easy to read and error handling effortless. But what if you could bring that same magic to Java? While Java doesn’t have computational expressions built in, the same principles can simplify your code and make it safer, prettier, and almost as fun as F#

Result Expression in F#

The Result type can either be Ok of type T or Error of type E. It is defined as:

type Result<'T,'E> = 
    | Ok of 'T
    | Error of 'E

It represents whether a computation succeeds or fails, and wraps values of type ‘T (on success) or ‘E (on error).

With a computational expression, you can use Result like this:

// A divide function that emits Error if the denominator is 0
let divide x y = if y = 0 then Error "Divide by zero" else Ok (x / y)

// A computation expression of result
let computation =
    result {                     // result-expression inside {}
        let! a = divide 10 2     // Ok 5, a is 5
        let! b = divide a 3      // Ok 1, b is 5/3=1
        let! c = divide b 0      // Error "Divide by zero"
        return a + b + c         // Not reached if any step fails. 
    }

match computation with
| Ok v    -> printfn "Success: %d" v
| Error e -> printfn "Failure: %s" e

The nice thing with this construct is that the result of each step is available, so that we can sum it all up in the end: a + b + c. This might feel obvious, but it’s surprisingly rare in mainstream languages. The computation short-circuits as soon as there’s an Error, for example when dividing by zero. Instead of blowing up or running into nested ifs, you get a graceful failure: Failure: Divide by zero gets printed, and the rest of the computation is skipped. The sum: return a + b + c will not occur, and neither would any other let! expressions after the division by zero, if they were there. Without the computational expression we would probably write something like the equal nested expression:

let computation =
   let aResult = divide 10 2
    match aResult with
    | Error e -> Error e
    | Ok a ->
        let bResult =  divide a 3
        match bResult with
        | Error e -> Error e
        | Ok b ->
            let cResult = divide b 0
            match cResult with
            | Error e -> Error e
            | Ok c -> Ok (a + b + c)
match computation with
| Ok v    -> printfn "Success: %d" v
| Error e -> printfn "Failure: %s" e

It’s harder to get an overview, even though the code does the same thing, as the result expression above. Each error check is explicit, but you lose the flow. It’s is easy to understand how the combination of getting access to the result of each step, while simultaneously allowing graceful failures. The same path is used regardless of wheter we will sum a + b + c, or if there is an error that short circuits. The error is later taken care of in the pattern match.

Some claim that this type of syntax wrapped context is what makes F# such a great language. Computational Expressions can be very advanced and powerfull, making code very elegant. Still just handling errors with elegance is enough in real world scenarios. Some call it (railway oriented programming)[https://fsharpforfunandprofit.com/rop/].

And there is a pattern here. Computational expressions are not only for Results. It works for anything that is wrapped in a context. The functions map and bind are usually used when dealing with values wrapped in contexts. The computational expression is using bind. The above expression is translated to something like:

let aResult = divide 10 2
let bResult = Result.bind (fun a -> divide a 3) aResult
let cResult = Result.bind (fun b -> divide b 0) bResult

let computation =
    match cResult with
    | Ok c ->
        //Extract value from the Ok result using destructuring 
        let (Ok a) = aResult
        let (Ok b) = bResult
        Ok (a + b + c)
    | Error e -> Error e

The bind function exist with many types, not just Result. Here it is applied to the result with a anonymous function that takes the value of Success as argument, and returns either Success of a new value, or Error.

In the example from above, aResult is transformed into a new Result, bResult:

let bResult = Result.bind (fun a -> divide a 3) aResult

Here bind takes the anonymous function as first parameter and the source result as second. The function has the Success value, and diviedes it by 3. We wrote divide ourself, as a safe division that creates an Error when dividing by zero, or Success when division succeeds.

The expression could be written like, for more clarity:

let bResult = Result.bind (fun a -> if a = 0 
                                    then Error "Divide by zero" 
                                    else Ok (a / 3)) 

Result.bind wont call the function when there is Error, and instead just propagate the Error as is.

Result expression in Java

Java dont have Computational Expressions, or do notation like the similar thing is called in Haskell. But the same structure can be created in Java, using the Result type in previous blog Java without exceptions:

// A division that cretes Failure when denominator is zero
Result<Integer, String> divide ( int a, int b){
    return b == 0
        ? Result.failure("Division by zero");
        : Result.success(a / b);
}
// A computational expression of Result
var result =
    ResultExpression.of(divide(10, 2))
        .bind(a -> divide (a, 3))
        .bind((a, b) -> divide(b, 0))
        .yield((a, b, c) -> a + b + c);
print( switch (result) {
         case Success(var a) -> "Success " + a;
         case Failure(var e) -> "Failure " + e;
       });

Compared to the F# version, there is not much difference. In F# the let! means take out the value from the monad structure, which we do explicitly here with bind. Java does not have the computational expression in the language, so bind for a ResultExpression simply grows in each step, and provides the values for each step. Bind for first step has the first value, Second step has first and second, and third has 3 of them and so on. The sibling function yield does not accumulate the steps, but returns to a simple Result, giving you access to all three values at once. Yield is like return in the F# computational expression.

If something goes wrong, like the divide by zero on the second bind, then the result will be short circuited, and end up as Failure in the pattern match.

The forwarding of the values from each step allows for gracefull degradation under the hood. This is prettier with the language support in F#, as the values aren’t listed in each step, but it is as effective for code complexity in Java.

map

Some operations won’t fail. The ResultExpression provides the map function that puts the calculated value into Success for you, to be used instead of bind, when success is the only path

Other aritmetic operations are safe, so use map for those:

var result =
    ResultExpression.ofValue(5)
        .map(a -> a * 3)
        .bind((a, b) -> divide(b, 0))
        .yield((a, b, c) -> a + b + c);
print( switch (result) {
         case Success(var a) -> "Success " + a;
         case Failure(var e) -> "Failure " + e;
       });

The ResultExpression actually use many types, Steps, to mimic the language construct. It can be written as:

ResultExpression.Step1<Integer, String> x1 = ResultExpression.ofValue(5);
ResultExpression.Step2<Integer, Integer, String> x2 = x1.map(a -> a * 3);
ResultExpression.Step3<Integer, Integer, Integer, String> x3 = x2.bind((a,b) -> divide( b, 0));
Result<Integer, String> r = x3.yield((a, b, c) -> a+b+c);
print( switch (r) {
         case Success(Integer a) -> "Success " + a;
         case Failure(String e) -> "Failure " + e;
       });

This is arguably easier to follow step-by-step, but much more verbose.

As we can see, each step provides its own type parameters. Using different types in each step is straightforward.

 var result = 
     ResultExpression.ofValue(21)
         .map(a -> "string " + a)
         .map((a, b) -> String.format( "0x%x as %s", a ,b))
         .bind((a, b, c) -> divide (a, (double) b.length()))
         .yield((a, b, c, d) -> String.format("a=%d,b=%s,c=%s,d=%.2f and product is %.2f", a, b, c, d, a*d));
print( switch (result) {
          case Success(var a) -> a;
          case Failure(var e) -> "Failed " + e;
       });

Here, a is an integer, d is double, while b and c are strings. The idea is however to let each position represent the same value, in each step.

Handling exceptions

As we can see above, we can gracefully take care of exception using Result functions, like in divide. We can also use trial from Result, which wraps any exceptions in Failure, up front, especially handy for those scary divisions!

var result =
    ResultExpression.of(trial(() -> 10/2))
        .map(a -> a*3)
        .bind((a, b) -> trial(() -> b/0))
        .yield((a, b, c) -> a + b + c);
print( switch (result) {
    case Success(var a) -> a;
    case Failure(ArithmeticException e) -> "Math problems:" + e.getMessage();
    case Failure(var e) -> "Some other failure:" + e.getMessage();
});

Here, trial simply runs the lambda in a try/catch block, converting any exceptions into Failure. It catches early and prevents extra code paths induced by exceptions, preventing code complexity.

Sure, showing off these techniques with a simple calculation isn’t really fair. Computational expressions are tools for code that would otherwise become messy—especially in real-world scenarios, when things get gnarly fast.

Optional expression in Java

Computational expressions works for Optionals as well. It’s a neat way to deal with many Optionals, which otherwise easily becomes complex.

 print(OptionalExpression.of(getUserId(loginName))
        .bind(userId -> getEmail(userId))
        .bind((userId, email) -> getAvatarUrl(userId))
        .yield((userId, email, avatarUrl) ->
                String.format("User: %s, Email: %s, Avatar: %s", userId, email, avatarUrl))
        .orElse("User info incomplete!"));                

The equivalent without the OptionalExpression would be:

print(getUserId(loginName)
        .flatMap(userId ->
            getEmail(userId)
                .flatMap(email ->
                    getAvatarUrl(userId)
                        .map(avatarUrl ->
                            String.format("User: %s, Email: %s, Avatar: %s", userId, email, avatarUrl)
                        )
                )
        )
        .orElse("User info incomplete!"));

Summary

So, whether you prefer F#, Java, or just want to avoid waking up in a cold sweat over nested try/catch blocks, computational expressions can make your code safer, flatter, and a lot more readable.

This demonstrates that structure can be improved with computational expressions. Here we are merely toying around with simple calculation. Computational Expressions shines when it prevent complexity from emerging in real world programs. And it can be done in Java by simply forwarding all values through the complexities that the context manages. Code nesting disapears.

The Result.java and OptionalExpression.java are provided as stand alone files, and are not dependednt on any third party. Code that manage structure, is code that you need to foster and adapt in your environment. You can’t do that when they belong to libraries that you dont own. Use it in your project and adapt it as you need. Library code is someone else’s garden. Your error handling deserves homegrown tomatoes.

I have done a few static imports and implemented a few functions that has not been shown:

The code for the Computational Expression in F# is simply:

type ResultBuilder() =
    member _.Bind(m, f) =
        match m with
        | Ok v    -> f v
        | Error e -> Error e

    member _.Return(v) = Ok v
    member _.ReturnFrom(m) = m

let result = ResultBuilder()

The Java source for print is simply:

void print(Object o){
    System.out.println(o);
}

We have made static import of the members in Result.

There are no implementation of the: getUserId, getEmail and getAvatarUrl. They are simply functions returning Optionals.

Java might not let you write poetry, but with computational expressions, at least your error handling can rhyme.


All #art #clojure #csharp #data-structures #database #datomic #emacs #fortran #fsharp #functional #haskell #history #immutability #java #jit #jmm #lambdas #lisp #pioneers #poetry #programming #programming-philosophy #randomness #rant #reducers #repl #smalltalk #sql #threads #women