In response to my last article on Groovy's lack of static typing James Strachan, the original Groovy guy, tweeted "groovy should fix this really IMHO." Well, perhaps. But "fixing" it would substantially change Groovy's semantics. Most importantly it would prevent Groovy from executing some programs that work just fine right now.
An Example
Here's a simple example of a program that works now but wouldn't under a static typing regime. It's a simple variation on the program from the previous article.
interface Foo {} interface Bar {} class Baz implements Foo, Bar {} Foo test(Bar x) {return x} test(new Baz()) println("Everything's Groovy")
Compile it, run it, and feel groovy.
~/test$ groovyc test.groovy ~/test$ groovy test Everything's Groovy
If I transliterate into Scala
trait Foo trait Bar class Baz extends Foo with Bar def test(x : Bar) : Foo = x test(new Baz) println("Everything's Scala?")
And try to compile
~/test$ scalac -Xscript test test.scala !!! discarding <script preamble> (fragment of test.scala):5: error: type mismatch; found : this.Bar required: this.Foo def test(x : Bar) : Foo = x ^ one error found
Scala rejects a program that Groovy is happy with.
Conclusion
That's the nature of the beast. Static typing rejects some good programs that might otherwise work. And as my last article showed, dynamic typing allows some bad programs that can't possibly work. That's a pretty fundamental difference and there's no way around it.
If the Groovy community wants to make its type annotations work in a more statically typed manner that's ok. This article just shows that such a change wouldn't be 100% compatible with today's Groovy.
More advanced use cases of metaprogramming are not possible with a statically typed language: so things like all the Domain-Specific Languages or the Groovy builders now out there wouldn't be possible anymore if Groovy simply switched to being a statically typed language.
ReplyDeleteWhile builders aren't possible in Scala, I'm not sure I buy that they aren't possible in a statically typed language in general.
ReplyDeleteAnd saying that internal DSLs aren't possible in Scala is just plain wrong.
I certainly don't think Groovy is broken in this respect and, as you so clearly point out, "fixing" it would break perfectly good programs.
ReplyDeleteType information is of very limited use in a dynamic language. Knowing the type of an object tells us next to nothing about the semantics of that object. It is, of course, very useful in Groovy to reduce the impedance mismatch with Java to close to zero.
In my view Groovy should be "fixed" to make the type tell us less about the semantics not more!
I see no reason why the MetaClass cannot have a method which is called when an object is used in a context requiring a specific type. This would allow Groovy objects to allow conversions which are disallowed in plain Java.
This would mean that people who (unlike me) like the Perl string to numeric conversions to modify the String semantics to allow
int x = "10"
Robert, it's not the static typing that prevents you implementing Builders it's the static behaviour.
ReplyDeleteIf you implemented methodMissing in a statically typed language you could do Builders.
I'm far from being a Groovy expert, but what I have seen in practice makes me believe it would be possible to "have our cake and eat it, too."
ReplyDeleteUsing the JetBrains IntelliJ IDEA IDE with the JetGroovy plugin, I do get warnings in the code if I try to assign an object reference to a reference of an incompatible type. At this point, the code still compiles, but the tooling is smart enough to advise me I shouldn't assign a Bar to a Foo.
It would seem possible for a compiler to at least warn about incompatible type assignments when the types are known and let assignments to dynamic types compile without warning - all without breaking the sweet simplicity of a dynamic language.
Then developers could use static typing in the majority of cases where strict typing is applicable, yet apply dynamic typing to solve those cases where static typing gets in the way of a simpler, more elegant solution.
Wouldn't it be possible to have a configuration to force groovyc to act more or less aggressive, checking for 'wrongful' usage of annotated types?
ReplyDeleteI fail to see the point since the Scala example is simply a bug. It should be:
ReplyDeletescala> def test(x: Bar with Foo) : Foo = x
test: (Bar with Foo)Foo
scala> test(new Baz)
res0: Foo = Baz@37504d
There can be no surprise that you need to specify the correct method signatures in a static typed language.
@Baldur,
ReplyDeleteIt's a bug according to Scala's semantics which rejects something that is not a bug according to Groovy's semantics. That was pretty much the whole point of the article.
well, mabe Scala needs fixing? In old C++ days you'd call this a case of cross-casting: if an object is both Foo and Bar but will be passed as Foo, compiler will automatically cross-cast it to Bar if needed (or I think so, it's a long time...). WHich I think is a correct semantic for a statically typed OO language.
ReplyDeleteMarek,
ReplyDeleteTry again.
$ cat test.cpp
class Foo {};
class Bar {};
class Baz : public Foo, public Bar {};
Foo* test(Bar* x) { return x; }
int main(int argc, char** argv) {
Baz* baz = new Baz();
test(baz);
delete(baz);
return 0;
}
$ g++ test.cpp
test.cpp: In function ‘Foo* test(Bar*)’:
test.cpp:5: error: cannot convert ‘Bar*’ to ‘Foo*’ in return