I have a enum that represents a container and two case classes:
enum Container[+A]:
case Value(value: A)
case Default(default: A)
def get: A = this match
case Value(value) => value
case Default(default) => default
case class PersonTemplate(name: Container[String], age: Container[Int], enabled: Container[Boolean])
case class Person(name: String, age: Int, enabled: Boolean)
and I want to write a generic function in Scala 3 that converts all case classes like PersonTemplate into their counterpart like Person, something like:
def extract[I <: Product, O <: Product](input: I): O = ???
val initial = PersonTemplate(Value("John"), Default(12), Default(true))
val final = extract[PersonTemplate, Person](initial)
// Result: Person("John", 12, true)
I tried several approaches but none of them was succesfull and mainly because I don't understand how to use Scala 3 Tuple that to me looks different from Scala 2 Shapeless' HList (and even in shapeless I'm not that good).
My overall approach was:
- Convert the case class in a Tuple. For this I found
Tuple.fromProductTyped - Constrain each element to be a
Container[_]. I foundTuple.IsMappedByto guarantee the tuple has the correct shape andTuple.InverseMapthat seems to extract the type inside the container. I'm not sure where to put this code, though. - Apply a (poly?)function to each value that calls
Container.get. With the little I found around the net I ended up using a lot of.asInstanceOfand it didn't seem right to me. - Convert the resulting
Tupleto the output type usingsummon[Mirror.Of[O]].fromProduct(output)
For sake of completeness, this is the last code I tried, that of course doesn't work:
def resolve[I <: Product: Mirror.ProductOf, O: Mirror.ProductOf](input: I): O =
val processed =
Tuple
.fromProductTyped(input)
.map { [T] => (value: T) => ??? }
summon[Mirror.Of[O]].fromProduct(processed)
type ExtractG = [G] =>> G match {
case Container[a] => a
}
def process[I <: Tuple, O <: Tuple](input: I)(using Tuple.IsMappedBy[Container][I]): O =
input.map { [A] => (a: A) =>
a.asInstanceOf[Container[_]].get.asInstanceOf[ExtractG[A]]
}.asInstanceOf[O]
Well if you don't mind a bit of casting, you can do this:
Requested
Fromandevonly serve to prove the type-safety of all the casting. IMO the mirror machinery lacks in ability to operate on things in a type-safe way without macros like shapeless can.