Есть ли в scala условия, при которых неявное представление не может распространяться на другие неявные функции?

Предполагая, что был определен класс с именем «summoner», который может вызывать неявные представления из области видимости:

  case class Summoner[R]() {

    def summon[T](v: T)(implicit ev: T => R): R = ev(v)
  }

Я обнаружил, что он работает большую часть времени, но есть случаи, когда он не работает, например Ниже приводится (не слишком) короткий случай, в котором используется библиотека singleton-ops:


import shapeless.Witness
import singleton.ops.+
import singleton.ops.impl.Op

  trait Operand {

    def +[
        X >: this.type <: Operand,
        Y <: Operand
    ](that: Y): Op2[X, Y] = {

      Op2[X, Y](this, that)
    }
  }

  object Operand {

    abstract class ProvenToBe[O <: Arity]()(implicit val out: O) extends Operand {}

    object ProvenToBe {

      implicit class Trivial[O <: Arity, T <: ProvenToBe[O]](
          val self: T
      ) extends Proof {

        override type Out = O

        override def out: Out = self.out
      }
    }
  }

  trait Proof extends Serializable {

    def self: Operand

    type Out <: Arity

    def out: Out
  }

  object Proof {

    trait Out_=[+O <: Arity] extends Proof {
      type Out <: O
    }

    trait Invar[S] extends Out_=[Arity.Const[S]] {

      type SS = S
    }
  }

  trait Arity extends Operand {}

  object Arity {

    trait Const[S] extends Arity {

      type SS = S
    }

    object Const {

      implicit class Same[S](val self: Const[S]) extends Proof.Invar[S] {
        override type Out = Const[S]

        override def out: Const[S] = self
      }
    }

    class FromOp[S <: Op]() extends Const[S]

    object FromOp {

      implicit def summon[S <: Op](implicit s: S): FromOp[S] = new FromOp[S]()
    }

    class FromLiteral[S <: Int](val w: Witness.Lt[Int]) extends Const[S] {}

    object FromLiteral {

      implicit def summon[S <: Int](implicit w: Witness.Aux[S]): FromLiteral[S] =
        new FromLiteral[S](w)
    }

    def apply(w: Witness.Lt[Int]): FromLiteral[w.T] = {

      FromLiteral.summon[w.T](w) //TODO: IDEA inspection error
    }

  }

  case class Op2[
      +A1 <: Operand,
      +A2 <: Operand
  ](
      a1: A1,
      a2: A2
  ) extends Operand {}

  object Op2 {

    implicit class ProveInvar[
        A1 <: Operand,
        A2 <: Operand,
        S1,
        S2
    ](
        val self: Op2[A1, A2]
    )(
        implicit
        bound1: A1 => Proof.Invar[S1],
        bound2: A2 => Proof.Invar[S2]
    ) extends Proof.Invar[S1 + S2] {

      override type Out = Arity.FromOp[S1 + S2]

      override def out: Out = new Arity.FromOp[S1 + S2]()
    }
  }

При попытке использовать неявное представление как есть:

  implicit val a = Arity(3)
  implicit val b = Arity(4)

  val op = a + b

  op: Proof // implicit view works

Но при использовании призывателя:

  val summoner = Summoner[Proof]()

  summoner.summon(op) // oops

[Error] /home/peng/git/shapesafe/spike/src/main/scala/edu/umontreal/kotlingrad/spike/arity/package.scala:141: No implicit view available from edu.umontreal.kotlingrad.spike.arity.package.Op2[edu.umontreal.kotlingrad.spike.arity.package.Arity.FromLiteral[Int(3)],edu.umontreal.kotlingrad.spike.arity.package.Arity.FromLiteral[Int(4)]] => edu.umontreal.kotlingrad.spike.arity.package.Proof.
one error found

FAILURE: Build failed with an exception.

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

  1. В чем причина такого поведения?

  2. Откуда ты знаешь?


person tribbloid    schedule 29.06.2020    source источник


Ответы (1)


Я рассказал вам об отладке имплицитов с reify, -Xlog-implicits и ручном разрешении имплицитов в Можно ли в scala 2 или 3 отлаживать процесс неявного разрешения во время выполнения?

Если вы печатаете дерево

import scala.reflect.runtime.universe._
println(reify{
  op: Proof
}.tree)

вы увидите, как разрешается неявное преобразование

(App.this.Op2.ProveInvar(App.this.op)(((self) => Arity.this.Const.Same(self)), ((self) => Arity.this.Const.Same(self))): App.this.Proof)

Действительно, вручную разрешено

summoner.summon[Op2[Arity.FromLiteral[3], Arity.FromLiteral[4]]](op)(t =>
  Op2.ProveInvar(t)(a1 => Arity.Const.Same(a1), a2 => Arity.Const.Same(a2))
)

компилируется, но сам компилятор не может найти неявное преобразование

summoner.summon[Op2[Arity.FromLiteral[3], Arity.FromLiteral[4]]](op) //doesn't compile

