1

At a high-level, I'm curious to know how implicits defined inside a class body are resolved? I assumed they would be considered part of the current scope (as per the awesome answer here), but I am wrong.

For a specific example, why doesn't the following work:

trait Convert[T, U] { 
  final def value(record: T)(implicit f: T => U): U = f(record) 
}


case class ConvertMap(key: String) 
  extends Convert[Map[String, String], Boolean] { 
  implicit def a2b(m: Map[String, String]): Boolean = m.contains(key)
}

I can instantiate the class ConvertMap, but when I call the value method I get an error stating that the view from Map[String, String] => Boolean can't be found.

scala> val c = ConvertMap("key")
c: ConvertMap = ConvertMap(key)

scala> c.value(Map("key" -> "value"))
<console>:13: error: No implicit view available from Map[String,String] => Boolean.
user451151
  • 386
  • 1
  • 9

2 Answers2

2

If you re-read the answer you provided for implicit resolution scope, you see that there are a couple of things which happen here causing the implicit you've defined not to be found.

First, if the implicit isn't found in the local scope of the call-site, we defer to "category 2" lookup for implicits which involves the types underlying the implicit. We're searching for an implicit conversion of type Map[String, String] => Boolean. According to the implicit scoping rules, the following is applicable to our use case:

  • If T is a parameterized type S[T1, ..., Tn], the union of the parts of S and T1, ..., Tn.

Our S[T1.. Tn] is a Map[String, String] (and it's base classes), so we have both Map and String as a candidate type for implicit lookup, but neither of them hold the conversion. Further, the return type is also considered, meaning Boolean is also in scope, but again, it doesn't hold the implicit and hence why the compiler complains.

The simplest thing that can be done to help the compiler find it is to place the implicit inside the companion object of ConvertMap, but that means we can no longer accept a value key in the constructor, which makes your example a bit contrived.

Yuval Itzchakov
  • 136,303
  • 28
  • 230
  • 296
  • Admittedly, I need to go back and learn scoping rules. This does make some sense. Although the example I've provided is somewhat silly, it is very close to a real-world problem I am facing. I need to satisfy some implicit T => U, but it is dependent on argument to the constructor of a class that extends my trait (which precludes me from defining it in the companion object). – user451151 Jul 05 '17 at 20:07
  • @user451151 I think you need to go back and either re-think the problem after reading the scoping rules again, or asking a question with a [MCVE] of your actual problem where we can actually try to give you concrete answers to the underlying problem. – Yuval Itzchakov Jul 05 '17 at 20:09
  • This is very similar to my actual problem; I would argue it satisfies MCV. Whether or not it's a poor design is a better question imo. – user451151 Jul 05 '17 at 20:11
  • These kind of problems can usually be resolved with the type class pattern. Instead of requiring the actual function conversion, you require a trait which you implement for each concrete type, and that trait can take a `key: String` value. – Yuval Itzchakov Jul 05 '17 at 20:15
1

It needs to be resolvable at the call site (where c.value is called). At that time, the only thing you have in scope c. I am not sure why you would think it makes sense for something defined inside a class to be considered in scope at that point.

BTW, your example doesn't really seem like a good use for implicits to begin with. Why not just make f a member method in the trait?

Dima
  • 33,157
  • 5
  • 34
  • 54
  • It sounds like I might just be confused on scoping rules in general, but, in the effort to learn: If the only thing I have in scope is `c` at that time, and `c` contains my `implicit def a2b` then how is it not in scope? – user451151 Jul 05 '17 at 20:05
  • `Why not just make f a member method in the trait?` Good question. Sometimes T and U will happen to be the same type, which makes that member superfluous; any suggestions for handling that case? In other words, I'm trying to handle the case where T and U could be different *or* the same in the same trait. – user451151 Jul 05 '17 at 20:08
  • make a specialized trait with single parameter? – Dima Jul 05 '17 at 20:12