Partial method evaluation

91 views
Skip to first unread message

Philipp Krueger

unread,
Nov 15, 2024, 12:48:54 PM11/15/24
to Project Lombok
Hi,

I have a proposal for a new feature:
I would like to have an annotation @PartialEvaluation for methods with multiple arguments.
An annotated method gets an overloaded version expecting all but one of the original input arguments. The result of the generated method is a lambda expression that expects the last of the input arguments and finally executes the original method with the full set of arguments.

Example

public class Example {
  @PartialEvaluation(variable = "str")
  public String addPrefix(String prefix, String str) {
    return prefix + str;
  }

  @PartialEvaluation(variable = "someText")
  public boolean isTooLong(String someText, int maxLength) {
    return someText.length() > maxLength;
  }
}

Generated Code

public class Example {
  public String addPrefix(String prefix, String str) {
    return prefix + str;
  }

  public boolean isTooLong(String someText, int maxLength) {
    return someText.length() > maxLength;
  }

  // Generated
  public Function<String, String> addPrefix(String prefix) {
    return (str) -> this.addPrefix(prefix, str);
  }

  public Predicate<String> isTooLong(int maxLength) {
    return (someText) -> this.isTooLong(someText, maxLength);
  }
}

Benefit
It can be applied for example in Streams:

List<String> values = List.of("abcd", "12345678", "Some important text");

// now
List<String> resultList = values.stream()
  .filter(value -> isTooLong(value, 5))
  .map(value -> addPrefix("prefix_", value))
  .toList();

// with @PartialEvaluation
List<String> resultList = values.stream()
  .filter(isTooLong(5))
  .map(addPrefix("prefix_"))
  .toList();

What du you think about my idea?

Best regards,
Philipp

Mat Jaggard

unread,
Nov 15, 2024, 6:19:35 PM11/15/24
to Project Lombok
Hi Philipp,
Just my two cents - I think this is a great addition to the language. Many languages already have Currying as a concept and Java is no exception but it takes a lot of typing to create Curried functions. I assume that the result would be a chain of functions if there are more than two arguments? I'm also not sure about the name @PartialEvaluation because whether it is partial or not depends on the caller, not the callee where the annotation has to be added. However the term "Curry" is not used much outside of computer science and mathematics.


Code:
@Curry
public String threeArgFunc(String one, int two, float three) {
...
}

Generated:
public Function<String, Function<Integer, Function<Float, String>>> threeArgFunc(String one) {
  return two -> three -> threeArgFunc(one, two, three);
}

I'm not sure how many arguments would be too many for this to work nicely.

Lastly, I've probably wanted this feature rarely enough that I've been happy implementing it myself but most of my code is not often functional in nature and so I could definitely see some types of code benefiting from this significantly.

Many thanks,
Mat.

Roger Wernersson

unread,
Nov 18, 2024, 3:51:42 AM11/18/24
to Project Lombok
This sounds like a really nice feature. I second this!

Mat Jaggard

unread,
Nov 19, 2024, 5:15:22 AM11/19/24
to Project Lombok
People will likely ask for a different order of function application, but that should be handled by duplicating to overload the hand-defined methods.

@Curry
String bothOrders(String name, Date startDate) {
   // real implementation here
}

@Curry
String bothOrders(Date startDate, String name) {
   return bothOrders(name, startDate);
}

Although that does take us to an edge case to consider. If you have two methods like this:
String myFunction(String name, int milliseconds)
String myFunction(String name, Date startDate)
then there would be a conflict between the generated functions - because the return type of the first method for each would be different but the method would take a single String argument.

Linnea Gräf

unread,
Nov 19, 2024, 4:20:40 PM11/19/24
to project...@googlegroups.com
Another thing you could do is to use a helper method to permute the arguments like so:


class Test {
  @Curry boolean validate(String string, int max_length) {
    return name.length()<=max_length;
  }
  // Generated:
  Function<String, Function<Integer, Boolean>> validate = string -> max_length -> validate(string, max_length);

  // Not Generated:
  Function<Integer, Function<String, Boolean>> validateFlipped = flip(validate);
  Function<String, Boolean> atMost4 = validateFlipped.apply(4);

  // Helper, somewhere else:
  static void<T1, T2, R> Function<T2, Function <T1, R>> flip(Function<T1, Function<T2, R>> function) {
    return a -> b -> function.apply(b).apply(a);
  }
}

It is usually more common to use these helpers on the fly however:

Function<String, Boolean> atMost4 = flip(validate).apply(4);
Reply all
Reply to author
Forward
0 new messages