- http://codahale.com/downloads/email-to-donald.txt
- http://codahale.com/the-rest-of-the-story/
I have obviously not used Scala to the extent that the guy describes, but I can testify at how annoying it is the constant translation between Java and Scala (especially, since my small app, , was using Tapestry 5, which is obviously a Java framework). The conversion between the two was made worse (at least for me, a total Scala newb) by the following:
- Classes with the same names but completely or subtly different usages and intents . The fact that the Scala library used the same class names as the Java library (java.lang.Long and scala.Long) , the default imports use them, and there are some magical conversions that occur between the eponymous types. On a few occasions I was totally baffled about something not working only to find that in the end, the code was getting the wrong type . Thus, in a bunch of my classes, I ended up explicitly having to import the Java classes that the framework expected to work with , e.g.
import java.util.{List => JList }
import org.slf4j.Logger
import scala.collection.JavaConversions._
class FooPage {
@Property
private var catPieces:JList[ArtPiece] = _
// explicitly declaring the returning types as the Java types
def onPassivate():JList[String]= {
// and using the Scala provided conversions
return seqAsJavaList(List(category,subCategory))
}
}
- Null handling - because I was interacting w/ a Java framework, there was an expectation that nulls are OK and at different places, the framework does expect methods to return nulls in order to behave in certain ways. Scala goes for the whole Option pattern (where you aren't supposed to use nulls to make it all better) and has some conversion (that I obviously, don't fully understand) between null and these types. However, because of the interaction w/ the Java framework, I had to learn how to deal with both. It kinda sucked.
- Tapestry 5 and Scala interactions - because Tapestry 5 pushes the envelope on being a Java framework w/ a whole bunch of annotation processing, class transformations, etc. , in some cases there were clashes between the T5 approach and Scala. In some respects, Tapestry 5 manages to be a respectable and succinct Java framework by adding a whole bunch of metaprogramming features, which when used with Scala make the scala code less attractive, e.g:
- Page properties that would otherwise be set up as private fields in regular Tapestry 5, now have to be declared as private fields and initialized. If you didn't declare them as private, then T5 would complain (since pages can't have non-private members as it is managed by T5) , e.g. :
class Foo {
@Inject
var logger:Logger = _
@Inject
var pm:PersistenceManager = _
} - Sometimes the T5 and Scala approaches seemed to clash in ways that make things complicated. For example, in the persistent objects in the class, I often annotated the private fields w/ @BeanProperty (so that Scala generates proper getters/setters for those fields).
import scala.reflect.BeanProperty
import javax.jdo.annotations.Persistent;
class PersistentFoo {
@BeanProperty
@Persistent
var title = ""
}
import org.apache.tapestry5.annotations.Property;
class FooPage {
@Property
private var category:String = _
}
When I was working on the app, a few times I had to just stop for a day because I couldn't figure out how to do something massively simple (e.g. how to succinctly join a list of Strings into a comma separated string - stuff that would have taken me 30 seconds to do in Java, 2 seconds in Groovy and the proposed Scala solutions seemed like massive overkill). I originally started wanting to write some tests in Scala for this app, because I thought, "wouldn't it be nice to have something a little more flexible and less verbose than Java", but that still has nice static typing. Later I decided to try the whole Scala+T5 approach, and I have to admit I was pretty mad at myself when I would get stuck .
Obviously, many of my problems described above were due to my own weak Scala-foo (e.g. I had read through at least 2-3 books in order to be brave enough to try this just to learn that until I try things hands on, it doesn't stick too well), and other issues that I had were due to the interaction w/ the specific Java framework that I chose (Tapestry 5). Yet, in some ways, the experience was somewhat disappointing - having worked w/ Groovy for the last few years there is a massive difference in the approaches of the two languages. Where Groovy would often sacrifice some "internal beauty" in order to make a Java developer's life sweet and pleasant, e.g. :
- Joining a list of strings
[1,2,3].join(","); - string formatting using $ inside of strings
"Blah blah $fooVar" - Null safe dereference
foo?.bar
- Functional programming purism , or
List(1,2,3).reduceLeft(_ + "," + _)
- bizarre nullsafe dereference comments on StackOverflow
def ?[A](block: => A) =
... and usage ...
try { block } catch {
case e: NullPointerException if e.getStackTrace()(2).getMethodName == "$qmark" => null
case e => throw e
}val a = ?(b.c.d.e)
One part of my setup that worked very well and I enjoyed quite a bit was the Continuous Compilation and Tapestry's Live Class Reloading. Whereas for prior Tapestry pure-Java projects I had to rely on IDE magic to do some Compile-on-Save so that Tapestry can reload the changed classes, w/ the Scala setup it was much nicer. I set up a Maven project w/ the Scala Maven plugin , and then kick off the scala:cc goal to make it compile the changed page classes into my project. Thus, I had a completely IDE-independent setup that gave me a live-reloading experience on-par (and possibly beyond) the reloading experience with Grails.
In the end, after I managed to work through some of the issues described above, it ended up being a pretty reasonable set up and I was able to make pretty decent progress in getting the app out the door (for my wife's birthday). At the same time, I wasn't really able to leverage any cool Scala features that would magically boost my productivity, or make the codebase significantly cleaner or smaller (in some respects, it feels like the Scala based code is more verbose because of all the conversions and casting into Java types). I feel that if I knew more about Scala and was more knowledgeable about Tapestry internals, I might be able to write a Tapestr5 - Scala adapter layer that would plug into some of T5's extensions points to make it work more naturally with Tapestry (e.g. working w/ Scala lists in views, different handling of null values, etc). As a learning experience - I learned a lot, both about things that were interesting and useful (a bit of functional programming, Java/Scala integration), and some things that I really didn't want to know that much about (how Scala and T5 munge the Java classes to make the things tick).
In any event, the advice to people who like to try this kind of integration would be to allow yourself plenty of time for learning and experimentation w/ Scala and not giving up too early ( as I was almost ready to do on a few occasions). Fanf's blog has a few blog entries and a project on GitHub that is an excellent starting point.
[apologies in advance if the code-formatting doesn't work out below]
ReplyDeleteString join:
scala> List("one","two","three").mkString(",")
res2: String = one,two,three
or if you want to get all fancy
scala> List("one","two","three").mkString("[[[", ",", "]]]")
res3: String = [[[one,two,three]]]
I'm not sure what the use-case is with your null dereferencing. Generally, if I want to check for nulls and do something on non-null values, I'll convert them to an Option and go from there. ex.
scala> def timesTwo(mightBeNull:String):String = {
| Option(mightBeNull).map(mbn => mbn * 2) getOrElse ""
| }
timesTwo: (mightBeNull: String)String
scala> timesTwo(null)
res4: String = ""
scala> timesTwo("abc")
res5: String = abcabc
I've recently written a medium size REST service that uses JPA, Spring, and Apache CXF. The most trouble I can recall having was some conversion issues between Java and Scala collection instances *inside* of entity beans. I hacked around it, but a scala point-release or two later, I found that JavaConversions now "did the right thing" and out came the hacks. CXF and Spring integration "just worked" though I used the latter only for DI.
Infidel - as I mentioned , in the end I managed to get a pretty reasonable set up and it worked pretty decently. I ended up finding the mkString method on List, so you're right - in that particular example, there certainly is a simpler solution than doing reduceLeft. I used the mkString example to illustrate the general philosophical difference with Groovy - this one one of the many examples where I really wanted to do something super simple and the proposed solutions that I would find seemed overly complicated or overly purist.
ReplyDeleteHello,
ReplyDeleteThe fanf from the blog you linked here.
Well, my experiences with T5 and Scala weren't exactly stellar, but with some years of Scala behind me, I think I would have made that differently: T5 remove so much of Java from Java and does so much byteclass writting that it is not the best starting point to learn Scala. It forces to learn far to Scala internal to do simple things, and I really started to appreciate Scala when I used it without T5.
Now, perhaps if I would do thing in other way to use T5 with Scala - perhaps just keep pur Java for the presentation part, and use Scala for the complex business logic of the application, I don't know. Or just go and try Play! framework.
Cheers,