Vilius Normantas
Vilius Normantas

Reputation: 3758

Scala: Generic class with multiple constructors

I'm trying to create a generic class like this:

class A[T](v: Option[T]) {
  def this(v: T) = this(Some(v))
  def this() = this(None)
  def getV = v 
}

Then I do some testing:

scala> new A getV
res21: Option[Nothing] = None
scala> new A(8) getV
res22: Option[Int] = Some(8)

So far so good. But as soon as I try to call the main constructor, I get this:

scala> new A(Some(8)) getV
<console>:9: error: ambiguous reference to overloaded definition,
both constructor A in class A of type (v: T)A[T]
and  constructor A in class A of type (v: Option[T])A[T]
match argument types (Some[Int])
       new A(Some(8)) getV
       ^

scala> new A(None) getV
<console>:9: error: ambiguous reference to overloaded definition,
both constructor A in class A of type (v: T)A[T]
and  constructor A in class A of type (v: Option[T])A[T]
match argument types (None.type)
       new A(None) getV
       ^

What's so "ambiguous" between those two constructors? Or (let me guess) it's yet another thing I do not know about Scala's type system? :)

Sure, if I use non-generic class everything works as expected. My B class works just fine:

class B(v: Option[String]) {
  def this(v: String) = this(Some(v))
  def this() = this(None)
  def getV = v 
}

scala> new B("ABC") getV
res26: Option[String] = Some(ABC)
scala> new B getV
res27: Option[String] = None
scala> new B(Some("ABC")) getV
res28: Option[String] = Some(ABC)
scala> new B(None) getV
res29: Option[String] = None

Upvotes: 4

Views: 2407

Answers (3)

Profiterole
Profiterole

Reputation: 187

Two workarounds when you want several constructions for a generic class.

1) Extend your class with another class that has the constructor you're interested in. Mind the + in C[+T], it means that C0[T] is covariant to C[+T], so that C0[T] will be accepted when C[T] is required. At least most often, check out this covariance thing.

class C[+T](i: Int)

class C0[T](s:String) extends C[T](Integer.parseInt(s))

2) Use a method, that you can, for instance, conveniently put in a companion object. It's rather idiomatic in Scala.

object C {
  def apply[T](s:String) = new C[T](Integer.parseInt(s))
}

Upvotes: 0

Rex Kerr
Rex Kerr

Reputation: 167871

The problem has already been identified. What about a solution that doesn't require typing?

Solution: implicit conversions with priority.

The problem with implicit conversions is that you probably don't want to write implicit def everything_is_optional[A](a: A) = Some(a) because this breaks your type system for options (in that you'll get promotion without noticing). Maybe you want this, but personally, I like the type system to tell me when I've gotten confused about whether something is an option or not. So we need some sort of other wrapper. Like so:

// Might want to do this for more than just one class, so generalize
class Implicator[A,B](val value: A) {
  def to[C] = this.asInstanceOf[Implicator[A,C]]
}

class X[A](i: Implicator[Option[A],X[A]]) {
  private val v = i.value
  def getV = v
}
trait LowPriorityX {
  implicit def everything_for_x[A](a: A) = new Implicator(Option(a)).to[X[A]]
}
object X extends LowPriorityX {
  implicit def option_for_x[A](oa: Option[A]) = new Implicator(oa).to[X[A]]
}

Now we can try this out (make sure to enter the above in :paste mode if you use the REPL, or enter it all inside an object and import the object, so that object X is interpreted as the companion object for class X:

scala> new X(5)
res0: X[Int] = X@26473f4c

scala> new X(Some(5))
res1: X[Int] = X@1d944379

So we get our desired behavior at the expense of a bit of extra code and an implicit conversion.

I'm almost positive there's a type encoding scheme that will work also, but I haven't had time to finish it, plus I lost my enthusiasm for it once I noticed that the compiler insists on creating and boxing the implicit used for type bounding in such schemes, even though it's only needed for type checking.

Upvotes: 1

miah
miah

Reputation: 8609

The new A(Some(8)) can be either:

  • new instance of A[Int] via primary constructor,
  • new instance of A[Option[Int]] via alternate constructor.

You can specify the type explicitly, like new A[Int](Some(8)).

Upvotes: 7

Related Questions