[project lombok] Some ideas for fluent syntax

16 views
Skip to first unread message

Maaartin

unread,
Apr 19, 2010, 7:39:13 PM4/19/10
to Project Lombok
Fluent methods often allows for much more compact and readable syntax.
But it's not without problems:
- fluent setters are incompatible with java.beans.Introspector
- fluent setters often use different naming convention
- most of available Java methods are not fluent
- mixing fluent and classical methods may be ugly and confusing

I'd love Java with things like
map.put(key1, value1).put(key2, value2);
but this will simply never happen - at least not for java.util.Map.

...unless we change the syntax. AFAIK, Reinier plans already some Java
syntax extension. I also think he finds the fluentness quite
important.

I've got different (but similar) ideas for the syntax, I hope, not all
of them are stupid:


Version 0: Allow statements like above and treat all method returning
void as if they'd return their this.
This could be a bit confusing and wouldn't work e.g. with Map.put as
it returns a result (although it gets ignored most of the time).


Version 1: Allow statements starting with a dot followed by an
identifier like

map.put(key1, value1);
.put(key2, value2);

The semantic should be equivalent to a program with the last top-level
object repeated before the dot. Let me make it clear by examples:

ListOfMaps.get(0).put(key1, (inputStream.readLine()));
.put(key2, value2)

is equivalent to

Map _map = ListOfMaps.get(0); // _map is a new local variable
_map.put(key1, (inputStream.readLine()));
_map.put(key2, value2); // not inputStream since it was not at the top
level

There may be some strange cases like

if (cond) map.put(key1, value1);
.put(key2, value2);

Any such case should simply lead to a compile error.


Version 2: This version looks about the same, but uses ";." as an
operator (no spaces between the two chars are allowed), so you must
write
map.put(key1, value1);.put(key2, value2);
I don't like it since the semicolon in my mind always terminates a
statement.


Version 3: The same like above, but with "&." as the operator:
map.put(key1, value1) &.put(key2, value2);
This could be quite readable and intuitive. The statement

if (cond) map.put(key1, value1) &.put(key2, value2);

would get an obvious meaning then.

--
You received this message because you are subscribed to the Google
Groups group for http://projectlombok.org/

To post to this group, send email to project...@googlegroups.com
To unsubscribe from this group, send email to
project-lombo...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/project-lombok?hl=en

Reinier Zwitserloot

unread,
Apr 21, 2010, 6:00:27 AM4/21/10
to Project Lombok
I'm kinda partial to "&." too!

