Scala - Co/Contra-Variance применительно к неявному выбору параметров

У меня есть такая черта:

trait CanFold[-T, R] {
  def sum(acc: R, elem: T): R
  def zero: R
}

С функцией, которая работает с ней следующим образом:

def sum[A, B](list: Traversable[A])(implicit adder: CanFold[A, B]): B = 
  list.foldLeft(adder.zero)((acc,e) => adder.sum(acc, e))

Цель состоит в том, чтобы сделать что-то вроде этого:

implicit def CanFoldSeqs[A] = new CanFold[Traversable[A], Traversable[A]] {
  def sum(x: Traversable[A], y: Traversable[A]) = x ++ y
  def zero = Traversable()
}

sum(List(1, 2, 3) :: List(4, 5) :: Nil)
//=> Traversable[Int] = List(1, 2, 3, 4, 5)

Итак, это тип-класс для типов, для которых среда уже знает, как складываться и может быть определена для Ints, Strings, что угодно.

Моя проблема в том, что я хочу также иметь более специфические импликации, которые имеют приоритет, например:

implicit def CanFoldSets[A] = new CanFold[Set[A], Set[A]] {
  def sum(x: Set[A], y: Set[A]) = x ++ y
  def zero = Set.empty[A]
}

sum(Set(1,2) :: Set(3,4) :: Nil)
//=> Set[Int] = Set(1, 2, 3, 4)

Однако этот вызов метода порождает конфликт, так как существует двусмысленность:

both method CanFoldSeqs in object ...
and method CanFoldSets in object ...
match expected type CanFold[Set[Int], B]

Итак, я хочу, чтобы компилятор выполнил поиск наиболее конкретного неявного между Any и my type. Целью является предоставление стандартных реализаций базовых типов, которые могут быть легко переопределены для более конкретных подтипов, без теневого копирования, которое является уродливым.

Возможно, я думаю об этом, но можно только надеяться: -)

Ответ 1

Обычный подход в подобной ситуации использует преимущество того, что имплициты имеют приоритет по наследованию:

trait LowPriorityCanFoldInstances {
  implicit def CanFoldSeqs[A] = new CanFold[Traversable[A], Traversable[A]] {
    def sum(x: Traversable[A], y: Traversable[A]) = x ++ y
    def zero = Traversable()
  }
}

object CanFoldInstances extends LowPriorityCanFoldInstances {
  implicit def CanFoldSets[A] = new CanFold[Set[A], Set[A]] {
    def sum(x: Set[A], y: Set[A]) = x ++ y
    def zero = Set.empty[A]
  }
}

import CanFoldInstances._

Теперь экземпляр Set будет использоваться, когда он применим, но тот, который для Traversable по-прежнему доступен, когда он отсутствует.