Showing posts with label constructor. Show all posts
Showing posts with label constructor. Show all posts

Monday, February 1, 2010

Temporary variables during object instantiation

In Java a common pattern with class constructors is to assign field values and often there are several intermediate values used for the calculation. If the code is ported to Scala the resulting class will have the intermediate values as fields, which take up space in the object. However the issue is easily worked around. Lets look at a couple examples.

Example 1: Assigning a single field
  1. //  Java
  2. import java.io.File
  3. /** 
  4. No real logic behind class.  But for some reason it needs the path of a tmp directory in the working directory
  5. */
  6. class OneAssignment {
  7.   final String field;
  8.   public OneAssignment() {
  9.     File file = new File("tmp");
  10.     if(!file.exists()) {
  11.       file.mkdirs();
  12.     }
  13.     field = file.getAbsolutePath();
  14.   }
  15. }

In Scala the naive way to port this would be:
  1. //  Scala
  2. import java.io.File
  3. class OneAssignment {
  4.   val file = new File("tmp")
  5.   if(!file.exists()) {
  6.     file.mkdirs()
  7.   }
  8.   val field = file.getAbsolutePath()
  9. }

Problem is that it has an extra field "file" now. The correct way to port this would be as follows:
  1. //  Scala
  2. import java.io.File
  3. class OneAssignment {
  4. /* 
  5. notice that assignment is in a block so file is only visible within the block
  6. */
  7.   val field = {
  8.     val file = new File("tmp")
  9.     if(!file.exists()) {
  10.       file.mkdirs()
  11.     }
  12.     file.getAbsolutePath()
  13.   }
  14. }


Example 2: Assigning multiple fields
  1. //  Java
  2. import java.io.File
  3. /** 
  4. Basically the same as last example but multiple fields are assigned
  5. Notice that 2 fields depend on the temporary file variable but count does not
  6. */
  7. class MultipleAssignments {
  8.   final String tmp,mvn_repo;
  9.   find int count;
  10.   public OneAssignment() {
  11.     File file = new File("tmp");
  12.     if(!file.exists()) {
  13.       file.mkdirs();
  14.     }
  15.     tmp = file.getAbsolutePath();
  16.     count = file.listFiles.length;
  17.     
  18.     File home = new File(System.getProperty("user.home"));
  19.     mvn_repo = new File(home, ".m2").getPath();
  20.   }
  21. }

The Scala port:
  1. //  Scala
  2. import java.io.File
  3. class MultipleAssignments {
  4. /*
  5. When multiple fields depend on the same temporary variables the fields can be assigned together from one block by returning a tuple and using Scala's matching to expand the tuple during assignment.  See previous topics on assignment for details 
  6. */
  7.   val (tmp,count) = {
  8.     val file = new File("tmp");
  9.     if(!file.exists()) {
  10.       file.mkdirs();
  11.     }
  12.     val tmp = file.getAbsolutePath();
  13.     val count = file.listFiles.length;
  14.     (tmp, count)
  15.   }
  16.   val mvn_repo = {
  17.     val home = new File(System.getProperty("user.home"));
  18.     new File(home, ".m2").getPath();
  19.   }
  20. }

In some ways the Scala port is cleaner in that it splits the constructor up and decouples the dependencies between fields.

Monday, November 2, 2009

Multiple Constructors

In Scala there is a primary constructor: class MyClass(constructorParam:Any). Unlike Java, that constructor must be called. The question that often arises is, "How can one define multiple constructors?" There is a simple way to do this, however often a factory companion object can be used to remove the need for multiple constructors. Factory Companion Objects are covered in a previous post but I will review the pattern here quickly.

First multiple constructors:
  1. scala> class HelloConstructor(param1:Int, param2:Int) {
  2.      | def this(onlyParam:Int) = this(onlyParam,onlyParam)
  3.      | def this(p1:String, p2:String, p3:String) = this(p1.length, p2.length + p3.length)
  4.      | def this(onlyParam:String) = this(onlyParam.length)
  5.      | }
  6. defined class HelloConstructor

In Java if a constructor calls another constructor that call must be the first statement in the constructor. Scala is the same except that in Scala the primary constructor must be called. Notice that all constructors call this(param1,param2) at some point. In addition any method defined in the class HelloConstructor is not available until after the primary constructor is invoked. The following examples are not valid.
  1. scala> class HelloConstructor(param1:Int, param2:Int) {                                  
  2.      | def x = 1
  3.      | def this() = this(x,3)
  4.      | }
  5. <console>:6: error: not found: value x
  6.        def this() = this(x,3)
  7. scala> class HelloConstructor(param1:Int, param2:Int) {
  8.      | def this() = {
  9.      | println("constructing")  // the REPL won't even let me finish method definition
  10. <console>:3: error: 'this' expected but identifier found.
  11.        println("constructing")
  12.        ^

Factory companion objects can be used to work around these restrictions:
  1. scala> class HelloConstructor(param1:Int, param2:Int)  
  2. defined class HelloConstructor
  3. scala> object HelloConstructor {                             
  4.      | def apply() = {
  5.      | println("constructing object")
  6.      | new HelloConstructor(1,2)
  7.      | }
  8.      | }
  9. defined module HelloConstructor
  10. scala> HelloConstructor()
  11. constructing object
  12. res1: HelloConstructor = HelloConstructor@5b0010ec

Since companion objects can access private members of the class the factory methods can be as powerful as a constructor without the restrictions.

One last idea that is useful when designing classes is Scala 2.8 default arguments:
  1. scala> class HelloConstructor(param1: Int = 1, param2: Int = 2)
  2. defined class HelloConstructor
  3. scala> new HelloConstructor()
  4. res0: HelloConstructor = HelloConstructor@7cd47880
  5. scala> new HelloConstructor(1)
  6. res1: HelloConstructor = HelloConstructor@3834a1c8
  7. scala> new HelloConstructor(param1 = 1)
  8. res2: HelloConstructor = HelloConstructor@3b3e3940
  9. scala> new HelloConstructor(param2 = 1)
  10. res3: HelloConstructor = HelloConstructor@6dee2ea8
  11. scala> new HelloConstructor(3,4)       
  12. res4: HelloConstructor = HelloConstructor@397b6074
  13. scala> new HelloConstructor(param1 = 3, param2=4)
  14. res5: HelloConstructor = HelloConstructor@20272fec