Search code examples
scalamultiple-inheritance

Scala - Multiple inheritance with App trait


Consider two traits, TestTrait1 and TestTrait and assume NewObject extends both. The idea is to make use of variable in TestTrait1 in TestTrait. The below code works perfectly fine.

scala> trait TestTrait1 {
 | val arguments1: Array[String] = Array("1","2")
 | }

defined trait TestTrait1

scala> trait TestTrait {
 | val arguments: Array[String]
 | val len = arguments.length
 | }

defined trait TestTrait

scala> object NewObject extends TestTrait1 with TestTrait {
 |  lazy val arguments = arguments1
 | }

defined object NewObject

scala> NewObject
res30: NewObject.type = NewObject$@7c013560

Now replace TestTrait1 with App. Since arguments is set for lazy evaluation, I will assume that even in case of DelayedInit, the below code will work.

scala> object NewObject extends App with TestTrait {
 | lazy val arguments = args
 | }

But it doesn't. What is the reason behind this?

scala> NewObject
java.lang.NullPointerException
at TestTrait$class.$init$(<console>:12)
... 35 elided

If this is the case, what is the solution to use args in another trait similar to TestTrait here?


Solution

  • trait TestTrait1 {
      val arguments1: Array[String] = Array("1","2")
    }
    
    trait TestTrait {
      val arguments: Array[String]
      val len = arguments.length
    }
    

    If you see the difference, the TestTrait has a member len that would eagerly get initialized. But args is a def inside App which happens to have a default value as null. If you change the len to lazy val or def it would not blow-up with NPE.

    Let's try this on a quick REPL session:

    scala> :paste
    // Entering paste mode (ctrl-D to finish)
    
    trait TestTrait {
      def arguments: Array[String]
      lazy val len = arguments.length
    }
    
    object NewObject extends App with TestTrait {
      override lazy val arguments = super.args // Added `override` and `super` just for clarity.
    }
    
    // Exiting paste mode, now interpreting.
    
    defined trait TestTrait
    defined object NewObject
    
    scala> NewObject
    res0: NewObject.type = NewObject$@5ace1ed4
    
    scala> NewObject.arguments
    res1: Array[String] = null
    

    If you want to reproduce the issue you can call len as below:

    scala> NewObject.len
    java.lang.NullPointerException
      at TestTrait$class.len(<console>:12)
      at NewObject$.len$lzycompute(<console>:15)
      at NewObject$.len(<console>:15)
      ... 33 elided
    

    So, the answer to your question is you would need to make len either lazy val or def if you want to invoke the instance of NewObject. I would suggest making the NewObject a class or trait because you do not want an unsafe/eagerly initialized len member that would blow-up with NPE.