Home > Blockchain >  Scala 3: Deriving a typeclass for conversion between a case class and a tuple type
Scala 3: Deriving a typeclass for conversion between a case class and a tuple type

Time:04-29

I am aware that in Scala 3 it is now possible to convert between case class instances and tuples using Tuple.fromProductTyped and summon[Mirror.Of].fromProduct.

I would like to be able to define a typeclass that witnesses that a case class T can be converted to and from (A, B) using the aforementioned methods.

I've defined the typeclass like so:

trait IsPair[T, A, B]:
  def toTuple(t: T): (A, B)
  def fromTuple(ab: (A, B)): T

How can we derive IsPair for any conforming case class?

With the derivation in place, the following should compile and run without error:

case class Foo(a: Int, b: String)
val fooIsPair = summon[IsPair[Foo, Int, String]]

val foo = Foo(42, "qwerty")
val tuple = (42, "qwerty")

assert(fooIsPair.toTuple(foo) == tuple)
assert(fooIsPair.fromTuple(tuple) == foo)

CodePudding user response:

You can just use a Mirror but restrict it to be for pair-like case classes:

import scala.deriving.Mirror

sealed trait IsPair[T]:
  type A
  type B

  def toTuple(t: T): (A, B)
  def fromTuple(ab: (A, B)): T

object IsPair:
  type Aux[T, X, Y] = IsPair[T] { type A = X; type B = Y }

  inline def apply[T](using ev: IsPair[T]): ev.type = ev

  given instance[T <: Product, X, Y](using
    ev: Mirror.Product { type MirroredType = T; type MirroredMonoType = T; type MirroredElemTypes = (X, Y) }
  ): IsPair[T] with
    override final type A = X
    override final type B = Y

    override def toTuple(t: T): (X, Y) =
      Tuple.fromProductTyped(t)
    override def fromTuple(xy: (X, Y)): T =
      ev.fromProduct(xy)
end IsPair

That way we can do this:

val foo = Foo(42, "qwerty")
val tuple = (42, "qwerty")

val fooIsPair = IsPair[Foo]

assert(fooIsPair.toTuple(foo) == tuple)
assert(fooIsPair.fromTuple(tuple) == foo)

You can see the code running here.

  • Related