Open deusaquilus opened 4 years ago
It would be good to minimize this further. Right now it's hard to tell whether this is a bug or not.
The example is incomplete and cannot be compiled. @deusaquilus could you create a version that contains all the code needed to compile it and try to minimize it a bit more.
Please give me a couple days and I will reproduce the issue in Scastie
As it turns out, what I tried doing here is possible by using derived
instead. The following example of Eq[T]
has been modified to show this.
(A bit more detail: Basically, the whole pattern of "summon a Mirror for T, otherwise T is a leaf node" is completely superseded by Dotty's generic-derivation mechanism. The example I posted at the top can be fulfilled by generic derivation. The only problem was, from the Documentation I thought it was necessary to provided typeclass instances via class MyClass derives TC
or given TC[MyClass]
. As it turns out, if you write your derived
method in a particular way, this is not needed.)
package example.eqalternate
import scala.deriving._
import scala.quoted._
import scala.quoted.matching._
import scala.compiletime.{summonFrom, erasedValue}
trait Eq[T] {
def eqv(x: T, y: T): Boolean
}
object Eq {
given Eq[String] {
def eqv(x: String, y: String) = x == y
}
given Eq[Int] {
def eqv(x: Int, y: Int) = x == y
}
def check(elem: Eq[_])(x: Any, y: Any): Boolean =
elem.asInstanceOf[Eq[Any]].eqv(x, y)
def iterator[T](p: T) = p.asInstanceOf[Product].productIterator
def eqSum[T](s: Mirror.SumOf[T], elems: List[Eq[_]]): Eq[T] =
new Eq[T] {
def eqv(x: T, y: T): Boolean = {
val ordx = s.ordinal(x)
(s.ordinal(y) == ordx) && check(elems(ordx))(x, y)
}
}
def eqProduct[T](p: Mirror.ProductOf[T], elems: List[Eq[_]]): Eq[T] =
new Eq[T] {
def eqv(x: T, y: T): Boolean =
iterator(x).zip(iterator(y)).zip(elems.iterator).forall {
case ((x, y), elem) => check(elem)(x, y)
}
}
inline def summonInstance[T]: Eq[T] = summonFrom {
case t: Eq[T] => t
}
inline def summonAll[T <: Tuple]: List[Eq[_]] = inline erasedValue[T] match {
case _: Unit => Nil
case _: (t *: ts) => summonInstance[t] :: summonAll[ts]
}
inline def derived[T]: Eq[T] =
summonFrom {
case ev: Mirror.Of[T] =>
inline ev match {
case s: Mirror.SumOf[T] => eqSum(s, summonAll[s.MirroredElemTypes])
case p: Mirror.ProductOf[T] => eqProduct(p, summonAll[p.MirroredElemTypes])
}
}
}
object Macro_4 {
implicit inline def eqGen[T]: Eq[T] = Eq.derived
inline def [T](x: =>T) === (y: =>T): Boolean = {
val eq = summon[Eq[T]]
eq.eqv(x, y)
}
}
Then you can just do:
package example.eqalternate
@main def eqTest() = {
case class Address(street: String) // Hurrah! Don't need to do 'derived'
case class Person(name: String, address: Address) // Hurrah! Don't need to do 'derived'
import Macro_4._
println( Person("Joe", Address("123")) === Person("Joe", Address("123")) )
}
I did not know that the above pattern was possible. Please add it to the documentation page and/or make a regression test for it.
Now I see! Where do you think its best to mention this in the documentation so that it is clear? Can you leave a suggestion on #8011 as a comment? And I will incorporate it in that PR! Thx! @deusaquilus
So a couple of notes about derivation.md
.
given match
construct doesn't exist anymore.derived
:
inline given derived[T]: (m: Mirror.Of[T]) => Eq[T] = {
val elemInstances = summonAll[m.MirroredElemTypes]
inline m match {
case s: Mirror.SumOf[T] => eqSum(s, elemInstances)
case p: Mirror.ProductOf[T] => eqProduct(p, elemInstances)
}
}
inline given derived[T]: (given m: Mirror.Of[T]) => Eq[T] = {
val elemInstances = summonAll[m.MirroredElemTypes]
inline m match {
case s: Mirror.SumOf[T] => eqSum(s, elemInstances)
case p: Mirror.ProductOf[T] => eqProduct(p, elemInstances)
}
}
inline given derived[T](given m: Mirror.Of[T]): Eq[T] = {
val elemInstances = summonAll[m.MirroredElemTypes]
inline m match {
case s: Mirror.SumOf[T] => eqSum(s, elemInstances)
case p: Mirror.ProductOf[T] => eqProduct(p, elemInstances)
}
}
summonFrom
.
inline given derived[T]: Eq[T] =
summonFrom {
case m: Mirror.Of[T] =>
val elemInstances = summonAll[m.MirroredElemTypes]
inline m match {
case s: Mirror.SumOf[T] => eqSum(s, elemInstances)
case p: Mirror.ProductOf[T] => eqProduct(p, elemInstances)
}
}
I would go as far as to say that there should be a regression test for all four variants and derivation.md
should mention the summonFrom
approach.
Also, interestingly eqGen
above is does not compile when I try inline given
instead of inline implicit def
. That needs to be investigated before implicits are deprecated.
object Macro_4 {
implicit inline def eqGen[T]: Eq[T] = Eq.derived
// inline given autoEq[T]: Eq[T] = Eq.derived // This does not work!!
inline def [T](x: =>T) === (y: =>T): Boolean = {
val eq = summon[Eq[T]]
eq.eqv(x, y)
}
}
Here's how I would do it: #8087.
In Quill, we frequently use the following pattern:
For some given type T (note, we do not have an actual instance of T yet!)
I am trying to reproduce this kind of behavior with mirrors as I understand it is no longer possible to do things like
tpe.members.collect
. The trouble is, it isn't working.minimized code
expectation
My expectation is for this code to work properly and go through the children of some type T. Instead, there are several very odd behaviors.
Firstly, I get an implicit resolution error because for some reason, it will match
Fooify[T]
for anyT
.Say I only keep
Fooify[String]
around and comment out the otherFooify
, then, the following error happens:Edit:
As it turns out, you can implement this pattern using generic derivation so this is just a documentation issue. See further below for a better example:
(This is how to do the same thing with typeclass derivation)
Then all you need to do is this:
More Edit:
As it turns out this pattern is well known as auto-derivation. I think it should definitely be documented on the typeclass-derivation page.