Quantcast
Channel: All – akquinet – Blog
Viewing all articles
Browse latest Browse all 133

No more ifs – an application of functional programming in Java

$
0
0

Java 8 introduced with Optional a functional datatype that enables the developer to work with optional values without nested if-statements. This can simplify your code a lot.

Consider this method to parse an argument.

Optional readFloatParameterUsingIfs(
                  String parameterName,
                  Map<String, Set<String>> parameters) {
  final Set values = parameters.get(parameterName);
  if (values == null) {
    return Optional.empty();
  }
  if (values.size() != 1) {
    return Optional.empty();
  }
  final String valueAsString = values.iterator().next();
  final float valueAsFloat;
  try {
     valueAsFloat = Float.parseFloat(valueAsString);
  } catch (NumberFormatException e) {
     return Optional.empty();
  }
  return Optional.of(valueAsFloat);
}

The code works, it is even readable if you are used to an imperative programming style. But, when the code changes, with all the if-branches and return-statements, it is likely that errors will sneak in. So, you need a good test coverage.
With the new Optional datatype in Java, you can code this method in a more functional way.

Optional readFloatParameterUsingOptionals(
                     String parameterName,
                     Map<String, Set<String>> parameters) {
  return
    Optional.ofNullable(parameters.get(parameterName))
            .filter(values -> values.size() == 1)
            .map(values -> values.iterator().next())
            .map(value -> {
               try { return Float.parseFloat(value);
               } catch (NumberFormatException e) {
                  return null;
               }});
    }

Instead of a sequence of if-statements the filter and map methods are combined in a fluent style, which encapsulate the handling of empty optionals. If a filter is applied to an optional and its value passes the test, the optional stays the same. If it does not or the optional is empty, it will be empty.
If a map is applied to an empty optional it stays empty. If it has a value, the value is mapped to a new value. If the new value is null, the result will be also empty.
Now there is no structure of if-branches that allows errors to sneak in. Still, the parsing of string to a float with the embedded try-catch structure is still noisy. The Option datatype of the framework Vavr provides a cleaner alternative.

Optional readFloatParameterUsingOption(
                    String parameterName,
                    Map<String, Set<String>> parameters) {
  return Option.of(parameters.get(parameterName))
               .filter(values -> values.size() == 1)
               .map(values -> values.iterator().next())
               .flatMap(Function1.lift(Float::parseFloat))
               .toJavaOptional();
}

The api of Option is pretty similar to Javas Optional. The last method toJavaOptional() converts the Vavr Option to a standard Javas Optional.
But, the clou is not the Option class. It is the lift() method in Function1. This method wraps the exectution of another function or method. If it returns a null or raises an exception, an empty Option value is returned. Otherwise the result value is put into the Option value. The combination with flatMap can now be used for an if-free concatenation of operations.
This approach works well if you have to handle optional values in your domain. In the example given above you might want to give the client of your method a hint why the parameter could not be resolved. In this case it is not a handling of an optional value but a validation and parsing of external data. Vavr offers a data type Validation that supports this approach.

Validation readFloatParameterUsingValidation(
                              String parameterName,
                              Map<String, Set<String>> parameters) {
  return Option.of(parameters.get(parameterName))
               .toValid("Parameter is not set")
               .flatMap(strings -> strings.size() == 1
                        ? Validation.valid(strings.iterator().next())
                        : Validation.invalid("Parameter is set several times"))
               .flatMap(s -> Try.of(() -> Float.parseFloat(s))
               .toValid("The parameter is not a valid String"));
}

A validation value is either valid and contains a value or is invalid and contains an error message. In our example we start with an Option. With toValid it is converted to a validation value. If the optional value is empty, it is transformed to an invalid value with the provided error message. Otherwise a valid value with the same value as the optional value is generated.
The first flatMap checks if the parameter is unique. According to the check either a valid or an invalid message is created.
The second flatMap converts the string to floating point number using the Try datatype. This datatype receives a computation and executes it. If an exception is raised, the result is a Failure containing the exception. Otherwise it is an Success containing the result value.
The final toValid method converts the Try to a validation value.
Again, we have a non trivial validation logic just by combining function calls without any error-prone nested if-statements. If you like to play with the code, you can find it here: https://github.com/akquinet/NoMoreIfs


Viewing all articles
Browse latest Browse all 133

Trending Articles