0

I am learning Scala in order to use it for a project.

One thing I want to get a deeper understanding of is the type system, as it is something I have never used before in my other projects.

Suppose I have set up the following code:

// priority implicits sealed trait Stringifier[T] { def stringify(lst: List[T]): String } trait Int_Stringifier { implicit object IntStringifier extends Stringifier[Int] { def stringify(lst: List[Int]): String = lst.toString() } } object Double_Stringifier extends Int_Stringifier { implicit object DoubleStringifier extends Stringifier[Double] { def stringify(lst: List[Double]): String = lst.toString() } } import Double_Stringifier._ object Example extends App { trait Animal[T0] { def incrementAge(): Animal[T0] } case class Food[T0: Stringifier]() { def getCalories = 100 } case class Dog[T0: Stringifier] (age: Int = 0, food: Food[T0] = Food()) extends Animal[String] { def incrementAge(): Dog[T0] = this.copy(age = age + 1) } } 

So in the example, there is a type error:

ambiguous implicit values: [error] both object DoubleStringifier in object Double_Stringifier of type Double_Stringifier.DoubleStringifier.type [error] and value evidence$2 of type Stringifier[T0] [error] match expected type Stringifier[T0] [error] (age: Int = 0, food: Food[T0] = Food()) extends Animal[String] 

Ok fair enough. But if I remove the context bound, this code compiles. I.e. if I change the code for '''Dog''' to:

case class Dog[T0] (age: Int = 0, food: Food[T0] = Food()) extends Animal[String] { def incrementAge(): Dog[T0] = this.copy(age = age + 1) } 

Now I assumed that this would also not compile, because this type is more generic, so more ambiguous, but it does.

What is going on here? I understand that when I put the context bound, the compiler doesn't know whether it is a double or an int. But why then would an even more generic type compile? Surely if there is no context bound, I could potentially have a Dog[String] etc, which should also confuse the compiler.

From this answer: "A context bound describes an implicit value, instead of view bound's implicit conversion. It is used to declare that for some type A, there is an implicit value of type B[A] available"

2 Answers 2

2

Now I assumed that this would also not compile, because this type is more generic, so more ambiguous, but it does.

The ambiguity was between implicits. Both

Double_Stringifier.DoubleStringifier 

and anonymous evidence of Dog[T0: Stringifier] (because class Dog[T0: Stringifier](...) is desugared to class Dog[T0](...)(implicit ev: Stringifier[T0])) were the candidates.

(Int_Stringifier#IntStringifier was irrelevant because it has lower priority).

Now you removed the context bound and only one candidate for implicit parameter in Food() remains, so there's no ambiguity. I can't see how the type being more generic is relevant. More generic doesn't mean more ambiguous. Either you have ambiguity between implicits or not.


Actually if you remove import but keep context bound the anonymous evidence is not seen in default values. So it counts for ambiguity but doesn't count when is alone :)

Scala 2.13.2, 2.13.3.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks @Dmytro, really appreciate the guidance as this is I think a pretty challenging area for newcomers. I think I am thinking about this problem the wrong way. When you say "only one candidate", is that the Double_Stringifier you are referring to?
@finite_diffidence Yes. If you remove the import in the scope where you define case class Dog the implicit will not be in the scope.
@finite_diffidence Actually if you remove import but keep context bound the anonymous evidence is not seen in default values. So it counts for ambiguity but doesn't count when is alone :) Scala 2.13.2.
@finite_diffidence See also "mistake #2" in "Some Mistakes We Made When Designing Implicits – Martin Odersky" youtu.be/1h8xNBykZqM?t=772 (starting at 12:52)
2

It seems to me (and if I'm wrong I'm hoping @DmytroMitin will correct me), the key to understanding this is with the default value supplied for the food parameter, which makes class Dog both a definition site, requiring an implicit be available at the call site, as well as a call site, requiring an implicit must be in scope at compile time.

The import earlier in the code supplies the implicit required for the Food() call site, but the Dog constructor requires an implicit, placed in ev, from its call site. Thus the ambiguity.

4 Comments

Actually if we remove import but keep context bound the anonymous evidence is not seen in default values. So it counts for ambiguity but doesn't count when is alone :) Scala 2.13.2.
Right. Without the import the food() call won't compile (no implicit available), but with the import then the resulting ev evidence counts as an ambiguity.
Thanks @jwvh, so if I follow correctly, do you mean something like this at the call site: case class Dog[T0] (age: Int = 0, food: Food[T0] = Food())(implicit ev: Stringifier[T0]) extends Animal[String] {
@finite_diffidence; Context bound syntax ([A:B]) is syntax sugar. Your Dog definition site de-surgars to what you've got there. The call site is wherever a new Dog instance is created. But because of the default value for the food parameter, the Dog definition site is also the Food() call site.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.