Reputation: 3758
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
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
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
Reputation: 8609
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))
.
Upvotes: 7