Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does auxiliary constructor not see import done in the class?

The code below gives a compile error:

  class Info(val x: String)

  object Info {
    val default = new Info("top")
  }

  case class Data(x: String) {
    import Info.default

    def this() = this(default.x)

  }

Error:(11, 23) not found: value default def this() = this(default.x)

Why is symbol default not seen in the constructor, in spite of the import?

Further experiments show it is not only import. Replacing import line with a def (or even val) does not help, still the error persists:

def default = Info.default
like image 317
Suma Avatar asked Dec 25 '15 21:12

Suma


People also ask

How can we define auxiliary constructor as method in a class?

We are allowed to create any number of auxiliary constructors in our Scala class, but a scala class contains only one primary constructor. Auxiliary constructors are defined as methods in the class with the keyword this. We can describe multiple auxiliary constructors, but they must have different parameter lists.

What is the purpose of auxiliary constructor in Scala?

The auxiliary constructor in Scala is used for constructor overloading and defined as a method using this name. The auxiliary constructor must call either previously defined auxiliary constructor or primary constructor in the first line of its body.

What is primary constructor in Scala?

The primary constructor of a Scala class is a combination of: The constructor parameters. Methods that are called in the body of the class. Statements and expressions that are executed in the body of the class.


2 Answers

Scoping doesn't work the way you expected because of the scoping of self constructor invocations (when secondary constructors invoke the primary constructor):

The signature and the self constructor invocation of a constructor definition are type-checked and evaluated in the scope which is in effect at the point of the enclosing class definition, augmented by any type parameters of the enclosing class and by any early definitions of the enclosing template.

In other words, the scope in effect for the expression default.x is the scope outside Data.

This is confusing because textually it looks like that expression is inside the class.

This rule has to do with lexical scope and not with evaluation order.

You have to move the import outside the class definition.

For fun, scoping is slightly broken in the following case:

object Playroom {
  def default = "playing"
  case class Toy(squeezeMsg: String = default)
  object Toy {
    def default = "srsly"
  }
}

object Test extends App {
  import Playroom._
  println(Toy())
  println(new Toy())
}

which is this puzzler. Generally speaking, it's more idiomatic (and robust) to use apply methods on the companion.

like image 161
som-snytt Avatar answered Oct 17 '22 09:10

som-snytt


This is behaving according to specs. The body of the class is part of the so called primary constructor. The auxiliary constructor(s) needs to start by calling the primary or another auxiliary defined before. Assuming you only have one auxiliary, since the call to primary must be the first statement in your auxiliary, it means that during this first statement you do not have access to anything defined inside the primary. After the first call is done, then you can access any of the members and imports defined in the primary.

In your example, the problem is that the import (import Info.default) is not visible to the first statement of your auxiliary. If you replace it with def this() = this(Info.default.x) it should work.

Example:

The following compiles fine since the call to Constant is done after the call to the primary.

class G(i: Int) {
   val Constant = 1
   // No argument auxiliary constructor    
   def this() = {
      this(3) // Call to primary 
      println(Constant * 2)
   }
}

The following does not compile since the call to Constant is done before the call to the primary, which means that Constant has not been created yet.

class G(i: Int) {
   val Constant = 1
   // No argument auxiliary constructor    
   def this() = {
      this(Constant) // This will cause an error!
   }
}

Solution:

Have any constants you need defined in the companion object. The companion object is by definition initialized first, so you will be able to access any members of it from within the constructors of your class.

object G {
   val Constant = 1
}

class G(i: Int) {
    // No argument auxiliary constructor    
    def this() = {
       this(G.Constant) // This now works!
    }
 }
like image 21
marios Avatar answered Oct 17 '22 07:10

marios