开发者

Struggle against habits formed by Java when migrating to Scala

What are the most common mistakes that Java developers make when migrating to Scala?

By mistakes I mean writing a code 开发者_如何转开发that does not conform to Scala spirit, for example using loops when map-like functions are more appropriate, excessive use of exceptions etc.

EDIT: one more is using own getters/setters instead of methods kindly generated by Scala


It's quite simple: Java programmer will tend to write imperative style code, whereas a more Scala-like approach would involve a functional style.

  • That is what Bill Venners illustrated back in December 2008 in his post "How Scala Changed My Programming Style".
  • That is why there is a collection of articles about "Scala for Java Refugees".
  • That is how some of the SO questions about Scala are formulated: "help rewriting in functional style".


One obvious one is to not take advantage of the nested scoping that scala allows, plus the delaying of side-effects (or realising that everything in scala is an expression):

public InputStream foo(int i) {
   final String s = String.valueOf(i);
   boolean b = s.length() > 3;
   File dir;
   if (b) {
       dir = new File("C:/tmp");
   } else {
       dir = new File("/tmp");
   }
   if (!dir.exists()) dir.mkdirs();
   return new FileInputStream(new File(dir, "hello.txt"));
}

Could be converted as:

def foo(i : Int) : InputStream = {
   val s = i.toString
   val b = s.length > 3
   val dir = 
     if (b) {
       new File("C:/tmp")
     } else {
       new File("/tmp")
     }
   if (!dir.exists) dir.mkdirs()
   new FileInputStream(new File(dir, "hello.txt"))
}

But this can be improved upon a lot. It could be:

def foo(i : Int) = {
   def dir = {
     def ensuring(d : File) = { if (!d.exists) require(d.mkdirs); d }
     def b = { 
       def s = i.toString
       s.length > 3
     }
     ensuring(new File(if (b) "C:/tmp" else "/tmp"));
   }
   new FileInputStream(dir, "hello.txt")
}

The latter example does not "export" any variable beyond the scope which it is needed. In fact, it does not declare any variables at all. This means it is easier to refactor later. Of course, this approach does lead to hugely bloated class files!


A couple of my favourites:

  1. It took me a while to realise how truly useful Option is. A common mistake carried from Java is to use null to represent a field/variable that sometimes does not have a value. Recognise that you can use 'map' and 'foreach' on Option to write safer code.

  2. Learn how to use 'map', 'foreach', 'dropWhile', 'foldLeft', ... and other handy methods on Scala collections to save writing the kind of loop constructions you see everywhere in Java, which I now perceive as verbose, clumsy, and harder to read.


A common mistake is to go wild and overuse a feature not present in Java once you "grokked" it. E.g. newbies tend to overuse pattern matching(*), explicit recursion, implicit conversions, (pseudo-) operator overloading and so on. Another mistake is to misuse features that look superficially similar in Java (but ain't), like for-comprehensions or even if (which works more like Java's ternary operator ?:).

(*) There is a great cheat sheet for pattern matching on Option: http://blog.tmorris.net/scalaoption-cheat-sheet/


I haven't adopted lazy vals and streams so far.

In the beginning, a common error (which the compiler finds) is to forget the semicolon in a for:

 for (a <- al;
      b <- bl
      if (a < b)) // ...

and where to place the yield:

 for (a <- al) yield {
     val x = foo (a).map (b).filter (c)
     if (x.cond ()) 9 else 14 
 }

instead of

 for (a <- al) {
     val x = foo (a).map (b).filter (c)
     if (x.cond ()) yield 9 else yield 14  // why don't ya yield!
 }

and forgetting the equals sign for a method:

 def yoyo (aka : Aka) : Zirp { // ups!
     aka.floskel ("foo")
 }


Using if statements. You can usually refactor the code to use if-expressions or by using filter.

Using too many vars instead of vals.

Instead of loops, like others have said, use the list comprehension functions like map, filter, foldLeft, etc. If there isn't one available that you need (look carefully there should be something you can use), use tail recursion.

Instead of setters, I keep the spirit of functional programming and have my objects immutable. So instead I do something like this where I return a new object:

class MyClass(val x: Int) {
    def setX(newx: Int) = new MyClass(newx)
}

I try to work with lists as much as possible. Also, to generate lists, instead of using a loop, use the for/yield expressions.


Using Arrays.

This is basic stuff and easily spotted and fixed, but will slow you down initially when it bites your ass.

Scala has an Array object, while in Java this is a built in artifact. This means that initialising and accessing elements of the array in Scala are actually method calls:

//Java
//Initialise
String [] javaArr = {"a", "b"};
//Access
String blah1 = javaArr[1];  //blah1 contains "b"

//Scala
//Initialise
val scalaArr = Array("c", "d")  //Note this is a method call against the Array Singleton
//Access
val blah2 = scalaArr(1)  //blah2 contains "d"
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