Scala: specify a default generic type instead of Nothing

Viewed 4134

I have a pair of classes that look something like this. There's a Generator that generates a value based on some class-level values, and a GeneratorFactory that constructs a Generator.

case class Generator[T, S](a: T, b: T, c: T) {
  def generate(implicit bf: CanBuildFrom[S, T, S]): S =
    bf() += (a, b, c) result
}

case class GeneratorFactory[T]() {
  def build[S <% Seq[T]](seq: S) = Generator[T, S](seq(0), seq(1), seq(2))
}

You'll notice that GeneratorFactory.build accepts an argument of type S and Generator.generate produces a value of type S, but there is nothing of type S stored by the Generator.

We can use the classes like this. The factory works on a sequence of Char, and generate produces a String because build is given a String.

val gb = GeneratorFactory[Char]()
val g = gb.build("this string")
val o = g.generate

This is fine and handles the String type implicitly because we are using the GeneratorFactory.


The Problem

Now the problem arises when I want to construct a Generator without going through the factory. I would like to be able to do this:

val g2 = Generator('a', 'b', 'c')
g2.generate // error

But I get an error because g2 has type Generator[Char,Nothing] and Scala "Cannot construct a collection of type Nothing with elements of type Char based on a collection of type Nothing."

What I want is a way to tell Scala that the "default value" of S is something like Seq[T] instead of Nothing. Borrowing from the syntax for default parameters, we could think of this as being something like:

case class Generator[T, S=Seq[T]]

Insufficient Solutions

Of course it works if we explicitly tell the generator what its generated type should be, but I think a default option would be nicer (my actual scenario is more complex):

val g3 = Generator[Char, String]('a', 'b', 'c')
val o3 = g3.generate  // works fine, o3 has type String

I thought about overloading Generator.apply to have a one-generic-type version, but this causes an error since apparently Scala can't distinguish between the two apply definitions:

object Generator {
  def apply[T](a: T, b: T, c: T) = new Generator[T, Seq[T]](a, b, c)
}

val g2 = Generator('a', 'b', 'c')  // error: ambiguous reference to overloaded definition

Desired Output

What I would like is a way to simply construct a Generator without specifying the type S and have it default to Seq[T] so that I can do:

val g2 = Generator('a', 'b', 'c')
val o2 = g2.generate
// o2 is of type Seq[Char]

I think that this would be the cleanest interface for the user.

Any ideas how I can make this happen?

3 Answers
Related