10

Java 8 added the concept of functional interfaces, as well as numerous new methods that are designed to take functional interfaces. Instances of these interfaces can be succinctly created using method reference expressions (e.g. SomeClass::someMethod) and lambda expressions (e.g. (x, y) -> x + y).

A colleague and I have differing opinions on when it is best to use one form or another (where "best" in this case really boils down to "most readable" and "most in line with standard practices", as they're basically otherwise equivalent). Specifically, this involves the case where the following are all true:

  • the function in question isn't used outside a single scope
  • giving the instance a name helps readability (as opposed to e.g. the logic being simple enough to see what's happening at a glance)
  • there aren't other programming reasons why one form would be preferable to the other.

My current opinion on the matter is that adding a private method, and referring that by method reference, is the better approach. It feels like this is how the feature was designed to be used, and it seems easier to communicate what is going on via method names and signatures (e.g. "boolean isResultInFuture(Result result)" clearly is saying it's returning a boolean). It also makes the private method more reusable if a future enhancement to the class wants to make use of the same check, but doesn't need the functional interface wrapper.

My colleague's preference is to have a method which returns the instance of the interface (e.g. "Predicate resultInFuture()"). To me, this feels like it's not quite how the feature was intended to be used, feels slightly clunkier, and seems like it's harder to really communicate intent through naming.

To make this example concrete, here is the same code, written in the different styles:

public class ResultProcessor {
  public void doSomethingImportant(List<Result> results) {
    results.filter(this::isResultInFuture).forEach({ result ->
      // Do something important with each future result line
    });
  }

  private boolean isResultInFuture(Result result) {
    someOtherService.getResultDateFromDatabase(result).after(new Date());
  }
}

vs.

public class ResultProcessor {
  public void doSomethingImportant(List<Result> results) {
    results.filter(resultInFuture()).forEach({ result ->
      // Do something important with each future result line
    });
  }

  private Predicate<Result> resultInFuture() {
    return result -> someOtherService.getResultDateFromDatabase(result).after(new Date());
  }
}

vs.

public class ResultProcessor {
  public void doSomethingImportant(List<Result> results) {
    Predicate<Result> resultInFuture = result -> someOtherService.getResultDateFromDatabase(result).after(new Date());

    results.filter(resultInFuture).forEach({ result ->
      // Do something important with each future result line
    });
  }
}

Are there any official or semi-official documentation or comments around whether one approach is more preferred, more in-line with the language designers' intents, or more readable? Barring an official source, are there any clear reasons why one would be the better approach?

  • 1
    Lambdas were added to the language for a reason, and it was not to make Java worse. – user22815 Nov 3 '16 at 20:06
  • @Snowman That goes without saying. I therefore assume there's some implied relation between your comment and my question that I'm not quite catching. What is the point you're trying to make? – M. Justin Nov 3 '16 at 20:09
  • 2
    I assume that the point he is making is that, had lambdas not been an improvement to the status quo, they would not have bothered to introduce them. – Robert Harvey Nov 3 '16 at 20:16
  • 2
    @RobertHarvey Sure. To that point, however, both lambdas and method references were added at the same time, so neither were status quo before. Even without this particular case, there are times when method references would still be better; for instance, an existing public method (e.g. Object::toString). So my question is more about whether it's better in this particular type of instance that I'm laying out here, than whether there exist instances where one is better than the other, or vice versa. – M. Justin Nov 3 '16 at 20:19
  • 1
8

In terms of functional programming, what you and your colleague are discussing is point free style, more specifically eta reduction. Consider the following two assignments:

Predicate<Result> pred = result -> this.isResultInFuture(result);
Predicate<Result> pred = this::isResultInFuture;

These are operationally equivalent, and the first is called pointful style while the second is point free style. The term "point" refers to a named function argument (this comes from topology) which is missing in the latter case.

More generally, for all functions F, a wrapper lambda that takes all arguments given and passes them to F unchanged, then returns the result of F unchanged is identical to F itself. Removing the lambda and using F directly (going from the pointful style to the point-free style above) is called an eta reduction, a term that stems from lambda calculus.

There are point free expressions that are not created by eta reduction, the most classic example of which is function composition. Given a function from type A to type B and a function from type B to type C, we can compose them together into a function from type A to type C:

public static Function<A, C> compose(Function<A, B> first, Function<B, C> second) {
  return (value) -> second(first(value));
}

Now suppose we have a method Result deriveResult(Foo foo). Since a Predicate is a Function, we can construct a new predicate that first calls deriveResult and then tests the derived result:

Predicate<Foo> isFoosResultInFuture =
    compose(this::deriveResult, this::isResultInFuture);

Although the implementation of compose uses pointful style with lambdas, the use of compose to define isFoosResultInFuture is point free because no arguments need to be mentioned.

Point free programming is also called tacit programming, and it can be a powerful tool to increase readability by removing pointless (pun intended) details from a function definition. Java 8 doesn't support point free programming nearly as thoroughly as more functional languages like Haskell do, but a good rule of thumb is to always perform eta-reduction. There's no need to use lambdas that have no different behavior from the functions they wrap.

  • 1
    I think what my colleague and I are discussing is more these two assignments: Predicate<Result> pred = this::isResultInFuture; and Predicate<Result> pred = resultInFuture(); where resultInFuture() returns a Predicate<Result>. So while this is a great explanation of when to use method reference vs. a lambda expression, it doesn't quite cover this third case. – M. Justin Nov 3 '16 at 20:36
  • 4
    @M.Justin: The resultInFuture(); assignment is identical to the lambda assignment I presented, except in my case your resultInFuture() method is inlined. So that approach has two layers of indirection (neither of which does anything) - the lambda-constructing method and the lambda itself. Even worse! – Jack Nov 3 '16 at 20:39
4

Neither of the current answers actually addresses the core of the question, which is whether the class should have a private boolean isResultInFuture(Result result) method or a private Predicate<Result> resultInFuture() method. While they are largely equivalent, there are advantages and disadvantages to each.

The first approach is more natural if you call the method directly in addition to creating a method reference. For example, if you unit test this method, it might be easier to use the first approach. Another advantage to the first approach is that the method doesn't have to care about how it's used, decoupling it from its call site. If you later change the class so that you call the method directly, this decoupling will pay off in a very small way.

The first approach is also superior if you might want to adapt this method to different functional interfaces or different unions of interfaces. For example, you might want the method reference to be of type Predicate<Result> & Serializable, which the second approach doesn't give you the flexibility to achieve.

On the other hand, the second approach is more natural if you intend to perform higher order operations on the predicate. Predicate has several methods on it that can't be called directly on a method reference. If you intend to use these methods, the second approach is superior.

Ultimately the decision is subjective. But if you don't intend to call methods on Predicate, I would lean toward preferring the first approach.

  • Higher order operations on the predicate are still available by casting the method reference: ((Predicate<Resutl>) this::isResultInFuture).whatever(...) – Jack Apr 17 '18 at 19:05
3

I don't write Java code anymore, but I write in a functional language for a living, and also support other teams who are learning functional programming. Lambdas have a delicate sweet spot of readability. The generally accepted style for them is you use them when you can inline them, but if you feel the need to assign it to a variable for use later, you should probably just define a method.

Yes, people assign lambdas to variables in tutorials all the time. Those are just tutorials to help give you a thorough understanding of how they work. They are not actually used that way in practice, except in relatively rare circumstances (like choosing between two lambdas using an if expression).

That means if your lambda is short enough to be easily readable after inlining, like the example from @JimmyJames' comment, then go for it:

people = sort(people, (a,b) -> b.age() - a.age());

Compare this with the readability when you try to inline your example lambda:

results.filter(result -> someOtherService.getResultDateFromDatabase(result).after(new Date()))...

For your particular example, I'd personally flag it on a pull request and ask you to pull it out to a named method because of its length. Java is an annoyingly verbose language, so perhaps in time its own language-specific practices will differ from other languages, but until then, keep your lambdas short and inline.

  • 1
    Re: verbosity - While the new functional additions don't, by themselves reduce much verbosity, they allow for ways to do so. For example you can define: public static final <T> List<T> sort(List<T> list, Comparator<T> comparator) with the obvious implementation and then you can write code like this: people = sort(people, (a,b) -> b.age() - a.age()); which is a big improvement IMO. – JimmyJames Nov 3 '16 at 20:58
  • That's a great example of what I was talking about, @JimmyJames. When lambdas are short and can be inlined, they are a great improvement. When they get so long that you want to separate them off and give them a name, you're better off just defining a method. Thanks for helping me understand how my answer was misconstrued. – Karl Bielefeldt Nov 3 '16 at 22:46
  • I think your answer was just fine. Not sure why it would be voted down. – JimmyJames Nov 4 '16 at 13:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.