Skip to content

Utilize Java functional interfaces to improve signal and callable code from Java and other JVM languages #754

@aaronjyoder

Description

@aaronjyoder

Right now, the callable interface is generated in a way that makes it very difficult to use in Java.

The Java example given here is incorrect, but besides that it could also be much simplier if using Java's functional interface feature, introduced in Java 8.

Functional Interfaces

Java has 5 baseline built-in functional interfaces:

Runnable: Takes in nothing, returns nothing. A function from Void -> Void.
Consumer<T>: Takes in a value T, returns nothing. A function from T -> Void.
Predicate<T>: Takes in a value T, returns a boolean. A function from T -> Boolean.
Function<T, R>: Takes in a value T, returns a value R. A function from T -> R.
Supplier<R>: Takes in nothing, returns a value R. A function from Void -> R.

There are also BiConsumer, BiPredicate, and BiFunction, which take in two values. There are also a few other handy built-in functional interfaces. You can find more details here.

You can also create your own functional interfaces using the @FunctionalInterface annotation, like so:

@FunctionalInterface
interface Logger<T> {
    void log(T message);
}

A functional interface can only have one abstract method. The above functional interface is a function from T -> void.

You would utilize it like so:

Logger<String> logger = message -> { // Creating the functional definition
	String timestamp = LocalDateTime.now().toString();
	System.out.println("[" + timestamp + "] ERROR: " + message);
};

// logger = System.out::println; // Can reference a method to pass the data directly to as well.

logger.log("An error occurred while processing the request."); // Using it

You can also create functional interfaces that take in multiple parameters (however many you like):

@FunctionalInterface
interface Calculator {
	int calculate(int a, int b);
}

Utilizing it looks like:

Calculator calculator = (a, b) -> a + b; // You can do this
calculator = Integer::sum; // Or this
calculator = (a, b) -> a * b; // Or whatever else you like!

Outcome

Currently, the example linked above looks like this in Kotlin:

    val regularCallable = NativeCallable(myObject, MyObject::myMethod)
    val customCallable = callable1<String> { println(it) }

and it looks like this in Java:

    NativeCallable regularCallable = Callable.create(myObject, StringNames.toGodotName("myMethod"));
    LambdaCallable1<String, Void> customCallable = LambdaCallable1.create(
            Void.class,
            String.class,
            (string) -> {
                System.out.println(string);
                return "";
            }
    );

I am not entirely sure if this even functions correctly, but this is what's required to get it to compile.

Using functional interfaces (and other modern Java features), the Java could instead look like:

final var regularCallable = Callable.create(myObject, MyObject::myMethod);
final var customCallable = LambdaCallable1<String>.create(System.out::println);

Or, maybe a more useful example:

final var regularCallable = Callable.create(myObject, MyObject::myMethod);
final var customCallable = LambdaCallable1<String>.create(string -> {
	System.out.println(string);
	// Do something else if you like
});

This is just one example of potentially many more. The Java code can look almost identical to the Kotlin code if functional interfaces are utilized. This does require a little bit more code on the project side in order to support it (you have to specify what functional interfaces are being used, whereas in Kotlin-land it's all inferred), but the benefit is that it would allow similar usage of lambdas across the entire JVM ecosystem. For the end-user, lambdas would function identically in Kotlin as they do now, AND Java/Scala users would be able to use callables properly.

Other places this approach can be used:

  • Mutating vectors or other built-in Godot value types, just like can be done in Kotlin as shown here.
  • Doing the same as above with collection types as shown here
  • Notifications

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions