'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 |