Struggle against habits formed by Java when migrating to Scala

后端 未结 7 706
星月不相逢
星月不相逢 2021-02-01 08:48

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

相关标签:
7条回答
  • 2021-02-01 09:17

    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.

    0 讨论(0)
  • 2021-02-01 09:30

    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 讨论(0)
  • 2021-02-01 09:31

    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.

    0 讨论(0)
  • 2021-02-01 09:32

    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!

    0 讨论(0)
  • 2021-02-01 09:36

    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")
     }
    
    0 讨论(0)
  • 2021-02-01 09:39

    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".
    0 讨论(0)
提交回复
热议问题