Почему это неявное разрешение не работает?

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

trait GetItem[A[_], T, R] {
  type Out
  def ret(a: A[T], ref: R): Out
}
object GetItem {
  implicit def ifRefIsInt[A[_], T]: GetItem[A, T, Int] { type Out = A[T] } = new GetItem[A, T, Int] {
    type Out = A[T]
    def ret(a: A[T], ref: Int): Out = a
  }
}
import GetItem._
//this works fine:
val t: GetItem[List, Double, Int] { type Out = List[Double] } = ifRefIsInt[List, Double]
// so does this:
implicitly[GetItem[List, Double, Int] { type Out = List[Double] }](t)
// this does not:
implicitly[GetItem[List, Double, Int] { type Out = List[Double] }] 
// Could not find implicit parameter for value e: Example.Main.GetItem[List, Double, Int]{type Out = List[Double]}

Любая помощь очень ценится, я смотрел на это в течение некоторого времени без особого успеха.


person Chris J Harris    schedule 13.11.2020    source источник


Ответы (2)


Кажется, это еще один пример чрезмерно ограниченных имплицитов (1 2 3 4 5 6). Кажется, это слишком много для имплицитов за один шаг. Компилятору не нравятся сложные типы, такие как (A, B), H :: L и A[T] (в нашем случае) при уточнении типа. Если мы заменим

implicit def ifRefIsInt[A[_], T]: GetItem[A, T, Int] { type Out = A[T] } = 
  new GetItem[A, T, Int] {
    type Out = A[T]
    def ret(a: A[T], ref: Int): Out = a
  }

с участием

implicit def ifRefIsInt[A[_], T, O](implicit 
  ev: A[T] =:= O
): GetItem[A, T, Int] { type Out = O } = new GetItem[A, T, Int] {
  type Out = O
  def ret(a: A[T], ref: Int): Out = a
}

тогда

implicitly[GetItem.Aux[List, Double, Int, List[Double]]]
implicitly[GetItem[List, Double, Int] { type Out = List[Double] }]

скомпилировать: https://scastie.scala-lang.org/P5iXP2ZfQUCKEIMukYyqIg (Scala 2.13.3 )

Компилятор почему-то проглатывает предупреждение (при включенном -Xlog-implicits). Если я запускаю неявный поиск вручную

import scala.language.experimental.macros
import scala.reflect.macros.{whitebox, contexts}

def foo[A]: Unit = macro fooImpl[A]

def fooImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
  import c.universe._

  val context = c.asInstanceOf[contexts.Context]
  val global: context.universe.type = context.universe
  val analyzer: global.analyzer.type = global.analyzer
  val callsiteContext = context.callsiteTyper.context

  val typ = weakTypeOf[A]

  val search = new analyzer.ImplicitSearch(
    tree = EmptyTree.asInstanceOf[global.Tree],
    pt = typ.asInstanceOf[global.Type],
    isView = false,
    context0 = callsiteContext.makeImplicit(reportAmbiguousErrors = true),
    pos0 = c.enclosingPosition.asInstanceOf[scala.reflect.internal.util.Position]
  )

  println(s"allImplicits=${search.allImplicits}")

  q""
}

тогда

foo[GetItem[List, Double, Int] { type Out = List[Double] }]

выдает предупреждение

App.this.GetItem.ifRefIsInt is not a valid implicit value for App.GetItem[List,Double,Int]{type Out = List[Double]} because:
hasMatchingSymbol reported error: polymorphic expression cannot be instantiated to expected type;
 found   : [A[_], T]App.GetItem[A,T,Int]{type Out = A[T]}
 required: App.GetItem[List,Double,Int]{type Out = List[Double]}

scalac: allImplicits=List()

т.е. A, T не подразумеваются.

person Dmytro Mitin    schedule 13.11.2020
comment
Благодарность! Из других связанных примеров я смутно могу понять, что подразумевается под чрезмерно ограниченным неявным, но я не думаю, что доверяю себе обязательно предсказывать или диагностировать их. На ваш взгляд, поможет ли переход на Scala 3 в решении подобных проблем? Кажется, он справляется с ними лучше, а проект, над которым я работаю, имеет мало зависимостей, поэтому его, вероятно, можно без проблем переместить. - person Chris J Harris; 13.11.2020
comment
@Chrisper Что ж, многие проблемы исчезают, когда мы заменяем Scalac на Dotty. Но с другой стороны, в Dotty возникают проблемы, которых нет в Scalac. github.com/lampepfl/dotty/issues/8882 - person Dmytro Mitin; 13.11.2020

Не уверен, почему это не работает, но хороший способ решить такие проблемы - использовать шаблон Aux для поднятия элементов типа в параметр типа, улучшающий разрешение.

trait GetItem[A[_], T, R] {
  type Out
  def ret(a: A[T], ref: R): Out
}

object GetItem {
  type Aux[A[_], T, R, O] = GetItem[A, T, R] { type Out = O }

  implicit def ifRefIsInt[A[_], T]: Aux[A, T, Int, A[T]] = new GetItem[A, T, Int] {
    type Out = A[T]
    def ret(a: A[T], ref: Int): Out = a
  }
}

Что вы можете проверить так:

implicitly[GetItem.Aux[List, Double, Int, List[Double]]]
// res: GetItem.Aux[List, Double, Int, List[Double]] = ...

implicitly[GetItem[List, Double, Int]] 
// res: GetItem[List, Double, Int] = ...
person Luis Miguel Mejía Suárez    schedule 13.11.2020
comment
Спасибо. Я фанат шаблона Aux, я просто хотел, чтобы пример был простым. Я честно подумываю о том, чтобы просто перезагрузить свой компьютер и посмотреть, имеет ли это значение. - person Chris J Harris; 13.11.2020
comment
Итак (перезапустив мой компьютер без каких-либо последствий), я обнаружил, что ваш ответ (с шаблоном Aux) отлично работает на моем ПК, тогда как мой исходный пример - нет. Это решение, но меня все еще беспокоит вопрос, почему оригинал не работает. - person Chris J Harris; 13.11.2020