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
The new A(Some(8))
can be either:
A[Int]
via primary constructor,A[Option[Int]]
via alternate constructor.You can specify the type explicitly, like new A[Int](Some(8))
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With