Если вы включите -Xlog-implicits, вы увидите подробности

Information: $conforms is not a valid implicit value for App.Arity.FromLiteral[3] => App.Proof.Invar[Nothing] because:
hasMatchingSymbol reported error: type mismatch;
 found   : App.Arity.FromLiteral[3] => App.Arity.FromLiteral[3]
 required: App.Arity.FromLiteral[3] => App.Proof.Invar[Nothing]
  summoner.summon[Op2[Arity.FromLiteral[3], Arity.FromLiteral[4]]](op)

Information: Arity.this.Const.Same is not a valid implicit value for App.Arity.FromLiteral[3] => App.Proof.Invar[Nothing] because:
hasMatchingSymbol reported error: type mismatch;
 found   : App.Arity.Const[Nothing] => App.Arity.Const.Same[Nothing]
 required: App.Arity.FromLiteral[3] => App.Proof.Invar[Nothing]
  summoner.summon[Op2[Arity.FromLiteral[3], Arity.FromLiteral[4]]](op)

Information: App.this.Op2.ProveInvar is not a valid implicit value for App.Op2[App.Arity.FromLiteral[3],App.Arity.FromLiteral[4]] => App.Proof because:
hasMatchingSymbol reported error: No implicit view available from App.Arity.FromLiteral[3] => App.Proof.Invar[Nothing].
  summoner.summon[Op2[Arity.FromLiteral[3], Arity.FromLiteral[4]]](op)

Как я уже говорил вам в Когда вызов функции scala с макросом времени компиляции, как плавно переключиться при отказе, когда это вызывает ошибки компиляции? вы не всегда можете проверить наличие неявного преобразования с помощью неявного параметра (implicit ev: T => R). Иногда существование неявного экземпляра T => R отличается от существования неявного преобразования T => R (не все неявные преобразования основаны на классах типов). Попробуйте заменить

val summoner = Summoner[Proof]()
summoner.summon(op) //doesn't compile

с участием

summonImplicitView[Proof](op) //compiles

def summonImplicitView[B] = new PartiallyAppliedSummonImplicitView[B]

class PartiallyAppliedSummonImplicitView[B] {
  def apply[A](a: A): B = macro summonImplicitViewImpl[A, B]
}

def summonImplicitViewImpl[A: c.WeakTypeTag, B: c.WeakTypeTag](c: whitebox.Context)(a: c.Tree): c.Tree = {
  import c.universe._
  val tpA = weakTypeOf[A]
  val tpB = weakTypeOf[B]
  val view = c.inferImplicitView(tree = a, from = tpA, to = tpB, silent = false)
  q"$view($a)"
}

Вы также можете попробовать ввести class ImplicitView из вопрос

case class Summoner[R]() {
  def summon[T](v: T)(implicit ev: ImplicitView[T, R]): R = ev.instance(v)
}

val summoner = Summoner[Proof]()
summoner.summon(op) // compiles

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


Думаю, я наконец нашел проблему (если мы ее исправим, Summoner будет работать без макросов). Вы снова потеряли типографскую изысканность.

case class Summoner[R]() {
  def summon[T](v: T)(implicit ev: T => R): R = ev(v)
}

val summoner = Summoner[Proof {type Out <: Arity.FromOp[3 + 4]}]() 

// or even
//val summoner = Summoner[Proof {type Out <: Arity.FromOp[3 + 4]; type SS = 3 + 4}]()

summoner.summon(op) //compiles

Вот почему у вас было Nothing в -Xlog-implicits журналах.


Думаю, я исправил ваш код. При написании своей логики вы смешивали неявные экземпляры с неявными преобразованиями. Неявные преобразования сложны. Я бы рекомендовал писать вашу логику только в терминах классов типов (MyTransform), а затем, если вам нужны преобразования, определите их (myConversion) по отношению к этим классам типов.

// doesn't extend T => R intentionally
trait MyTransform[-T, +R] {
  def transform(v: T): R
}
implicit def myConversion[T, R](v: T)(implicit mt: MyTransform[T, R]): R = mt.transform(v)

case class Summoner[R]() {    
  def summon[T](v: T)(implicit ev: MyTransform[T, R]): R = ev.transform(v)
}

trait Operand {  
  def +[
    X >: this.type <: Operand,
    Y <: Operand
  ](that: Y): Op2[X, Y] = {    
    Op2[X, Y](this, that)
  }
}
object Operand {   
  abstract class ProvenToBe[O <: Arity]()(implicit val out: O) extends Operand {}    
  object ProvenToBe {   
    implicit def trivial[O <: Arity, T <: ProvenToBe[O]]: MyTransform[T, Trivial[O, T]] = self => new Trivial(self)

    /*implicit*/ class Trivial[O <: Arity, T <: ProvenToBe[O]](
      val self: T
    ) extends Proof {   
      override type Out = O
      override def out: Out = self.out
    }
  }
}

trait Proof extends Serializable {    
  def self: Operand
  type Out <: Arity
  def out: Out
}
object Proof {
  trait Out_=[+O <: Arity] extends Proof {
    type Out <: O
  }

