4
votes

In trying to answer this question, I came up with the following code:

    case class Monkey(bananas: Int) 
    case class Tree(rings: Int)
    case class Duck(quacks: Seq[String])

    implicit class IntLike(val x : Int) extends AnyVal

    implicit def monkey2Age(monkey: Monkey): IntLike = monkey.bananas / 1000
    implicit def tree2Age(tree: Tree): IntLike = tree.rings
    implicit def duck2Age(duck: Duck): IntLike = duck.quacks.size / 100000

    def purchaseCandles[A <% IntLike]()(implicit age : A) = {
      val asAge : IntLike = age
      println(s"I'm going to buy $asAge candles!")
    }              

    {
        implicit val guest = Tree(50)
        purchaseCandles()
    }

Note that the IntLike is only there to convince me this wasn't a problem focussed on Int.

This seems a fairly standard, if bad, use of implicits, and I was expecting it to work happily. However, on invoking purchaseCandles() the REPL yields the following error:

error: ambiguous implicit values: both value StringCanBuildFrom in object Predef of type => scala.collection.generic.CanBuildFrom[String,Char,String] and value guest of type Tree match expected type A

I cannot for the life of me see how this is the case. A is bound to have an view bound of IntLike, a type I just invented. The REPL confirms that there is no implicit view available:

scala> implicitly[Tree => IntLike]

res14: Tree => IntLike = function1

but

scala> implicitly[scala.collection.generic.CanBuildFrom[String, Char, String] => IntLike]

:18: error: No implicit view available from scala.collection.generic.CanBuildFrom[String,Char,String] => IntLike.

So how can StringCanBuildFrom be of an appropriate type? Is the compiler capable of resolving the multiple dependent implicits, and if not, why is this the error being shown?

2

2 Answers

1
votes

Before attempting an answer, here is first the reduced case. Note that you are calling purchaseCandles with no hint whatsoever on type A which I think is the origin of the problem.

object Foo

def test[A <% Foo.type](implicit bar: A) {}

test

Error:

<console>:10: error: ambiguous implicit values:
 both value StringCanBuildFrom in object Predef of type =>
     scala.collection.generic.CanBuildFrom[String,Char,String]
 and method conforms in object Predef of type [A]=> <:<[A,A]
 match expected type A
              test
              ^

The StringCanBuildFrom seems more like a confusing error message. If you concentrate on conforms here, the reason why this doesn't work might become more clear: Predef.conform states that whenever you ask for an implicit conversion A => A, this is guaranteed by means of identity. This is so that if you have def foo[B <% A](b: B), you can always call foo with a value of type A without the need to define any sort of A => A.


You are asking for the resolution of implicit argument of an unspecified type along with a conversion from that unspecified type to IntLike. I think that is beyond the scope of the implicit resolution algorithm. Still the error message is somewhat strange.

0
votes

Still no explanation why it fails—although, as said, I expect this is too much for the implicit resolution because of the undefined input type—, but you can make it work like this:

    implicit def findIntLike[A <% IntLike](x: A): IntLike = x
    def purchaseCandles()(implicit age: IntLike) = {
      println(s"I'm going to buy $age candles!")
    }              

    implicit val guest = Tree(50)
    purchaseCandles()