I'm trying to figure out how to invoke a constructor for a Scala abstract type:
class Journey(val length: Int)
class PlaneJourney(length: Int) extends Journey(length)
class BoatJourney(length: Int) extends Journey(length)
class Port[J <: Journey] {
def startJourney: J = {
new J(23) // error: class type required but J found
}
}
Is this even feasible? I'm familiar with Scala manifests but I'm not clear how they could help here. Likewise I can't figure out how to do the same with a companion object's apply() constructor:
object Journey { def apply() = new Journey(0) }
object PlaneJourney { def apply() = new PlaneJourney(0) }
object BoatJourney { def apply() = new BoatJourney(0) }
class Port[J <: Journey] {
def startJourney: J = {
J() // error: not found: value J
}
}
Any thoughts gratefully received!
There is no direct way to invoke the constructor or access the companion object given only a type. One solution would be to use a type class that constructs a default instance of the given type.
trait Default[A] { def default: A }
class Journey(val length: Int)
object Journey {
// Provide the implicit in the companion
implicit def default: Default[Journey] = new Default[Journey] {
def default = new Journey(0)
}
}
class Port[J <: Journey : Default] {
// use the Default[J] instance to create the instance
def startJourney: J = implicitly[Default[J]].default
}
You will need to add an implicit Default
definition to all companion objects of classes that support creation of a default instance.
Your class needs an implicit constructor parameter to get the Manifest
. Then you can call erasure to get the Class
and call newInstance
, which reflectively calls the nullary constructor if there is one.
class J[A](implicit m:Manifest[A]) {
def n = m.erasure.newInstance()
}
new J[Object].n
As of Scala 2.10, the erasure
property in the manifest is deprecated. def n = m.runtimeClass.newInstance()
does the same thing, but without warnings.
My inclination is that this cannot be done. I am far from a Scala guru, but my reasoning is this:
- You have a class Port with a type argument T where T must inherit from Journey (but T does not have to be exactly Journey, this is important).
- Within Port, you define a method which creates a new T. This class has no idea what T is, and therefore what T's constructor looks like.
- Because you don't know what arguments T's constructor takes, you don't know what arguments to pass to it.
The solutions to this problem are handled very nicely in another question, so I will point you there for them rather than repeating here: Abstract Types / Type Parameters in Scala