Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 775
rashmina Avatar asked Oct 30 '22 14:10

rashmina


1 Answers

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.

like image 79
Sudheer Aedama Avatar answered Nov 15 '22 07:11

Sudheer Aedama