Reputation: 3331
I'm going to create custom fluent API for my application. I decided to check code of already existing libraries and found some monsters in at least 2 projects that using fluent api.
I saw that there're many classes and interfaces, that differ only in number of generic types.
For example: In jOOQ library DerivedColumnList22
In RxJava: Action9
What is the purpose of monsters like this? Is it only for performance cases? Or is it some commonalities and way to go in fluent apis?
It looks scary to implement own DSL in fluent api, when you see monsters like this.
Upvotes: 4
Views: 787
Reputation: 70007
In RxJava, those numbered interfaces give lambda-convenience to users who need to work with 2-9 sources/values at the same time. Action9
is not in use by RxJava proper but its sister Func9
is available as an argument on combineLatest
and zip
. Based on experience from Rx.NET, we thought it is unlikely people will use combine/zip with more than 9 direct sources, plus not having Func10
avoids member sorting problems when listing them out (i.e., Func1
, Func10
, Func2
).
For more than 9 sources, there is the FuncN<R>
defined as a functional interface with a a single method R call(Object... args)
at the cost that you have to manually extract elements and cast them back to their respective types:
Observable.zip(Arrays.asList(ints, strings, objects, t4, t5, t6, ...), a -> {
int p1 = (Integer)a[0];
String p2 = (String)a[1];
//...
})
Under the hood, the regular Func2
-Func9
are wrapped into a FuncN
for combineLatest/zip so these operators each can have an any number of sources implementation instead of 9 slightly different one. Then the wrapper just unpacks the array it gets called with like above.
Upvotes: 2
Reputation: 221106
Since you mentioned the jOOQ library, I'll give an authoritative answer here, being the author of jOOQ, and having written this post about designing fluent APIs (or rather: designing internal domain specific languages in Java): https://blog.jooq.org/2012/01/05/the-java-fluent-api-designer-crash-course
What you've discovered is the common desire of API designers to work with tuples. A few languages have built-in support for structural tuple types, i.e. tuple types that can be created in an ad-hoc manner, rather than declaring them in advance and giving them names. The latter is called nominal typing. More details about nominal/structural typing here.
SQL is an excellent example of a language that allows for creating ad-hoc tuple types:
SELECT first_name, last_name, age
FROM people
The above query creates a table of tuples of degree 3 with types (string, string, number)
. JavaScript is another example, where I can quickly generate a tuple as such:
var x = {
firstName: "Lukas",
lastName: "Eder",
age: undefined
};
There are other languages that have tuple support to some level. Ideally, tuples allow access by name and index to individual attributes. Sometimes, access is given only by name. In other cases, it is only given by index. But it's always the same thing, conceptually.
DerivedColumnList22
and Action9
The Java language, unfortunately, has no such tools to create ad-hoc structural tuple types. (Almost) everything in Java needs to be nominally typed. Even "anonymous functions" (lambda expressions) need to be assigned to a nominal SAM type:
Runnable r = () -> { doSomething(); }
// ^^^^^^^^^^^^^^^^^^^^^^^^ --- Syntactically looks like a structural type
// ^^^^^^^^^^ ------------------------------ But it's really a nominal type
So, if an API wants to give its users the illusion of supporting real structural tuple types, it has to provide names for each supported type in advance. For instance, Action9
:
Action9<T1, T2, T3, T4, T5, T6, T7, T8, T9> action =
(a, b, c, d, e, f, g, h, i) -> { doSomething(); }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ------------- Look ma! Almost a structural tuple type!
So, this technique is mostly implemented by libraries like RxJava (which encourages functional programming where structural tuple types really shine) or jOOQ (which encourages SQL where structural tuple types also really shine).
Other libraries / APIs include:
Tuple1
- Tuple22
Tuple1
- Tuple8
This doesn't mean that you need those types in your own DSL. Start simple. Eventually, you may feel you need to add them.
Upvotes: 9