-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix #8007: Add regression and show type class derivation with macros #8011
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@deusaquilus this works with the high-level API 😍 (I just discovered myself, thanks @nicolasstucki) case '{ $m: Mirror.ProductOf[T]{ type MirroredElemLabels = $t } } => { |
I see how I can use this to get the labels. Can I use the mirror element given SomeTypeclass[T] = SomeTypeclass.derived(m) ? |
I assume yes. Since #8007 was about labels, I'll update the docs and merge this. |
Co-authored-by: Fengyun Liu <[email protected]>
I updated the PR to demonstrate that in |
@julienrf since we updated the code (mainly Macro 3 that shows the full ported example) can you have one more look in the doc page? Thankssss! |
Yeap. As shown in the test file. I updated the doc to make it more clear.
// answering to a (now) deleted comment 🎉 |
Also, is it possible to do this kind of thing using summonFrom inside ‘derived’ to summon the mirror? |
|
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 all you have to do this this! 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")) )
} Could you please add this example to the regression test as well? |
Indeed, however the point of the prototype and the documentation page was to show that derivation can be implemented with macros as well. |
@biboudis I still see the What happens if a data type has a field of type |
@biboudis My point is that you actually don't need to extend |
I've commented more on this in #7875 which we can treat as a separate issue. |
@deusaquilus IIUC, using
The schema you propose is also known as “auto” derivation, whereas |
@julienrf I agree that semi-auto derivation is a very good thing but it should certainly be known to the community that auto-derivation is also possible (and how it do it should be documented...). My impression from the documentation was it's not. If you want I can file a separate issue for this or just rename #7875. |
Indeed your encoding is more elegant and avoids the cast. Without it made the code a bit more straightforward into porting it from the inline version (even though the original doesn't use
Indeed the |
One problem with the macro-based approach is that since generated/synthesised definitions are not top-level, as a result they are not cached and it is not clear to me how this can be achieved (only with macro) code. @julienrf do you have any ideas regarding this? Otherwise, I'll need to note this somewhere. |
I guess there is a way to not require this exhaustivity. How to make this extensible? A derived instance of a type class |
If the following code: case class User(name: String, age: Int) derives Show Desugars to: case class User(name: String, age: Int) derives Show
object User {
given as Show[User] = Show.derived
} Then the derived instance will be cached regardless of whether the derivation mechanism is implemented with macros or |
Yes! You are right about the |
Shapeless has a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in the long run we should move the text of this example and of the main example from derivation.md into their respective pos tests, and point to them from the docs. IMO these doc pages should be written in the style of a specification rather than "this and that by example"...
This PR adds:
Macro_1
). The regression test from that issue mixed macro, mirrors and inline definition. The observed result was thatMirorredElemLabels
returned zero results, but there were two problems with original test.Macro_2
)Macro_3
)derived
method with macros