I've been knocking myself out trying to use a companion object to instantiate one of a class's subtypes. It's not known at compile time which subclass will be instantiated. This is remarkably similar to an example found in Programming Scala starting on page 127. I've contrived an example here:
import scala.reflect.runtime.universe._
abstract class Animal
class Dog extends Animal { def bark = "woof" }
class Cat extends Animal { def meow = "meow" }
object Animal {
def apply(specify: String): Option[Animal] = {
specify match {
case "dog" => Some(new Dog)
case "cat" => Some(new Cat)
case _ => None
}
}
}
object Test extends App {
def getType[T: TypeTag](obj: T) = typeOf[T]
var dog = Animal("dog")
println(getType(dog))
}
This program prints out scala.Option[Animal]
. I'd expect it to print out scala.Option[Dog]
. Furthermore, if I attempt to add the line println(dog.bark)
to the end of the Test
object, it fails to compile. Is this simply impossible?
I've been poring over Scala reflection documentation, but it seems very dense and difficult. Furthermore, this seems to be exactly how the Programming Scala example works, so I can't imagine what I've done wrong here.
EDIT: This version doesn't have reflection and simply throws a compile-time error due to the wrong type.
abstract class Animal
class Dog extends Animal { def bark = "woof" }
class Cat extends Animal { def meow = "meow" }
object Animal {
def apply(specify: String): Option[Animal] = {
specify match {
case "dog" => Some(new Dog)
case "cat" => Some(new Cat)
case _ => None
}
}
}
object Test extends App {
var dog = Animal("dog")
println(dog.get.bark)
}
// $ scalac test.scala
// test.scala:17: error: value bark is not a member of Animal
// println(dog.get.bark)
// ^
// one error found
EDIT: Evidently this requires pattern matching to work out. Here is a working example, somewhat simplified.
abstract class Animal
class Dog extends Animal { def bark = "woof" }
class Cat extends Animal { def meow = "meow" }
object Animal {
def apply(specify: String): Animal = {
specify match {
case "dog" => new Dog
case "cat" => new Cat
}
}
}
object Test extends App {
val dog = Animal("dog")
dog match {
case d: Dog => println(d.bark)
case _ =>
}
}
It is impossible.
In the second example, Animal.apply
always returns Option[Animal]
, because that is its type signature.
Your object Test
is really saying, expanded out a bit:
object Test extends App {
var dog: Option[Animal] = Animal.apply("dog")
println(dog.get.bark)
}
The compiler might be able to tell at compile time that it could be an Option[Dog]
, but the language's semantics don't allow for that: the grammar would have to be much more complex to be able to encapsulate that knowledge.