Which implicit will be used

#1

When I’m trying to provide sufficiently many implicit variables/methods for a certain operation, do I need to be concerned that they have disjoint applicability? I was pleasantly surprised that the following code wasn’t flagged as an error when I compiled. Certainly there are cases when multiple implicits would be applicable at the same time. If I’m not mistaken an Array is a Seq and there are also Seqs which are TraversableOnce, right?

Do I need to be concerned about this concern, and carefully define these implicit values so that overlap never occurs? Is that even possible?

object TreeReducable {
  implicit def traversableTreeReducable[T[X] <: TraversableOnce[X]]:TreeReducable[T] = new TreeReducable[T] {
    override def foldLeft[A, B](m: T[A])(z: B)(op: (B, A) => B):B = m.foldLeft(z)(op)
  }

  implicit val arrayTreeReducable: TreeReducable[Array] = new TreeReducable[Array] {
    override def foldLeft[A, B](m: Array[A])(z: B)(op: (B, A) => B):B = m.foldLeft(z)(op)
  }

  implicit val seqTreeReducable:TreeReducable[Seq] = new TreeReducable[Seq] {
    override def foldLeft[A, B](m: Seq[A])(z: B)(op: (B, A) => B):B = m.foldLeft(z)(op)
  }
}
#2

Actually, you’re incorrect on the first one – if you look at the documentation for Array, you’ll see that it isn’t technically a Seq. That isn’t often obvious, since there are implicit conversions to, eg, WrappedArray (which is a Seq), but details do matter here.

(Suffice it to say, Array is actually a Java type, so it can’t inherit from Scala’s Seq.)

But to your point: the rules for implicits are complicated, and I can’t say that I know them by heart, but IIRC there’s a notion of specificity. In case of potential ambiguity, the compiler tries to rank the candidates based on inheritance hierarchy and lexical scope, and chooses the most-specific one; it’s only an error if there are multiple equally-good candidates. So it’s not astonishing that Seq wins over TraversableOnce.

That said – like I said, the rules are complicated, so I don’t recommend getting too casual about this. It’s fairly easy to trip yourself up if you’re not careful.

#3

Also (and this is important): I don’t think it’s ever an error to define conflicting implicits. IIRC, it’s only an error when you try to use them – if there are conflicts, the compiler will sometimes complain that it can’t find the desired implicit, where the reality is that it is finding too many of them.

#4

It seems to me (from my experimentation) that adding more implicits can reduce the number of applicable implicits, contrary to intuition. Perhaps this is just a wrong error message, as the error messages do not mention conflicts, yet fewer lines of the function are successfully compiled.

Take a look at this example. (3 errors). If I un-comment implicits #3 and #4, thus enabling to more implicits, then I get a new error on line 85 (total of 4 errors).

val reduced = range.doReduce(0)(_, _ + _)

The error message does not say there is a conflict, but rather that there is NO parameter type for the expanded function.

missing parameter type for expanded function ((x$20: ) => range.(0)(x$20, ((x$21, x$22) => x$21.$plus(x$22))))
#5

Yeah, my recollection is that that is common – the compiler is bad about distinguishing between “no implicits” and “too many implicits” in its error messages…

#6

It seems like a but if the compiler cannot distinguish between the two cases.

#7

Is there a way to ask the compiler for me information? Or given a set of types, partition it into a set of disjoint types with he same union. It turns out that the three chapters or two in my PhD dissertation about how to do this and how to do it efficiently. I’m not even sure this ist guaranteed to be possible without union/intersection/complement types.

Is there any help available, for example, from the compiler or from the IntelliJ Scala plugin?

What is the name of the type which is a subtype of TreeReducable[Seq] but neither a subtype of TreeReducable[Array] nor of TreeReducable[T] such that T[X] <: TraversableOnce[X] ?

#8

Honestly don’t know – that’s a bit out of my depth.

#9

View inheritance hierarchy and follow overrides in the IDE. In the case of parallel.mutable.ParArray/ParSeq this should lead you to parallel.ParSeq as a potential “sibling” to TraversableOnce - or to GenTraversableOnce as an overarching type for both subhierarchies.

#10

Thanks for the clue. I’m not sure I understand the use model yet. If I understand correctly I need to one-by-one create a type-hierarchy using the Navigate->Type Hierarchy menu item, and then trace that upward using the hierarchy browser. But I can only do this one type at a time, right? I cannot in any way trace several types upward until the meet.

#11

Lacking more information/constraints, I’d do this per type and try to remember the types I’ve seen along the way. In this specific case, I’d additionally look for the most general type declaring #foldLeft() and drill down from there - bidirectional search, basically. :slight_smile: