Function types, or method types as FCM refers to them,
are one of the most controversial features of closures.
Is there an alternative that provides 80% of the power but in the style of Java?
Function/Method types
Function types allow the developer to define a type using just the signature, rather than a name.
For example:
// BGGA
{String, Date => int}
// FCM v0.5
#(int(String, Date))
Apart from the different syntax, these are identical concepts in the two proposals as they currently stand.
They both mean "a type that takes in two parameters - String and Date - and returns an int".
This will be compiled to a dynamically generated single method interface as follows:
public interface IOO<A, B> {
int invoke(A a, B b);
}
When instantiated, the generics A and B will be String and Date.
This is a complicated underlying mechanism.
It is also one that can't be completely hidden from the developer, as the exception stack traces
will show the auto-generated interface "IOO".
This will certainly be a little unexpected at first.
Update: Neal points out correctly that an interface name will not appear in the stacktrace!
A second complaint about function types is that there is no home for documentation.
One of Java's key strengths is its documentation capabilities in the form of Javadoc.
This is perhaps the unsung reason as to why Java became so popular in enterprises as a long-life,
bet your company, language.
Maintenance coders love that documentation.
And everybody loves the ability to link to it within your IDE
So, why are we even considering function types?
Well they allow APIs to be written that can take in any closure, simply defining it in terms of the
input and output types.
They also allow lightweight definition - there is no need to define the type before using it.
These are highly powerful features, and they lead towards functional programming idioms.
But are these idioms completely in the style of Java?
Another option
Lets start from what we would write in Java today.
public interface Convertor {
int convert(String str, Date date);
}
The advantage of this is that everyone knows and understands it.
Its part of the lingua franca that is Java.
Now lets examine what we could do with this.
Firstly, we need to remember that it is possible to define a class or interface such
as this one within a method in Java today.
The scope of such a class is the scope of the method. This will come in useful later.
So, lets examine what would happen if we start shortening the interface definition.
For a function type equivalent, we know that there is only one method.
As such, there isn't really any need for the braces:
public interface Convertor int convert(String str, Date date);
Now, lets consider that for a function type equivalent, the method name is pre-defined as 'invoke'.
As such, there is no need to include the method name:
public interface Convertor int(String str, Date date);
Now, lets consider that for a function type equivalent, the parameter names are unimportant.
As such, lets remove them (or maybe make them optional):
public interface Convertor int(String, Date);
And that's it. I'm calling this a lightweight interface for now.
They represent a reasonable reduction of the code necessary to define a named single method interface.
The syntax would be allowed anywhere an existing interface could be defined.
This includes its own source file, nested in another class or interface, or locally scoped within a method.
This is a longer example:
// with function types (FCM syntax)
public int process() {
#(int(int, int)) add = #(int a, int b) {return a + b;};
#(int(int, int)) mul = #(int a, int b) {return a * b;};
return mul.invoke(add.invoke(2, 3), add.invoke(3, 4));
}
// with named lightweight interfaces - solution A
interface Adder int(int,int);
interface Multiplier int(int,int);
public int process() {
Adder add = #(int a, int b) {return a + b;};
Multiplier mul = #(int a, int b) {return a * b;};
return mul.invoke(add.invoke(2, 3), add.invoke(3, 4));
}
// with named lightweight interfaces - solution B
public int process() {
interface MathsCombiner int(int,int);
MathsCombiner add = #(int a, int b) {return a + b;};
MathsCombiner mul = #(int a, int b) {return a * b;};
return mul.invoke(add.invoke(2, 3), add.invoke(3, 4));
}
Solution A shows how you might define one lightweight interface for each operation.
Solution B shows how you might define just one lightweight interface.
It also shows that the lightweight interface could be define locally within the same method.
What we have gained is a name for the function type.
It is now possible to write Javadoc for it and hyperlink to it in your IDE.
And it can be quickly and easily grasped as simply a shorthand way of defining a single method interface.
In fact, you would be able to use this anywhere in your code as a normal single method interface,
implementing it using a normal class, or extending it as required.
Its also possible to imagine IDE refactorings that would convert a lightweight interface to a full interface
if you needed to add additional methods. Or to convert a single method interface to the lightweight definition.
Of course it would be possible to take this further by eliminating the name:
// example showing what is possible, I'm not advocating this!
public int process() {
interface int(int,int) add = #(int a, int b) {return a + b;};
interface int(int,int) mul = #(int a, int b) {return a * b;};
return mul.invoke(add.invoke(2, 3), add.invoke(3, 4));
}
However, the developer must now mentally parse both the lines "interface int(int,int)" to see if they are
the same type. Previously, they could just see that they were both "MathsCombiner".
As such, I prefer keeping the name, and requiring developers to take the extra step.
I see this as an example of where the style of Java differs from other more dynamic languages.
In Java you always define your types up front.
In more dynamic languages, you often just code the closure.
As this concept requires defining types up front, I might suggest it is more in the Java style.
Final example
One final example is from my last blog post, this time in BGGA syntax:
// Example functional programming style method using BGGA syntax
public <T, U> {T => U} converter({=> T} a, {=> U} b, {T => U} c) {
return {T t => a.invoke().equals(t) ? b.invoke() : c.invoke(t)};
}
// The same using lightweight interfaces
interface Factory<C> C();
interface Transformer<I, O> O(I);
public <T, U> Transformer<T, U> converter(Factory<T> a, Factory<U> b, Transformer<T, U> c) {
return {T t => a.invoke().equals(t) ? b.invoke() : c.invoke(t)};
}
Personally, I find the latter to be much more readable, even though it involves more code.
Both Factory and Transformer can be defined once, probably in the JDK or framework, and have associated documentation.
In addition, if I'd never seen the code before, I'd much prefer to be assigned to maintain the latter code
with lightweight interfaces.
Perhaps that is the key to Java's success - code that can be maintained. Write once. Read many times.
Thanks
Finally, I should note that some of the inspiration for this idea came from blogs and documents by
Remi Forax and
Casper Bang.
Summary
I've outlined an alternative to function types that keeps a key Java element - the type and its name.
Lightweight interfaces are easy and quick to code if you don't want to document,
but have the capacity to grow and be full members of the normal Java world if required.
I'd really love to hear opinions on this.
It seems like a great way to balance the competing forces, but what do you think?
PS. Don't forget to vote for FCM at the java.net poll!