This may come as a shock to some people. Isn't GWT just Java syntax you say? Yes, it is, and it does not extend the Java grammar in anyway. Yet, it is nonetheless true that GWT is more powerful than Java.
The Overlay Type
The reason why GWT is more powerful is because it is actually a unification of two type systems, Java and Javascript. And while it seems that these are relatively walled off from one another, there is a bridge that unites the two, and that is the GWT Overlay Type system.
The overlay system in essence, permits the 'overlay' or attachment of Java types to Javascript objects. And since you can pass Java objects into Javascript and back, this means you can in fact, overlay types on Java as well.
Categories and Extension Methods
Some languages have a facility called Categories or Extension Methods, modern examples include Objective-C, Groovy, and C#. A category allows you to pretend as if an existing reference implements additional methods, when it actually doesn't.
In reality, they are syntax sugar for invocation of static utility functions. That is:
SomeType x = new SomeType();
SomeTypeUtils.doSomething(x);
becomes
SomeType x = new SomeType();
x.doSomething();
Transparently, behind the scenes, the compiler rewrites the invocation of
x.doSomething()
into SomeTypeUtils.doSomething(x)
. GWT's JavaScriptObject overlays are essentially Categories, as the compiler transparently rewrites methods that appear to exist on an Overlay into static method calls on the underlying JavaScriptObject. This is one reason why JSO methods have to be effectively final, as there is no polymorphism allowed.This all sounds very interesting and theoretical, but what's the practical benefit?
Type-Safe Enumerations with Zero Overhead
Let's say you want to write a method that can set the display property of an element to one of the legal values, and only the legal values. Traditional approaches would include using a Java enum and writing a setter method that accepts only this type:
enum DisplayType {
BLOCK, INLINE, TABLE, NONE;
}
public static void setDisplay(Element e, DisplayType t) {
e.getStyle().setProperty("display", t.name().toLowerCase());
}
Unfortunately, this will generate a lot of bloat, since each enum value is a class, the class must be initialized by a static initializer, and the ultimate CSS property value string has to be obtained through method calls that might not inline because of polymorphic dispatch.
Think about what we want here. Don't we really just want to create a subclass of
java.lang.String
, create a bunch of this String subclass constants, and write a method to accept that type? Unfortunately, java.lang.String is final. You can't subclass it in Java. But you can in GWT, and I'll show you how!
Turn a String into a JSO
public class DisplayType extends JavaScriptObject {
protected DisplayType() {}
public native static DisplayType make(String str) /*-{
return str;
}-*/;
public native String value() /*-{
return this;
}-*/;
}
For brevity, I left out the extra code to support Hosted Mode. In hosted mode, you must wrap the 'str' argument in an array, e.g.
[str]
and fetch it using return this[0]
, but that's not important. What this code is effectively doing is casting a String
into a DisplayType
and imposing an additional categorical method on this reference, which is value()
. Keep in mind, the value()
is never actually attached to the prototype of the underlying Javascript object.To use,
DisplayType NONE = DisplayType.make("none");
DisplayType BLOCK = DisplayType.make("block");
DisplayType INLINE = DisplayType.make("inline");
DisplayType TABLE = DisplayType.make("TABLE");
Now, what is the output of the GWT compiler when compiling
element.getStyle().setProperty("display", BLOCK.value())
? Here it is:
element.style['display']='block';
In fact, even calling the
setDisplay()
method with various DisplayType
enums results in inlined assignments to the element.style property with no method calls!Adding methods to Numbers
In Groovy, Scala, and some languages, you can even add methods to primitive integers. Using GWT overlay types, you can even do this!
public class Int extends JavaScriptObject {
protected Int() {}
public static native Int make(int x) /*-{
return x;
}-*/;
final public native int value() /*-{
return this;
}-*/;
final public Int square() {
return make(value() * value());
}
}
int val = (int) Duration.currentTimeMillis();
Int x = Int.make(val);
Int sq = x.square();
Window.alert(String.valueOf(sq.value()));
And what do you think the generated code looks like? Try this:
val = (new Date()).getTime();
x = val;
sq = x * x;
$wnd.alert('' + sq);
There you have it. I successfully added a method to a primitive 'int' called
square()
without using a wrapper, and with no overhead whatsoever. This opens the doors to implementing primitive 'wrappers' for GWT for int, double, float, etc, that are not wrappers at all and have no overhead, which would be very useful in many circumstances where Integer, Double, Float, in java.lang are too heavy.So, can we conclude from this that GWT is more powerful than Java? ;-)
-Ray
14 comments:
The only, but major, problem with your "Type-Safe Enumerations with Zero Overhead" is that you cannot switch/case on them, which might be extremely handy. The workaround is then either to chain if/else's or write the swich/case in JSNI (as javascript knows how to switch/case on a String), but then you loose the "type-safe" benefits (compile-time checks, etc.)
So I think I'll stick to "normal" Java enums waiting for GWT to optimize them (or I have a real problem with the associated overhead)
Dude, very nicely done! I had no idea the monster we've created... :)
Hi,
I don't see how this is more powerful than Java.
In your first example you showed a way to workaround an overhead problem that I believe exists only in GWT. (But I could be wrong)
Your second example you just wrapped an int inside another object and added more methods to it. The only difference is that you did it using a GWT feature, but yet, you can't actually do "int x = 5.square()", can you?
Regards,
Thiago Souza
But the thing is, the 'wrapper' types are not autoboxable in the same way actual java primitive wrappers are, and thus you loose convenience wont you?
Sure, GWT is more powerful. And oranges are more citrusy than apples! :) Seriously, what you've demonstrated here is very cool, but is it really fair to compare a programming language to a compiler/toolkit, especially in terms of "power?"
And on the DisplayType example: it's certainly lighter weight, but have you really gained any of the Enum's benefits with this approach? Couldn't any code call DisplayType.make("foo") and bypass the faux Enum you created?
Again, very cool stuff! Thanks for sharing.
@Thiago, the overhead problem also exists in Java, enums are more heavy weight than integers or Strings. With GWT, I can avoid object creation entirely. Yes, you can't actually write 5.square(), but you can write Int(5).square() and the result is still a boxed int, with no object creation or virtual dispatch.
The point is, Java lacks extension methods, and this hack in GWT yields the ability to box other types and add extension methods with absolutely zero overhead.
@Isaac, all you need to do to is change make() to protected.
@Chii/Thomas, yes some convenience is lost with autoboxing and switch/case gone, on the other hand, autoboxed primitives are wasteful, especially for high performance apps.
The library I'm working on has enums for every CSS3 property and value. I've got a similar library for OpenSocial. Java Enums cause enormous bloat in these circumstances. Likewise, in my Chronoscope library which deals with tens of thousands of points, if I want something safer than a double (the most optimal numeric type for GWT), this is pretty much the only solution.
After having read your article I can only agree - GWT type system is more powerful than Java's - adding flexibility to type safety. Nice!
Cheers!
Shonzilla
I'm using a dev build of GWT, but the Int example doesn't seem to work for me. Your example with String works fine, but when I try the one with Int, I get "java.lang.IllegalArgumentException: Something other than a Java object was returned from JSNI method '@mypackage.MyWidget$Int::make(I)': Cannot convert to type java.lang.Object from number". Could be my build, but figured I'd mention it.
Are there any reasons that GWT isn't able to optimize the DisplayType enum to the same as your JavaScriptObject implementation? If it's so common to use enums this way, it would seem worthwhile to add functionality to detect this use case and optimize it.
Ray,
For those of us slow on the uptake, the hand waving about how to implement the 'string extension' in hosted mode caused a bit of consternation. But, we put our heads to it, think we figured it out.. Can you say if this is what you meant..
public class ClientContext extends JavaScriptObject {
protected ClientContext() {}
public final static ClientContext valueOf(String str) {
if (GWT.isScript()) {
return valueOfScript(str);
} else {
return valueOfHosted(str);
}
}
private final native static ClientContext valueOfHosted(String str) /*-{
this[0] = [str];
return this[0];
}-*/;
private final native static ClientContext valueOfScript(String str) /*-{
return str;
}-*/;
public final String getName() {
if (GWT.isScript()) {
return getNameScript();
} else {
return getNameHosted();
}
}
private final native String getNameHosted() /*-{
return this[0][0];
}-*/;
private final native String getNameScript() /*-{
return this;
}-*/;
}
When I hunt down the class in compiled js, all I see are raw strings, and hosted mode compiles. Just want to make sure we're doing it 'right'??
-adam malter
Adam, yes, that's exactly the solution I used for hosted mode.
Hi Ian,
Using the DisplayType enum-like technique, it works on the browser flawelessly.
However, if I run on the hosted-mode with the "Run built-in server" unchecked (using a live server), it complains about ClassCastException.
"Caused by: java.lang.ClassCastException: java.lang.String cannot be cast to com.example.hello5.gwt.model.Greet$Status"
Is this a known issue?
Oh btw, gwt-1.7.1 was used.
Ray,
Thanks for this.
So, using your approach, I can create a (PseudoEnum) class where I "make" types from GWT Constant interface strings...
public class ConstantsToEnums {
...
public static PseudoEnum SOME_STRING = PseudoEnum.make(constants.addToCart());
...
public final PseudoEnum SOME_STRING2 = PseudoEnum.make(constants.buyNow());
...
}
Question is will I be recreating the overhead I might otherwise have avoided (using your approach) by creating a whole bunch of static members or, alternatively, by creating an instance of the (PseudoEnum) class and accessing the final members?
What I'm going for here is OO design: I want to defer my exact choice of constants, e.g., shoe sizes, but still have logic built that handles my (PseduoEnum) typed constants whatever they finally are.
Maybe there's another facility for this in GWT...
Thanks,
Grar
Post a Comment