Why does Scala implicit conversion work here with two arguments, but not with one?

In the ValueSet, I also have apply(pairs : (String, ValueBase)*)implicit conversions from Int and String to ValueBase. If I applied ValueSet("a" -> 1, "b" -> 2), then the pairs are (String, Int)converted to (String, ValueBase), and it works fine. If I apply only to one pair, ValueSet("a" -> 1)then he says that there is no overload for (String,Int), i.e. It will not implicitly convert. I can hack this by adding apply[V <% ValueBase](p : (String, V))which works in a one-party case.

Why apply(pairs : (String, ValueBase)*)does it work with only one pair?

(Bonus question: adding extra apply () seems to solve the problem - is there a better solution? Is there something wrong with this solution?)

Here is a complete compiled example simplified from my actual code to try and show a minimal 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[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 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"))
}
+3
source share
1 answer

This happens, as Daniel Collected explained in a comment, because β€œthe compiler sees one argument, two apply methods that can take one argument, and the argument does not match any. Then he refuses to consider anything else because he doesn’t I know which method to try, if two arguments are passed, then one application is discarded, and the compiler looks for implicit conversions that make the other work.

(Remember that one is applyautomatically detected by the compiler, because you define it case class.)

If you write this instead, the 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 what you hoped for, I know ...

+4
source

All Articles