Skip to content

Code from docs does not compile (typeclass derivation with macros) #10381

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

Closed
ghostdogpr opened this issue Nov 19, 2020 · 3 comments · Fixed by #10382
Closed

Code from docs does not compile (typeclass derivation with macros) #10381

ghostdogpr opened this issue Nov 19, 2020 · 3 comments · Fixed by #10382

Comments

@ghostdogpr
Copy link
Contributor

Hi! I was trying to learn how to derive typeclass instances with macros but the code from How to write a type class derived method using macros does not compile with 3.0.0-M1.

Minimized example

import scala.deriving._
import scala.quoted._
import scala.quoted.matching._

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 eqProduct[T](body: (T, T) => Boolean): Eq[T] =
    new Eq[T] {
      def eqv(x: T, y: T): Boolean = body(x, y)
    }

  def eqSum[T](body: (T, T) => Boolean): Eq[T] =
    new Eq[T] {
      def eqv(x: T, y: T): Boolean = body(x, y)
    }

  def summonAll[T](t: Type[T])(using qctx: QuoteContext): List[Expr[Eq[_]]] = t match {
    case '[String *: $tpes] => '{ summon[Eq[String]] }  :: summonAll(tpes)
    case '[Int *: $tpes]    => '{ summon[Eq[Int]] }     :: summonAll(tpes)
    case '[$tpe *: $tpes]   => derived(using tpe, qctx) :: summonAll(tpes)
    case '[EmptyTuple] => Nil
  }

  given derived[T: Type](using qctx: QuoteContext) as Expr[Eq[T]] = {
    import qctx.reflect._

    val ev: Expr[Mirror.Of[T]] = Expr.summon(using '[Mirror.Of[T]]).get

    ev match {
      case '{ $m: Mirror.ProductOf[T] { type MirroredElemTypes = $elementTypes }} =>
        val elemInstances = summonAll(elementTypes)
        val eqProductBody: (Expr[T], Expr[T]) => Expr[Boolean] = (x, y) => {
          elemInstances.zipWithIndex.foldLeft(Expr(true: Boolean)) {
            case (acc, (elem, index)) =>
              val e1 = '{$x.asInstanceOf[Product].productElement(${Expr(index)})}
              val e2 = '{$y.asInstanceOf[Product].productElement(${Expr(index)})}

              '{ $acc && $elem.asInstanceOf[Eq[Any]].eqv($e1, $e2) }
          }
        }
        '{
          eqProduct((x: T, y: T) => ${eqProductBody('x, 'y)})
        }

      case '{ $m: Mirror.SumOf[T] { type MirroredElemTypes = $elementTypes }} =>
        val elemInstances = summonAll(elementTypes)
        val eqSumBody: (Expr[T], Expr[T]) => Expr[Boolean] = (x, y) => {
          val ordx = '{ $m.ordinal($x) }
          val ordy = '{ $m.ordinal($y) }

          val elements = Expr.ofList(elemInstances)
          '{
              $ordx == $ordy && $elements($ordx).asInstanceOf[Eq[Any]].eqv($x, $y)
          }
        }

        '{
          eqSum((x: T, y: T) => ${eqSumBody('x, 'y)})
        }
    }
  }
}

object Macro3 {
  extension [T](x: =>T)
    inline def === (y: =>T)(using eq: Eq[T]): Boolean = eq.eqv(x, y)

  implicit inline def eqGen[T]: Eq[T] = ${ Eq.derived[T] }
}

Output

[error] -- [E006] Not Found Error: /Users/pierre/GIT/dotty-example-project/src/main/scala/Test.scala:29:71 
[error] 29 |      case '[String *: $tpes] => '{ summon[Eq[String]] }  :: summonAll(tpes)
[error]    |                                                                       ^^^^
[error]    |                                                         Not found: tpes
[error] -- [E006] Not Found Error: /Users/pierre/GIT/dotty-example-project/src/main/scala/Test.scala:30:71 
[error] 30 |      case '[Int *: $tpes]    => '{ summon[Eq[Int]] }     :: summonAll(tpes)
[error]    |                                                                       ^^^^
[error]    |                                                         Not found: tpes
[error] -- [E006] Not Found Error: /Users/pierre/GIT/dotty-example-project/src/main/scala/Test.scala:31:71 
[error] 31 |      case '[$tpe *: $tpes]   => derived(using tpe, qctx) :: summonAll(tpes)
[error]    |                                                                       ^^^^
[error]    |                                                         Not found: tpes
[error] -- [E006] Not Found Error: /Users/pierre/GIT/dotty-example-project/src/main/scala/Test.scala:31:47 
[error] 31 |      case '[$tpe *: $tpes]   => derived(using tpe, qctx) :: summonAll(tpes)
[error]    |                                               ^^^
[error]    |                                               Not found: tpe
[warn] -- Warning: /Users/pierre/GIT/dotty-example-project/src/main/scala/Test.scala:38:53 
[warn] 38 |      val ev: Expr[Mirror.Of[T]] = Expr.summon(using '[Mirror.Of[T]]).get
[warn]    |                                                     ^^^^^^^^^^^^^^^
[warn]    |Consider using canonical type constructor scala.quoted.Type[Mirror.Of[T]] instead
[error] -- [E006] Not Found Error: /Users/pierre/GIT/dotty-example-project/src/main/scala/Test.scala:42:40 
[error] 42 |          val elemInstances = summonAll(elementTypes)
[error]    |                                        ^^^^^^^^^^^^
[error]    |                                        Not found: elementTypes
[error] -- [E006] Not Found Error: /Users/pierre/GIT/dotty-example-project/src/main/scala/Test.scala:57:40 
[error] 57 |          val elemInstances = summonAll(elementTypes)
[error]    |                                        ^^^^^^^^^^^^
[error]    |                                        Not found: elementTypes
[warn] one warning found
[error] 6 errors found
@smarter
Copy link
Member

smarter commented Nov 19, 2020

There's an up-to-date example in our testsuite, https://github.com/lampepfl/dotty/blob/master/tests/run-macros/i8007/Macro_3.scala, though the syntax has already changed compared to 3.0.0-M1, so if you're using that we need to go back in the history a bit: https://github.com/lampepfl/dotty/blob/3.0.0-M1/tests/run-macros/i8007/Macro_3.scala

@smarter
Copy link
Member

smarter commented Nov 19, 2020

So, PR welcome to copy-paste https://github.com/lampepfl/dotty/blob/master/tests/run-macros/i8007/Macro_3.scala into the docs in place of the version that is there now.

@ghostdogpr
Copy link
Contributor Author

@smarter Thanks! Submitted a PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants