2015-06-01 2 views
51

Учитывая класс типов, где выбор экземпляра должен быть выполнен на основе типа возвращаемого значения:Неожиданное неявное разрешение на основе умозаключения от обратного типа

case class Monoid[A](m0: A) // We only care about the zero here 
implicit def s[T] : Monoid[Set[T]] = Monoid(Set.empty[T]) 
implicit def l[T] : Monoid[List[T]] = Monoid(List.empty[T]) 
def mzero[A](implicit m: Monoid[A]) : A = m.m0 

почему Scala (2.11.6) не решить правильный экземпляр:

scala> mzero : List[Int] 
<console>:24: error: ambiguous implicit values: 
both method s of type [T]=> Monoid[Set[T]] 
and method l of type [T]=> Monoid[List[T]] 
match expected type Monoid[A] 
       mzero : List[Int] 
      ^

, когда он не имеет никаких проблем с поиском неявное на основе типа возвращаемого при использовании функции неявно (переопределить его здесь, как я, чтобы проиллюстрировать, как S imilar это в mzero)

def i[A](implicit a : A) : A = a 
scala> i : Monoid[List[Int]] 
res18: Monoid[List[Int]] = Monoid(List()) 

Monoid[A], вместо Monoid[List[Int]] в сообщении об ошибке озадачивает.

Я бы предположил, что многие разработчики сказаза знакомы с этой проблемой, поскольку, как представляется, это ограничивает удобство классов в scala.

РЕДАКТИРОВАТЬ: Я ищу, чтобы это работало без отклонения от типа. В противном случае я бы хотел понять, почему это невозможно. Если это ограничение документировано как проблема Scala, я не смог его найти.

+4

Ограничения способности Scala использовать ожидаемые типы возвращаемых данных для указания вывода параметров типа при вовлечении имплицитов являются невероятно запутанными, поэтому все просто записывают 'mzero [List [Int]]'. –

+2

Как некоторое обходное решение вы можете уйти, переопределив 'mzero' как' def mzero [A] (неявный m: Monoid [_ <: A]): A = m.m0'. Я бы сказал, что я бы пошел с 'mzero [List [Int]]', как упоминал Тревис Браун. –

+0

Впечатляющий и удивительный. Благодаря! Любое объяснение? Кажется, это расслабляет тип и получает меньше матчей! –

ответ

1

1) После перезаписи кода следующим образом:

case class Monoid[A](m0: A) // We only care about the zero here 
implicit def s[T] : Monoid[Set[T]] = Monoid(Set.empty[T]) 
implicit def l[T] : Monoid[List[T]] = Monoid(List.empty[T]) 
def mzero[A]()(implicit m: Monoid[A]) : A = m.m0 

val zero = mzero[List[Int]]() 
val zero2: List[Int] = mzero() 

тогда становится ясно, почему это работает так.

2) После того, как вы установили mzero как def mzero[A]()(implicit m: Monoid[_ <: A]) : A = m.m0, вы включили дополнительный тип вывода для разрешения существующего типа. Компилятор получил фактический тип из требуемого типа возврата. Если вы хотите, вы можете проверить его с помощью def mzero[A <: B, B]()(implicit m: Monoid[A]) : A = m.m0.

3) Конечно, все это поведение - это просто тонкости компилятора, и я не думаю, что такие частичные случаи действительно требовали глубокого понимания.

Смежные вопросы