  trait Invar[S] extends Out_=[Arity.Const[S]] {
    type SS = S
  }
}

trait Arity extends Operand {}
object Arity {
  trait Const[S] extends Arity {
    type SS = S
  }
  object Const {
    implicit def same[S]: MyTransform[Const[S], Same[S]] = self => new Same(self)

    /*implicit*/ class Same[S](val self: Const[S]) extends Proof.Invar[S] {
      override type Out = Const[S]
      override def out: Const[S] = self
    }
  }

  class FromOp[S <: Op]() extends Const[S]
  object FromOp {
    implicit def summon[S <: Op](implicit s: S): FromOp[S] = new FromOp[S]()
  }

  class FromLiteral[S <: Int](val w: Witness.Lt[Int]) extends Const[S] {}
  object FromLiteral {
    implicit def summon[S <: Int](implicit w: Witness.Aux[S]): FromLiteral[S] =
      new FromLiteral[S](w)
  }

  def apply(w: Witness.Lt[Int]): FromLiteral[w.T] = {
    FromLiteral.summon[w.T](w) //TODO: IDEA inspection error
  }
}

case class Op2[
  +A1 <: Operand,
  +A2 <: Operand
](
   a1: A1,
   a2: A2
 ) extends Operand {}
object Op2 {
  implicit def proveInvar[A1 <: Operand, A2 <: Operand, S1, S2](implicit
    bound1: MyTransform[A1, Proof.Invar[S1]],
    bound2: MyTransform[A2, Proof.Invar[S2]]
  ): MyTransform[Op2[A1, A2], ProveInvar[A1, A2, S1, S2]]
  = self => new ProveInvar(self)

  /*implicit*/ class ProveInvar[
    A1 <: Operand,
    A2 <: Operand,
    S1,
    S2
  ](
     val self: Op2[A1, A2]
   )/*(
     implicit
     bound1: A1 => Proof.Invar[S1],
     bound2: A2 => Proof.Invar[S2]
   )*/ extends Proof.Invar[S1 + S2] {
    override type Out = Arity.FromOp[S1 + S2]
    override def out: Out = new Arity.FromOp[S1 + S2]()
  }
}

implicit val a = Arity(3)
implicit val b = Arity(4)

val op = a + b

op: Proof // compiles

val summoner = Summoner[Proof]()
summoner.summon(op) // compiles
person Dmytro Mitin    schedule 29.06.2020
comment
Большое спасибо за ответ, на самом деле тип [3 + 4] намеренно пропущен, так как достаточно строгий вывод типов должен уметь его вывести (это единственное представление в области видимости). [Ничего] в журнале поиска действительно является точным наблюдением, но оно также неверно, поскольку инвар [S] невариантен или ковариантен. Если он контравариантен, то на самом деле имеет смысл искать из Инвара [Ничего], так как это будет надмножество всех видов Инвара [_], но компилятор выбирает его только тогда, когда используется op: Proof - person tribbloid; 01.07.2020
comment
Я пробовал реализацию, в которой определено Invar[+S] и val summoner = Summoner[Proof.Invar[Any]](), поиск по-прежнему начинается с ничего. Как-то странно - person tribbloid; 01.07.2020
comment
@tribbloid Думаю, я исправил ваш код (без макросов). Я заменил неявные преобразования классом типа. Смотрите обновление. - person Dmytro Mitin; 01.07.2020
comment
@tribbloid [Nothing] в журнале поиска действительно является точным наблюдением, но оно также неверно, поскольку Invar[S] невариантно или ковариантно. Обычно Nothing сигнализирует не о дисперсии, а о том, что какой-то тип не выводится. - person Dmytro Mitin; 01.07.2020
comment
большое спасибо! Я видел обновление, и оно, кажется, немного задерживает решение проблемы: попробуйте a + b + b или a + b + a + b, они все вырвутся на свободу: - ‹Более того, в обоих наших примерах IntelliJ IDE успешно показывает неявные подсказки: github.com/tribbloid/shapesafe/issues/1. Петербург перехитрил EPFL? : - › - person tribbloid; 01.07.2020
comment
@tribbloid, похоже, компилируется как в 2.13.2 scastie.scala-lang.org/pzJ6KHu5QUKoIw50Ht и 2.12.11 scastie.scala-lang.org/k3aacN98T7OV5lHHiFdW3A - person Dmytro Mitin; 02.07.2020
comment
Да, вы правы, я ошибся и использовал старое определение призывателя. Ничего себе, это взорвало меня. Оказывается, эта граница представления не имеет особой обработки. Я посмотрю, смогу ли я привлечь внимание к системе отслеживания проблем в scalac - person tribbloid; 02.07.2020
comment
@tribbloid Здесь Мартин Одерски объясняет, почему наличие неявных преобразований ухудшает вывод типов Contributors.scala-lang.org/t/ - person Dmytro Mitin; 04.07.2020