'Automatically deriving a transformer from a rich case class to a simple case class?

I have a simple case class that represents a pet:

case class Pet(name: String, age: Int)

Now say I have a case class that has a 1:1 mapping to Pet, but where all of the properties have been wrapped in some sort of "resolveable" container or monad:

case class ResolveableToPet(name: Future[String], age: Some[Int])

The transformation from the latter to the former is quite simple to define:

def resolvePet(rPet: ResolveableToPet)(implicit ec: ExecutionContext): Future[Pet] =
  rPet.flatMap(Pet(_, rPet.age.value))

Noticing that Future's flatMap and Some's value in some sense both "extract the value from within the container", I expect we could define an "extractor" typeclass that could treat them both equally, and thus would allow us – with enough work – to define resolvePet generically.

Maybe it would even look roughly something like this after all is said and done:

def resolve[R, T](resolveable: R)(implicit resolver: Resolver[R, T]): T =
  resolver.resolve(resoleveable)

I feel that this isn't even particularly a difficult or challenging problem, and having worked with cats often enough I'm noticing that this seems to align highly with the tools it provides. Such that I suspect that this has already been solved and I simply don't know the name by which to refer to this.

Is there a existing tooling – perhaps in cats, kittens, or scalaz – that solves this problem for me, or maybe simply gets me 75% of the way there?



Solution 1:[1]

Unfortunatelly, can't advice you auto deriving decision but I have one concise and not ugly solution. Take a look at Chimney library - it is very usefull library to make conversions between case classes. I write a simple example for you purposes but I'm sure, you can easily write another one more complex based on examples from documentation

import cats.Semigroupal
import cats.syntax.traverse._
import cats.instances.list._
import cats.instances.future._
import io.scalaland.chimney.TransformerFSupport
import io.scalaland.chimney.dsl._

import scala.collection.compat.Factory
import scala.concurrent.{Await, Future}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.DurationInt

case class LiftedFoo(a: Future[Int], b: Option[String])

case class Foo(a: Int, b: String)

// this transformer we write to call withFieldComputedF and pack our result into some Future
implicit val transformerFSupport: TransformerFSupport[Future] = new TransformerFSupport[Future] {
  override def pure[A](value: A) = Future.successful(value)

  override def product[A, B](fa: Future[A], fb: => Future[B]) = Semigroupal[Future].product(fa, fb)

  override def map[A, B](fa: Future[A], f: A => B) = fa.map(f)

  override def traverse[M, A, B](it: Iterator[A], f: A => Future[B])(implicit fac: Factory[B, M]): Future[M] =
    it.toList.traverse(f).map(fac.fromSpecific(_))
}

val foo: Future[Foo] = LiftedFoo(Future(1), Some("z - last"))
  .into[Foo]
  .withFieldComputedF(_.a, _.a)
  .withFieldComputed(_.b, _.b.getOrElse("")) // we can write here some fallback case for None in b field
  .transform

Await.result(event, 1.second) // Foo(1,z - last)

You can use same transformerFSupport for the same F[_] type and not write a lot of code to simple map case class into another.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Boris Azanov