I definitely don't like the most popular concept for fluent
interfaces, which is that 'void' method implicitly return self. This
sucks when interfaces are updated (the JVM may remain 100% backwards
compatible, but other libraries don't), and there's plenty of methods
that have obscure return types, such as Map's put which returns the
old element. I know this, you know this, but I wouldn't be at all
surprised if over 50% of the general java using population didn't and
presumed that map.put returns void. They'd chain and if they have a
map of Maps weird unexplainable things would happen.

I can't speak for Roel but I'm fairly sure he feels the same way about
this: Whenever there's an opportunity for magic weirdness, we don't
like it anymore. Weirdness is fine, as long as it results in a clear
error message that you see as you're typing. For the flying turtle
release we can add new operators. However, and this is really
unfortunate, we also need full resolution for this to work :(

For something like:

TYPE t = whatever();
t.foo()&.bar();

the translation is trivial:

TYPE t = whatever();
t.foo();
t.bar();

but what is the translation of:

whatever().foo()&.bar()?

It would be:

RETURN_TYPE_OF_WHATEVER_METHOD $t = whatever();
$t.foo();
$t.bar();

it won't (re)resolve and compile correctly unless the type of $t is
the return type of the whatever() method. Figuring that out requires
full + introspective resolution. It's annoying how many of these great
ideas require it. We definitely need to figure out resolution.
> Groups group forhttp://projectlombok.org/

Maaartin

unread,
Apr 21, 2010, 4:36:21 PM4/21/10
to Project Lombok
I agree. With the operator you need to specify its priority, but
that's quite obvious: Since it normaly stands in the place of
statement ending semicolon, it should get the lowest priority. I hope,
there are no unclear points, except for the question "What should the
operator |. do?". :D

[snip]

> For something like:
>
> TYPE t = whatever();
> t.foo()&.bar();
>
> the translation is trivial:
>
> TYPE t = whatever();
> t.foo();
> t.bar();
>
> but what is the translation of:
>
> whatever().foo()&.bar()?
>
> It would be:
>
> RETURN_TYPE_OF_WHATEVER_METHOD $t = whatever();
> $t.foo();
> $t.bar();

I'm afraid, it can get even worse:
http://dl.dropbox.com/u/4971686/Weirdness.java
public class Weirdness {
private interface Bar {
void bar();
}
private interface Foo {
void foo();
}
private interface FoobarFactory<T extends Foo & Bar> {
T create();
}
public void pita(FoobarFactory<?> factory) {
factory.create().foo();
factory.create().bar();
// there's no suitable type for the local variable here, you need
something like
final Foo foo = factory.create();
foo.foo();
((Bar) foo).bar();
// but this is strange enough case to be ignored
}
}

> it won't (re)resolve and compile correctly unless the type of $t is
> the return type of the whatever() method.

In my example the type <? extends Foo & Bar> and this does not really
exist in Java.
Changing the method to
public <T extends Foo & Bar> void pita(FoobarFactory<T> factory);
helps, but it's probably too crazy a change.

> Figuring that out requires
> full + introspective resolution. It's annoying how many of these great
> ideas require it. We definitely need to figure out resolution.

I can't help here (no time, no experience), but is there no cheating
possible? Those times before good IDEs I used to handle cases when I
couldn't find the type of an expression e by simply writing
Void v = e;
The compiler said e.g. "Type mismatch: cannot convert from
Map<String,capture#1-of ? extends E> to Void" and I won. Couldn't you
in the more complicated cases let the compilation fail, record the
information and use it next time?

Reinier Zwitserloot

unread,
Apr 21, 2010, 7:58:11 PM4/21/10
to Project Lombok
Yes, the so-called undenotable types problem that's also plaguing the
snoracle crew that's implementing the diamond operator for java7.
Their solution is to error out when it happens. Lombok is perfectly
capable of registering errors, so we'll generate just that: Can't
chain calls on an undenotable type. Technically there's a way out,
sort of, for all undenotables.

Let's say our undenotable is <? extends Foo>. This is trivial and
something we'll handle; the temporary variable's type can simply be
Foo. If our undenotable is <? super Foo>, then the temporary variable
will be Object.

But if our undenotable is a joint type, possibly with generics
involved, such as <? extends A & B> or a lub of A and B via for
example (A and B are interfaces, C and D both implement both A and B):
someUnkownBoolean? new C() : new D(); - this expression's type is also
undenotable, as it's "A & B", then we can first get rid of the
generics by using the same rule as before - extends just boils down to
what's being extended, and super boils down to "Object", and then to
handle the A & B relation we create 2 temporary variables, save to the
first, save to the second via a cast which we know cannot fail, and
then sort chained calls into the appropriate temporary variable.
Something like:

(foo ? new C() : new D()).a()&.b();

with 'a' a method of interface A and b a method of interface B, we
could translate this to:

A $t1 = (foo ? new C() : new D());
B $t2 = (B) $t1;
$t1.a();
$t2.b();


I'm not 100% convinced this is going to work though, especially if the
last call in the chain (the b method) uses generics itself - is it
possible to let the type of the operand in a method call have an
effect on the return type of said method? If yes, this won't work. If
no, this will work, but, it's crazy complicated for something
relatively rare. For a first release we'll most assuredly handle this
by generating an error that advises the user to cast first.

Fantastic catch though. I hadn't thought of undenotables yet until you
brought it up!
> Groups group forhttp://projectlombok.org/
Reply all
Reply to author
Forward
0 new messages