2

I have a function with a type parameter and I want to find out whether the type parameter is an Option or not. I have read some blogposts, i.e. this one, about type classes in scala recently, so I came up with this solution:

case class OptionFinder[A](isOption: Boolean)
implicit def notOption[A]: OptionFinder[A] = OptionFinder(false)
implicit def hitOption[A]: OptionFinder[Option[A]] = OptionFinder(true)

def myFunction[A](value: A)(implicit optionFinder: OptionFinder[A]): String = {
    if (optionFinder.isOption) {"Found Option!"} else {"Found something else."}
}

This works seemingly as desired:

scala> val x: Option[Int] = Some(3)
scala> myFunction(x)
res0: String = Found Option!

scala> val y: String = "abc"
scala> myFunction(y)
res1: String = Found something else.

In the case of Some(3) hitOption is the implicit parameter, even though notOption would match as well (with A = Option[Int]). Obviously the more specific is type chosen. But am I guaranteed that the compiler always chooses the more specific type? And how does that work in the compiler anyway? I did not find a documentation of this behavior yet.

Note: Maybe the title for this question is not best, I'll happily change it for a better one.

Phil
  • 139
  • 9
  • It's all in the implicit resolution, maybe https://stackoverflow.com/questions/5598085/where-does-scala-look-for-implicits has something. – Reactormonk Jan 27 '17 at 15:20
  • Yes, but here both implicits are in the same (=current) scope. My question is: How does the compiler choose between the two? – Phil Jan 27 '17 at 15:51

1 Answers1

2

There is already a question about this: Scala: Implicit parameter resolution precedence. Which answers itself through a complicated blog post. I think the most important piece of information is in Martin Odersky's comment on the blog post:

Here's a more high-level explanation what goes on with implicit search in Scala, and which corresponds to how the spec explains it, but in slightly less formalistic language.

  1. First, we look for implicits that are visible either as locals or as members of enclosing classes and packages or as imports - the precise rule is that we should be able to access them using their name only, without any prefix.

  2. If no implicits are found in step 1, we look in the "implicit scope", which contains all sort of companion objects that bear some relation to the type which we search for (i.e. companion object of the type itself, of its parameters if any are given, and also of its supertype and supertraits; the importance is to be as general as possible without reverting to whole program analysis like Haskell does).

If at either stage we find more than one implicit, disambiguation kicks in. Disambiguation is exactly the same as for overloading resolution. Static overloading resolution resolution rules are a bit involved, and I won't repeat them here. If it's any consolation: Java's rules and C#'s rules are considerably more complex than Scala's in this area.

Now according to this explanation it are "the rules of static overloading resolution" which will disambiguate between notOption and hitOption. To be honest, I fail to see how.

This answer explains that indeed methods with more specific arguments have priority, but I don't know if or how that is related to the overloading rules.

If I were you I would not depend on this behavior too much, but use the easier to understand concept of implicit priority through inheritance. It's a good idea to put your implicits in the companion object anyway.

It boils down to the fact that implicits that are inherited have lower priority. So it's safe to put the implicit you fall back to if hitOption doesn't match in a trait that the companion object extends.

case class OptionFinder[A](isOption: Boolean)

object OptionFinder extends LowerPriority {
  implicit def hitOption[A]: OptionFinder[Option[A]] = OptionFinder(true)
}

trait LowerPriority {
  implicit def notOption[A]: OptionFinder[A] = OptionFinder(false)
}

def myFunction[A](value: A)(implicit optionFinder: OptionFinder[A]): String = {
    if (optionFinder.isOption) {"Found Option!"} else {"Found something else."}
}

This should also work if you put your implicits in a non companion object MyImplicits and import them with import MyImplicits._.

Community
  • 1
  • 1
Jasper-M
  • 12,460
  • 1
  • 19
  • 34
  • Thank you, so disambiguation was the term I was missing to look for. The chapter on [overloading resolution](http://scala-lang.org/files/archive/spec/2.12/06-expressions.html#overloading-resolution) in the spec has some definitions of "as specific as" and "more specific than", but I will need some time to digest it. Meanwhile, I like your alternative solution which is guaranteed to work. – Phil Jan 27 '17 at 16:35