rogueleaderr
rogueleaderr

Reputation: 4819

How to create a Scala function that can parametrically create instances of sub-types of some type

Sorry I'm not very familiar with Scala, but I'm curious if this is possible and haven't been able to figure out how.

Basically, I want to create some convenience initializers that can generate a random sample of data (in this case a grid). The grid will always be filled with instances of a particular type (in this case a Location). But in different cases I might want grids filled with different subtypes of Location, e.g. Farm or City.

In Python, this would be trivial:

def fillCollection(klass, size):
    return [klass() for _ in range(size)]

class City: pass

cities = fillCollection(City, 10)

I tried to do something similar in Scala but it does not work:

def fillGrid[T <: Location](size): Vector[T] = {
    Vector.fill[T](size, size) {
        T()
    }
}

The compiler just says "not found: value T"

So, it it possible to approximate the above Python code in Scala? If not, what's the recommended way to handle this kind of situation? I could write an initializer for each subtype, but in my real code there's a decent amount of boilerplate overlap between them so I'd like to share code if possible.

The best workaround I've come up with so far is to pass a closure into the initializer (which seems to be how the fill method on Vectors already works), e.g.:

  def fillGrid[T <: Location](withElem: => T, size: Int = 100): Vector[T] = {
    Vector.fill[T](n1 = size, n2 = size)(withElem)
  }

That's not a huge inconvenience, but it makes me curious why Scala doesn't support the "simpler" Python-style construct (if it in fact doesn't). I sort of get why having a "fully generic" initializer could cause trouble, but in this case I can't see what the harm would be generically initializing instances that are all known to be subtypes of a given parent type.

Upvotes: 4

Views: 137

Answers (3)

Ethan
Ethan

Reputation: 841

You are correct, in that what you have is probably the simplest option. The reason Scala can't do things the pythonic way is because the type system is much stronger, and it has to contend with type erasure. Scala can not guarantee at compile time that any subclass of Location has a particular constructor, and it will only allow you to do things that it can guarantee will conform to the types (unless you do tricky things with reflection).

If you want to clean it up a little bit, you can make it work more like python by using implicits.

implicit def emptyFarm(): Farm = new Farm
implicit def emptyCity(): City = new City

def fillGrid[T <: Location](size: Int = 100)(implicit withElem: () => T): Vector[Vector[T]] = {
  Vector.fill[T](n1 = size, n2 = size)(withElem())
}

fillGrid[farm](3)

To make this more usable in a library, it's common to put the implicits in a companion object of Location, so they can all be brought into scope where appropriate.

sealed trait Location
...
object Location
{
  implicit def emptyFarm...
  implicit def emptyCity...
}
...
import Location._
fillGrid[Farm](3)

Upvotes: 1

Joe K
Joe K

Reputation: 18434

Scala cannot do this kind of thing directly because it's not type safe. It will not work if you pass a class without a zero-argument constructor. The Python version throws an error at runtime if you try to do this.

The closure is probably the best way to go.

Upvotes: 1

You can use reflection to accomplish what you want...

This is a simple example that will only work if all your subclasses have a zero args constructor.

sealed trait Location
class Farm extends Location
class City extends Location

def fillGrid[T <: Location](size: Int)(implicit TTag: scala.reflect.ClassTag[T]): Vector[Vector[T]] = {
  val TClass = TTag.runtimeClass
  Vector.fill[T](size, size) { TClass.newInstance().asInstanceOf[T] }
}

However, I have never been a fan of runtime reflection, and I hope there could be another way.

Upvotes: 1

Related Questions