Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
like image 707
Vilius Normantas Avatar asked Feb 24 '23 12:02

Vilius Normantas


2 Answers

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)).

like image 65
miah Avatar answered Mar 04 '23 21:03

miah


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.

like image 41
Rex Kerr Avatar answered Mar 04 '23 21:03

Rex Kerr