Why does Scala implicit conversion work here with two args but not with one?
On object ValueSet, I have apply(pairs : (String, ValueBase)*)
and implicit conversions from Int and String to ValueBase. If I apply ValueSet("a" -> 1, "b" -> 2)
then the pairs of (String, Int)
convert to (String, ValueBase)
and it works fine. If I apply with only one pair, ValueSet("a" -> 1)
then it says there's no overload of apply for (String,Int)
i.e. it will not implicitly convert. I can hack around this by adding apply[V <% ValueBase](p : (String, V))
which works in the one-pair case.
Why doesn't apply(pairs : (String, ValueBase)*)
work with just one pair?
(Bonus question: adding the extra apply() seems to solve the problem - is there a better solution? Is there anything wrong with this solution?)
Here's a complete compilable example, simplified from my actual code to try to show the minimum problem.
class ValueBase
case class ValueInt(val value : Int) extends ValueBase
case class ValueString(val value : String) extends ValueBase
case class ValuePair(val key : String, val value : ValueBase)
case class ValueSet(val value : List开发者_如何学C[ValuePair]) extends ValueBase
object ValueSet {
def apply(pairs : (String, ValueBase)*) : ValueSet = {
ValueSet(pairs.map(p => ValuePair(p._1, p._2)).toList)
}
/* Commenting out this apply() means single-pair
* ValueSet("a" -> 1)
* will not compile, error is:
* overloaded method value apply with alternatives: (value: List[ValuePair])ValueSet <and> (pairs: (String, ValueBase)*)ValueSet cannot be applied to ((java.lang.String, Int))
* Why does (String,Int) implicit convert to (String,ValueBase) if there are two args but not if there's one?
* Why do I need this apply()?
*/
def apply[V <% ValueBase](p : (String, V)) : ValueSet = {
ValueSet(List(ValuePair(p._1, p._2)))
}
}
object Sample {
implicit def int2value(i : Int) = ValueInt(i)
implicit def string2value(s : String) = ValueString(s)
/* These samples show the goal, to construct the sets
* in a nice Map-literal sort of style
*/
val oneInt = ValueSet("a" -> 1)
val oneString = ValueSet("b" -> "c")
val twoInt = ValueSet("d" -> 2, "e" -> 3)
val twoTypes = ValueSet("f" -> 4, "g" -> "quick brown fox")
/* Taking ArrowAssoc out of the picture and typing "Pair"
* explicitly doesn't seem to matter
*/
val oneInt2 = ValueSet(Pair("a", 1))
val twoTypes2 = ValueSet(Pair("f", 4), Pair("g", "quick brown fox"))
}
This happens, as explained by Daniel Sobral in a comment, because “the compiler sees one argument, two apply methods which may take one argument, and the argument passed fits neither. It then gives up considering anything else, because it doesn't know which method to try. If two arguments are passed, then one apply is discarded and the compiler looks for implicit conversions that make the other one work.”
(Remember that one apply
is automatically defined by the compiler, because you're defining a case class
.)
If you write this instead, implicit conversion works:
object ValueSet {
def fromPairs(pairs: (String, ValueBase)*): ValueSet = {
ValueSet(pairs.map(p => ValuePair(p._1, p._2)).toList)
}
}
object Sample {
implicit def int2value(i: Int): ValueInt = ValueInt(i)
implicit def string2value(s: String): ValueString = ValueString(s)
/* These samples show the goal, to construct the sets
* in a nice Map-literal sort of style
*/
val oneInt = ValueSet.fromPairs("a" -> 1)
val oneString = ValueSet.fromPairs("b" -> "c")
val twoInt = ValueSet.fromPairs("d" -> 2, "e" -> 3)
val twoTypes = ValueSet.fromPairs("f" -> 4, "g" -> "quick brown fox")
}
Not exactly what you were hoping for, I know...
精彩评论