2
\$\begingroup\$

I've been playing around with this example from the Scala 3 documentation, and I noticed that certain things duplicate functionality already present in the standard API; In particular, the summonAll (which was working for Eq[_] only) seemed to be a combination of

  • Tuple.Map[Tup, F] type constructor, which recursively deconstructs a tuple Tup and modifies each entry by wrapping it in F
  • compiletime.summonAll, which already knows how to summon elements for each entry of a tuple-type.

I think it was either done this way for didactic purposes, or maybe the API changed after the example was written.

Anyway, I've attempted to modify it, and to use the above mentioned definitions from the API, that's what I obtained:

import deriving.Mirror
import compiletime.{ erasedValue, summonAll }

trait Eq[T]:
  def eqv(x: T, y: T): Boolean

object Eq:
  inline given derived[T](using m: Mirror.Of[T]): Eq[T] =
    val elemInstances = summonAll[Tuple.Map[m.MirroredElemTypes, Eq]]
    inline m match
      case s: Mirror.SumOf[T] => eqSum(s, elemInstances)
      case p: Mirror.ProductOf[T] => eqProduct(p, elemInstances)

  def eqSum[T](s: Mirror.SumOf[T], elems: Tuple): Eq[T] = 
    new Eq[T]:
      def eqv(x: T, y: T): Boolean =
        val ordx = s.ordinal(x)
        (s.ordinal(y) == ordx) && check(elems.productElement(ordx))(x, y)
  
  def eqProduct[T](p: Mirror.ProductOf[T], elems: Tuple): Eq[T] =
    new Eq[T]:
      def eqv(x: T, y: T): Boolean =
        iterator(x).zip(iterator(y)).zip(elems.productIterator).forall{
          case ((x, y), elem) => check(elem)(x, y)
        }

  def iterator[T](p: T) = p.asInstanceOf[Product].productIterator

  def check(elem: Any)(x: Any, y: Any): Boolean = elem.asInstanceOf[Eq[Any]].eqv(x, y)

  given Eq[Int] with
    def eqv(x: Int, y: Int) = x == y
end Eq

case class Foo(a: Int, b: Int) derives Eq

Questions:

  • I had to introduce more Anys, in check in particular. Is there some way to specify that it's coming from a product type where each entry is a subtype of Eq[_]? Should one attempt to tighten some other types?
  • Is there anything else that could be shortened or simplified using the API methods?
  • Is p.asInstanceOf[Product] actually safe? Or should there be some additional conversion step that transforms the type T into Product?
\$\endgroup\$
1
  • \$\begingroup\$ I think p.asInstanceOf[Product] is safe, since there wouldn't be a given Mirror.ProductOf[T] otherwise. Also, it looks like casting is unavoidable for eqSum, although you might want to make these methods private. \$\endgroup\$
    – user
    Apr 23, 2021 at 14:53

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.